pax_global_header00006660000000000000000000000064152276657410014531gustar00rootroot0000000000000052 comment=9c9a8d34413ca2beb0b192b476f2ee288b99959f pr0m1th3as-datatypes-9c9a8d3/000077500000000000000000000000001522766574100160555ustar00rootroot00000000000000pr0m1th3as-datatypes-9c9a8d3/.gitattributes000066400000000000000000000000771522766574100207540ustar00rootroot00000000000000docs export-ignore logos export-ignore README.md export-ignore pr0m1th3as-datatypes-9c9a8d3/COPYING000066400000000000000000001045131522766574100171140ustar00rootroot00000000000000 GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . pr0m1th3as-datatypes-9c9a8d3/DESCRIPTION000066400000000000000000000006321522766574100175640ustar00rootroot00000000000000Name: datatypes Version: 1.2.7 Date: 2026-07-21 Author: Andreas Bertsatos Maintainer: Andreas Bertsatos Title: Data Types for GNU Octave Description: A package for providing extra data types and related functionality for GNU Octave. Categories: Datatypes Depends: octave (>= 11.1.0) Url: https://github.com/pr0m1th3as/datatypes Tracker: https://github.com/pr0m1th3as/datatypes/issues pr0m1th3as-datatypes-9c9a8d3/INDEX000066400000000000000000000015251522766574100166520ustar00rootroot00000000000000datatypes >> datatypes categorical arrays categorical iscategorical calendarDuration arrays calendarDuration caldays calweeks calmonths calquarters calyears iscalendarduration datetime arrays datetime isdatetime isnat NaT duration arrays duration days years hours minutes seconds milliseconds isduration string arrays string convertCharsToStrings convertStringsToChars table arrays table array2table cell2table csv2table ods2table ods2struct readtable struct2ods struct2xlsx struct2table xlsx2struct istable supplementary classes missing vartype @cell/disp @cell/display supplementary functions findgroups splitapply keyHash keyMatch parsePairedArguments compiled functions __ckeyHash__ __nkeyHash__ __datetime__ __csv2table__ __table2csv__ __ods2table__ __table2ods__ __xlsx2table__ __table2xlsx__ pr0m1th3as-datatypes-9c9a8d3/LICENSE.txt000066400000000000000000000003751522766574100177050ustar00rootroot00000000000000All software in the 'datatypes' package is published under the GNU General Public License verion 3 (GPLv3). The package's logo ('doc/datatypes.png') is licensed under a Creative Commons Attribution-ShareAlike 4.0 International license (CC BY-SA 4.0). pr0m1th3as-datatypes-9c9a8d3/NEWS000066400000000000000000000025411522766574100165560ustar00rootroot00000000000000Summary of important user-visible changes for datatypes 1.2.7: -------------------------------------------------------------- ** The `datetime` class gains a large set of operations, as its implementation continues incrementally. Newly available: arithmetic `plus`/`minus` and the `:` range operator; the relational operators `lt`/`le`/`gt`/`ge`/`eq`/`ne` together with `isequal`/`isequaln`; ordering with `sort`/`issorted`, `sortrows`/`issortedrows`, `min`/`max`, and `unique`; the set operations `intersect`/`union`/`setdiff`/`setxor`/ `ismember`; `diff` (returning a `duration`) and `caldiff`/`between` (returning a `calendarDuration`); the interval test `isbetween`; the reductions `mean`/`median`/`mode` (returning `datetime`) and `std` (returning a `duration`); and `dateshift`. All are timezone- and DST-aware. ** Updated the bundled IANA timezone database to version 2026c (issue #36). ** Added `%!demo` example blocks for the `table`, `categorical`, `duration`, `calendarDuration`, and `string` classes, and for the `table` file-I/O helper functions. Table demos now also show the input table, not just the result. ** Modernized docstrings across the package: removed empty `@multitable` spacer columns, added missing Name/Value headers, and use `*` for multiplication inside `@math` expressions. pr0m1th3as-datatypes-9c9a8d3/doc/000077500000000000000000000000001522766574100166225ustar00rootroot00000000000000pr0m1th3as-datatypes-9c9a8d3/doc/datatypes.png000066400000000000000000001103051522766574100213260ustar00rootroot00000000000000PNG  IHDRWVzTXtRaw profile type exifxmP gD桞}`s~>ܫ; K9Fh$[m6ctwxx̖?'0\ lhO!+ ƫ R*Zg)fbNm:gHY[R낭ր:Žw!"it&) KH ^9dA2"˝p_e{0X"iCCPICC profilex}=H@_ӊ"U;8dNDEIP Vh/hҐ8 ?.κ: "%/)=B4+4hmq1];_B/cFf1'II{zY=jb@@$eioOm}+*9I$~όA/K!W  @wV~bK ǁ:wFqq+ԁOk--zm-M.w'C6eW y) k^o}>i*y#^ywW{oiYr׻hiTXtXML:com.adobe.xmp 2.0 Linux 1744600475516750 2.10.38 image/png Andreas Bertsatos 1 GIMP 2.10 2025:04:14T06:14:33+03:00 2025:04:14T06:14:33+03:00 gimp:docid:gimp:c36f85fe-7e32-4501-97e3-7d47d2499f44 saved / xmp.iid:3f8dfdc4-0e75-4b69-9016-0456f95b7341 Gimp 2.10 (Linux) 2025-04-14T06:14:35+03:00 xmp.iid:a80de4dc-0b88-42bf-96d3-794dccb8d9ca xmp.did:fef46d76-114e-4380-bbc6-ea93d6100512 1bKGDC pHYsodtIME#bӼtEXtAuthorAndreas Bertsatos"PtEXtCopyrightCC BY-SA 4.0ωޔ IDATxw\Ցӓr"HB$!6ak{?Xg{m.ػu$ E"(#PΚh4s|i$֞z~}sOz=BJ)闓Υyl UA5tTË "4?B&HXIrf>@g{#V;+Y0Z %PQC NńYNBQuLߣ"-ȉ$۴CaK?NؑB !P!dڶӚu@D%Z vr.s G, `X&GMK~}s@ռMЋ8 GU54]G4#1\\/Uk^ɶb4p9sSxG!Bpaڳ=)WN' )賌r.u&"ض8(Hqlv\T=[ٹ} kұaX(E_b[7=k3pY?XH&@Dwz;zPFJuH* qp)%0sVxBϧ_Aά6,e/–"J+?fysѼ~H)q;Ic{l.GO'k*#$&Ց!? T˔^BM㎭l[5)H͢ ;E_UF o_E&X>1l_>6xu;o.Wz] -!(89"gXi蚊 lƱ-¾89,c!MPU۶( ;9o<34Yۯ ɳrl_y76oF){Oށ(*GiJGb6c-ڲIҝ$ 9$`7W5=ضeq*HSf8(݂R.EL9.wbW~9l'E ~𱮏U.hI(8.T*>C#ϓΛX p\LJ9 #dȘy:2I,xHnx~^;/ۯ NBC oXE 4ږUs+z^Z撨Jm B?B2P;'&y!oIړyb!GWP$dxHy er7w,t^s?|g)aKRWSh=lկ]+iW~p]+)Mִ $4j*t$LL!h-±mpעtt?t+ȩ=v.l6W>扎b!+p$#ESGñ,!݋Q,&,!UP1(¯Hi4hA<[+]; $hH!gS4wsMj5_{fO+g)~̑xI$n -iyCUB&gӑAE)[qXx Lq-n-Vi-EH*uIG2G*2mKU!рjk9gDg^~ w_ZҞߪQ?!kHaLj#~V060AC#ΑA(h S Uz<>^](R kd۶ KF*kә` yQ!#$y 3ݸX`_AP|["[L,HpG#S_1dM!]%$Bqno83y A?*#TGTBU\ţiIB*NlKޯ }(f*NӁ1M4Ifr}AfJJXwTM&gb{<uTCJPUuŶmbiEPMV C2Ƕ2E*/_ws>@~C Mc'86cLCG eaZ&cjbm6iD/viy]˥eaY;UP&5]GT7)L<"R"B<' Guz>l$jq:r_)%9,Z,~TtsٺWܰuVB^xZ% ro v܁ G;v|&X}`4[tM5Hf$2y"^O1p!ضN E'IJe%)/d!}^̼$ϓ5- MGV 5W,±\qЙb&c߻cHZ9ϰ/# 'ۺEc'@MIP,ѯ SMdnt'-;v/ΆկkV&NE/__G>/~gy[qe1~xn(UUU,X۶?L.g?uuuL:˲x衇7*v/~D>v;q$^p!&LF`[Y48u2X;Ӵ Mh?:0{b  9Y 蚡hgo/KѓW_}я}#ѺA ș<𣌘4nÆOJs!~Xx1=W_}5Ǐg„ ^իW`aŊL6 q>WBlmmEJO<ʵ! Xr~oGE*XXE[_{Wĩw,twR_G*Bq'af #0 94Å^"0NJy~?sEAmmmBPUUU]D # GU52IOHaabԣz\IC0/7_L)@ܲhv93[~8^խ-)J n6 XGb8س-M^9.w)u;`^Pt>}]̑8D (4 ѣM;*7|3/K(@)v\[L}wތ}E UTi2!)$9CK"UCG++ݯw8nU7p¨\ _+#o-H_Q7d L*? A+dɚZ.h|EA/v8J*YRl`6|U(xuM(}gʗ[yzN$wD}LH"aꁗrUd~ TWy+VQ ~Lvz+骎۸|U_ANVT C]2."Hi{t%M►TF7(*JQyJYs)A+QGJ()B`Bt!G6xU.{Pc*ƢzCm)ymZ:ӥ;}br'n֢V!͒J Bov֩$i|hs@ly[)bO7.IY@#@{SMؿ"**|BBX.K,%H"E,`(GU ][J C֔dMYZ3q E%dP=U<8iLZoϑ}˲$KL:J9R4dD"9qy{xwsG_CD טߜѱ{L4ig<҄-,6Dyin朗54Rd,EH`YvTM@iZ:R48Ic{TED"VWiRI|?)i Nص$YgDF7'DiYEv(.}W9KVW1|hgHª{J]w' Pw$~s|eW,Ҋr, UmKD:HU񪮋&qM)E2W5!TKHAjÕxu QSJ%v=oqїO%isؾṟ{-?ykrv#_($XggXs)rJJ(|>4MU9e3TVˑSWw ܋Wa]dLeX"̢)U^õ>Cđtdr3yjޞ&X;eےdd:KUGeć8M*53E0jJAc۬|;9OĻe,,Zڔ9b!pC,I"Lr~߲mbD1$ RYYAE 2¨l^>}h~݈ /~owROKAޒd&diŲt $9LqWbeXI2'S hiTWETX*C`8vl۶Yae[GT:q-JP S"'}eN4>/~@*g.2*J=o%IbұZgu8BpMznyoA޽GOfƔsXzJqZW2 ]\M.nS$tsc8xux6i]hmHYv$/C+4ªB2[ ɒ/$L;]31"|l1\pP-ާI2!P͖ JYx h4 B9Tv}+ d2t:q  e󴷷J |x.#)td:oA 3l={Cm$[xI:ґ] F: 4QMUp=)|_c=nθS*dϔ7bӍ/)1t ׋{ds9>~`[6Njnxt(W<^z-]b=A CGJZ5Li̊X'#q/7|eqlMEΙ8H--1k٤sUОMД e拍U*%Hre^TErllD\LUbeK^qpi/dU L6v4eZ"hn"%pn$C(܏G7#ޔvsD.4ժ%# wi,( w+Ҹw[y>˦g(*ANg*ahT<]5Ek"eDCȑq<>-&hO*aE( >-b,tH@Qeܰ?tR#NSh:kV>?(z٪z!RJnʲe˘8q"Ç'ps9n  ʉT*SO=eY\x1@_{/UY)%˗/g]jjp]vr/SYU3`R4< #%N|h0!0^ӇаY#[w+A\cXGoJgK' )b)Zd29jj~}AA M&E2%/p=A["G<[3[H"ÑU"AWU{xk7X]/=W~촍͛M6{݂G7hmm>7tC P(wp _o|B@K[;MMM466__TUU1~x~qAy&O̯{m۶eza^p 3lH>2sL~@&W_ez'z,zdNJzbD^ϼ[Nj?Gwx8+Ēi"**#%fZW4bj6 БrE:?^]öLtUULrɌ+hi`KSdW!q疭ÉFdXd `ӦMtbUmb8p ~$B1cƠPl.G[[Ç 0 Ǝeعe˖҂ n ree%C &iigFF7Ķߏ.7CN0#{=Fw ѳ/cm{n!Udo?CM$HA**vz@ct׃k,e**>A0tld?d+q}&ewmC[ъ};?\.G"mvI6"_K&TFTWV7| 5&avlm.$=~۳4K&gvBfc>$qX~ IDATUɓ)t? <؄zpQ#/``Eo @6,Ȧ;)\Ά] R-[M0v2~,ӯCjN5 ,fƋÝۊ#6v 3\_L!V zǮ/Xq3[yTdhz]e%{YOtҿD7tn?^+GUȟ)$=/m&^BP5`8sE;ʧ f2¶سr 泥IQ;2JtkNj.ʳr?u XLО\J"*'TRzyo rv͊m3'to'bPpK-*!az4Xp? K|B "SGزYxo?Ku@וz?3fbFe[[ -ʰy#F_qڕz71kwkuKY*[x/J}9WzD?^,[T*E>#bq0 B `N]:_cNں)LjɳgN'GΆac]yNuy{ovMW7/`9G+sB(J8CtRVXA:.\E9KqB477z.+8YnFxce >A8' y=(P(N[[e-y{9% t6O?`mCWW\{9ue3{G{ߝfŋ/'Q)} `ˡּ7,/n}Bwy'Bl\^3LId22[˞={x'HR8M>c0zH~0`O$h4J8DW&Jڊe I0iUٽl<(KxCU{=>Z[u`6n`P&/-aD [FA~Ĺ]{! W-3ihV&0aMMMyɯ͗Un6n9?qLk&gl۶Ç~zLL<`Ν|﮻ټe/*~q1g^}m9{ W/\.O{k4wO̲rWeP /g_y3)&bez.-0F*wgjyqZwщ$m l֭#f+kt;k ݅A!\r .d 4l#OaѼ4 #/Ew"5gd"ٖڛqdœ gTA,U@;٥U=, qvτrVܲ,~_aH|'j&t%z]2b^(u\>O*&H^L\Fܴ|U7hc&PY[cפ~u</S<~N]nk#6<:3 ylӶ3mж[WO"`75p$󚧘\me{PH >BeeeqQF(_m t)r !uITMex^~,3Hld!Dv#Q J*S4'H3El \3zrio-I. [w/cfc+(vہ7B:Gr)ڟ`M.Nx;kܴ#VfN_lm+V1pK6Y~kkуRNP HXJcRnURn>e\G*# C(˞J6Chf]CW wDוeгO 0:J P%"Ro6@!gcʿ`w$<\PSЋOҒrޡu(.IK9*#"T@=Kw_zo剕Iҹ B|X3 Tc7iOgâ'-Ҷ Le@T5*fʞP6[A"L8RqL(\;sDulbcr$.qӲgBON𚲫u aY& u^0J-%vuˊy$O=Gr&x UAcF;x2AF{&[|\!w=X_d᧿n[F~uxGR1Hu@˛TFq, = ð ^jq9y!%l`0}AhKBmmM$ ^pEE8Iy,vI?g=- /CGw%(mvD/GlCee%=|>OGGUUUT $#sl@ Z4NsuH;ac䉓 nz 31rG`v[;/3I9vkA!Z4à ~S]Vʣtō7p bL vzA@E[b. luح=qƍ DFAulh2@oݢ=Kd2$ TU-o\ʏ/py|~ꇟŞ4pJD:M/v3E1};39|>OضM{{;HJ$T 0=|+ޅ,Ʃa|)w$`삏38Zr:Mӷ?JYs7Г+{Pg+'0-T:MDIsݜ@z Y>G''X2ϼ]V_۶<%Tu[6sUcxp,Wu6@mϧDjF1e2>?x.S|^M(I9YHaaYy7%nD#Ag8dbݣ&4<l o+8$2);5kְvZŎ{Te\D"<MXl2}bLzC]VtvR H]ב]7/]| ~o4azNhI$tvvQ,EQhnnf߾}G?bΜ9|>) 477f|>hnnfҤI̘1lZZZh5@uu5a`6---,"QUUqԸ_d%#TY{Q 9=d ΋?GitZ~Wh_}s֧q)6% x?-'Dy~Ycа:Ȏ!z(9 06ev@}h)*[ι>jB;]*++{yɓ?"hfy[U,`9{P} RQ.'q! kaxϳjhFҨ@AKA]HFԏAW fm^gW8G+,8귚iT{מu)?M*I-U"׳dB8@ @Qbi:VZE0d˖-wy'Ey e)ŒƉ޻b3`8=ס$Bp%7Ҵ}cM$_¹&M?C}$, P؂Ll=]_`n0<- s?iWO9Ol-*^Ob1r477k.TUeĈB͛ikk3gJXf g1M^xT*!N3zh<`̙:tT*ť^J.cjwxEqsHN+1FW4 u,p:pA lUHnhr?o &Cor W#Ȟ1揟]lg/^o2r_acaa$\F!+1b&LȪ!u4Aڶ~TaÆ1lذ^WRQgKuEBee%HJ;vl9ihh`РAe'Џf'J^ߎG'!:˂@q pKfz5D( ('aIlSa%7\bX9:>!b5`nՖ?f]? qǮ>fRJYX|\j Wq'ÂTUU(M׃[,!*' 4QUUՃ!|>O(]JʀSUm(|]LS 8]vMPGiloB+ݥXRҮz80`Q5CNTςtu<~LZ),\.J[Wc,1Wb΢,BEw-v(:*uEcxt0ãHw7ߣ!XUe,έ1vw0*dSڎ>>c`PQ0i\Mx ;w=7ҥKݾ}1o߾n>ϣ:fo{Mi;$Τ=jKBƇICo<c&UUP(t;S' R~M}6[ /{ό9T*q]vtRjkkַŕW^kƲ,6mDm볯]c͛Wn;.޽2X$co|G $4c!͏%U̝y1P Ȫ1Y{ 3+1%ҎGn?H>?{`Iv4^30[.\ȃ>X6o̷mpYg1zcfYjilڴ\.c3f ˖-ry<Լu)͛뮻ky7?ۻUl\yr'5Iy ۣ͘A~!sE<_}GɈ7~Ȉ8(BEEs_*6mMMM}:sgٺu #O[cY3MR;hXTeн3RJ֭[) L0||޼yu]l߾ٳgڶ̓>Ȍ31***3f +W[n9a@%8mO+~ƺ'EWgAdܼ`bMGk!gݒǰ)cxx򑏔@ ĉyGq҂ >`VR(M&߲DG1/blosataJ}}=uu]͛ٹs';w䷿mw|\ՙefFҨr-.L 5JpI`I/$#  d!,M8bMU-#i4r~f$7,' >/ޙ{gΜyNuua=7SXlٰy??:(9Zx @z~s(6lD1]wj'na >ew`[)da|[R~tk4Yj~_y~[oIm۶q7f͚ak\V[r%'pB+V`۶m2qăA94<|z)X1P)@C{^\әиx L:8+v![ 7͍DLk7崱{-?gl,kʕ+9Å^{] `՜vi\{CJÍ7C=\p%řsN֬Yo~ Xl;w&Q @$g3E+ػj/@#б70miTjGR q IDAT<"/Z=dώ D>|x)`f(kp,'q;f1=̛B{Ρ&ʊz_W׿杻+()PGhiiŰMUU^{-ommmye;VZg=b4&LM83擧Zǡ$S/(} ;45?,g7a V@Dè.HkCEq?BDͲ<&pUHsA$|4/yw4>f͚ UVV2uTN>dfΜʓ2---̛7I.iii n3f H$Bcc#˗/}}Guu5'|2.4@2YvFO4@$qwzl!Z4Ds$,'-Y @2knjECԜ?W[HK/ˊ%߻8xTYAaqbJ**f_ 2Y)X'pnv ٸZ BRF$!P%$ I1+L= k͙HbPQyqK8*> n:!"0@$Թx]c98(`sԜD XlhROO_YZ*1jL|8*8|N]?`gw?(d,bɏ$}b+b(I0)W˴ IF>B;`WG joCa˪-1 >S뮐LcHŒȖFGv9L0F:6om|ز]?VeKaٺ{}r?LkZ"6˻{3IX:FQܸ:sdQlTJ;3},s Da gUB)}0 $Hp5`D"]c=6NJ,\0W!r/yζmۆa,/knnO]t6N;o}[{w8ypv[yݻݛ;w/c{H:J@ur~Ԩ~K NG1jcmO;fO. RIu̹s֢wmCaSGw1l,qW΅az)ϟϬY:Ea|kSO`dkƽO<1BvM$ _G}?/}$[/r+)*++ԉ-[Foo/fbAWWr 'tR^ؾ5JL;86tfʢ(olv4TJgX%IB=~)E%C% fK&(&f^齆cRg^IqOkQ?0a9]---?[oo#N_*]w].ߩ˗SUUmݖ[BdYfر#< ˗/;rKڕW^<@<?`qixoolذ`0wCJ14551f̘j́ݯ*K,駟泟,tɭ)L><[5d:@JS"[Q2%O8H$رctEQ2eJNB$=\l.k:V#=ų[Y8d'̗e~֚,Akk+wqvmy(d&FK.夓N2*W\q+VଳsΡ9sp)P[[xsHR5!2bΚª\뇬ys.o]׉D"#d:fU,wNWZY+xvd4fr)̟??;wvWW:b{ÞW>}:/2/26l'o_v[Z +P0룷 &W d$7t]RU5d7sR.j\r̙3;wnH32-!f̘gŋG$zVl2.~_w}R_5T;B#n$YIRBرc SYFiFAjjjYsMv8cɟa #ڑqbɼ⋇,Ȉ,cٰl,X 7ꫯ󱽯؞Q+UQ4_\uBTWQbt0u'g&XX,FWWVMUUUζzlƟ@`O\ͺ?ym.{ÁM6qڟ|_[,)=Eku>']$t^WY|Y$UUQUAqq1xOOOaޗJF\6u 5 @To`rO(@Z,ͤ7f^?{9Nn喼nm$qmgA}}3vnfZ#JQRRә2e(_6GTt[d 5 GDصkW7ng9c`$aZ(/:CfcR@PTɐR|bDF(iݧyɻگQAoo/p&VU՜Z%rN:lرcseUU7n\.m'H͊VL7]{?dDm&) 5YKp?u-uog8T'04b?~޹Wl٘0aB8]]]VdIUEQdzAgW#;oiMHaSqZ%wo3ʳTgg'aQM➞~{zzPU5u>GW ' & tXNAJK?kH08WK _;3!ԐSk׮3weeeNڵkװ짖6Xꔕiq, eee9-k3ZT:I<طʥ۱>~{D:l8loo[l–cK{+oaI 3m6n!F6K7!FKMxill̫x0av؁b .^I_Z."PSSx^.V\I{{4aDCaE$]EKDnIgLf6BncJ)=Y A :ź&6kP/wB% 4M^RVxjf[]\;li}±TM^(OEf28!Ȣ)տ 8;eRK%iyU¼ Y;B RWWwsuYn{{;mmmCxDz#zjnF lٲEQ4h4Jmm-N3h&T ̙3Yr%'OOvnUP;a>G8[XRHs?),Í kdݸZ,TVVKUUA7[xugUI%D,:I=?iZbe۰Or؝zd.;Y|s*VY٦ٝ>$,aN`;NE`h2wE7Q,NS^QUr6jAI OLb6I6S4M~i{]`7[}(6es$ Q*DUG "`L$dI 8ND6dvȋ=r |D3+4٤شv03RrK=(&jCjQ<6:ZĀoazsp8W(Wf{e+%Iٕ5rdё;'hZs$0!_:}zCΫb>}ڷ9X=٤ q5Thm~RSmqBF>IC?=(RH4X;7FqHLmdՎW0UUBׇ9'r><X,RAoooNU+((&xCL}l#+n< Er*UZ9U; ӌ05"m4vCcwPnjpEkBHΣ6ϓ摌h{vV+9'iMMMr$}F===455JùfnIҳH3j1)]Lz__LJȸU͘b=H.`q3x"G}?ft}Ӫ`Ȗ}"BFB`WXh;)\dAϏE:zc`[Uabʉr0.**"ʨa~ۍ jX"V^TT439ɻ(U#UGɤMA}=*``$ @Sy*:֠ 7Χf/Woi bYAͭ8ߌ#d8h4 ׂӐp2= UChQu,=-ye E2$ )//֫/w(L4)փ xCOO`0'eBM@š RY<=v3@TԠ )8-"H IH5": SH' F8ߏoZ^r5lz~ZF$icF0NBGNM}/Vv˨ҡ_ɷNo,+RSSCcc#Bq8-ڬ}bX~oy%/ k<-}P9m_A($pXhtla`ˬdiݓ;yGOw6`Z&}@JT*M*8)bz|Yوq{\IRQƢQY6{Z[6Mg6!nxUr|除C.pf[9xr,4inn&mmmR<0dhFkkk.>FR޽;8Ix:ξ`\I~HZ;vK斡,˔&&!-lI 1MC7@UT 0ucـE%d7Xw]nI˅kmԸ8P 3b[˔b߿;D_-o^݈HbvcIcQ*Fl$6w($YfM[Ä)#b$I?~<`0$I;林4ncZ |>bMMM9~,xd"I@3g\O"&kS Q\Z[uZ K2{ƹj"KK8<>ٰܼq:BYc-U!OO߇[ q,Y&Y0M4s7EcLka 3?c,t^֗QHIa &H2$J@MM *< IDAT^=]F|CjЫr9RЭv;HIΞrޡ\<@R{[W*0btՊa?:[Tv[AT"k`kUU F8τO/+ó N3>.)ہo8 ;Tegڄ tSfWz%I0G!55}BO1NI&D,4MZZZp\S\\!İ iٻw/H$vjkksxmof2i{~HNJk:g9&Rn CA,aN&8vR6@x7݆sQ )o|SD[oY\zi~#,TPdG#-\4>@V;Z-j}Sp92\MiӃiyen;ߟz8q"#Omʫ-/b9s¢ qdyAۇ?{OC?q]aNk.n^"%`옪! 좪2y$Y \\< >-2n)oʑ_&R( >1uԿ&ﵼsVp ̽SvpZթL;#B!Z[[s =+5M]Y컄- 555CO4ŝiZK}hdRe7 $>{S2YI`(B+2=ѯ))X'Q8!a{?S7^D?S)<QΒ$3g)YfgdqOjSXXɵ1b躞Gh)館!`<?ET }g8hp,$iZ XP c5Fi]'Jv|3?<=t*W|O<8w^.2FJ0a {$+9; UVrFk1i!:ţT=DU$GvP15cyo&ooyi >)!F 3IS؞ns@9g ئ({`W)φ&IÝl@I)uv?hF5C>MN31r Țp' q=koZ8 qœp [Y +ޙoJldBL +g*vb#:iΗ15\ <#tM7<!Epsҳq8]7si!K;o硇fl }h~@dD^k3ډyn*~y\veDi{P2AiG)_UC(@\wej+}tYu$ەI*[]Al'w|Л&.ZhχWqy\4|Tgxhk@"m'z"9_aRU}Dp榐rlXKNdɦmy\s;Ee޼yWTĒqM7sV+`QTL$W3ov~sH$¿0Fc<*͗O/G)0X&.H) ]4i3`ذ٭8\.H?KG1>(RoMwal酣:$qM vMCfڂHIB}:}Ξ )--} 4o|p#7K ׏C6h4.,,bX 4}iz3*b:dsgH(@(VtF8 x"Aχv"O/$ .z֊V$$TEp#j"8Oj 03MAMƌʕ+ٰagy&UUz464}v8L6+ӧOgڴi|s?3;79sjeܹlظ7^D"qGQQK.UV gR[[~-r[=TMnhOyN O  щ'$ %fjN b\àX셙XDRւ4H Ɣ8Z:fQ⃹k@3t ϛtbגZW0\Gop.QU{qyS4h LjɊR?><^O^Dx =2n*+ˆjKWx5SXZ6jPͅU[{/i3 J#).0S&v syO8ŋdqi`& I""ї&:fLC-=RPQ^n݇؛O@%ېn{ łӕ76LA"$Jr0I2"aHvLa&F *p@6E܄RȿO!e#q_ "&-ҠJ=Sv,!!t8)bZ bIH!ًdK9s$ T^$ņxEجAJKK(7m A.~FrH)_GMC],z#@djZ,|#[bH*xsDir_ P0d#'a]MѪ8<"HBHM22^"v;d>t Bee#7{N04L#d$3jXR__ӁA<{b&(P@)ɯRQx' G4HxԵ;gzzǐM NF#i+L#qS'[,I Je"2<j稬@kqnsgAF>;ujV繒>Z3nq}ȂxfOWI<ɣ6)#DCrPK qE( Th4J,EK'14.,G"nJ@XRXPESȠkJ `d:/Y{Yˏ䐇⦲x S!=z!+ItyCHfa Oe̼fmG[vKJ|flثGm.DlFE*9-8o/a )*RLS"C<!NaC ,~=x>aB5@ObXԼ9t|7n,>(?4EJ⬢;hV653x*Oojѿ8AAJӈ9KɴF,4k +UZ׈&Ɋ"emԴ %[Kh 8}T@x4fuKB&6Mt WU$J i]'IKL6vv)`gz3zai HN'u18ƶ26D fgjwV`fUp HSSZ6 [?ݏL&H@n^>`xJS:@xlq3?J(,E q!p ]2]L"mح&" 71e Bx43_bCBH#ҿdE٧* > ! "^D1.xnܩxh1ϰUEUU򂊃EȰm`¹co<Σ yNU?] G& xWe k En0MtҺjad"pP-[4> od&2I;!$z1)v  EStBz /dHTWP7ϐP?Qk4!xd1p:3^P F `[kkpi6]ɂUis&LD),ȐKti$26F*'?1_fgۑd+#{] [8R?]f+_6~̿0! d l6>,q j!qu)=1GΝ[S'bAQ n=]d̊J.^ݾ#c$ǍO"wu]90+oGe+$6mmʌ9 UN$2xˬr_dO{ =kW>nUOhRGV,w̙=];QRR²e|XU;B |׃̦nB y*2 Ѕ劺C{ $"+y*T4/ x h7`g~jN(şh ~!sKcʄ#5|[ //}}AjJH's+Wg\H2e" W_}5[]` ;hOr \t[b;/>;3uq I㼋9eRM$إ9r˱#[nAnPXE݇U(*Ǔ"Lx߽ϻ;NsM]|\ s(!XL0 0ƫFǞ5kVȪc2ijj?ߍDu(%EA7 7)pXc)is^jp`v~z~JF4,3|*CH#,O6Ҵg= q+0q'e(FQweQQ^J^ۻ^s b 12rLQO@+V0mjm&JvvZnoXb{$a`N'rU_J|%9'$RSJVbE{AđT'Çdu"Y]HLOnjgl6DhO.2Du=>,>QSbV>24rKlS,Κ.?vS&Т!B$H4wbÑ(Px&7oΓl& 5'5W*N0 wV~|(T9 Xqˆ ؾC'Mc⤩ذ~t Xγ>KwO3f`μy꫼۔PQ)9p:\M6xl6l}?D"c;%+56djB},]J~alԩSzwc1c#K^}C륻y;x(2[d0mC"y6ax`6J'yZ["-7ʆ&NsrBy>6=oxK [!{В=]b]La4-,%+ZyL$s d : lC={W&Af"G wإX]]Xk(Yw1vUUǽ̝ G(С6H4hDbc41bh0$!ACRh<,` t3s_s{o?;ә>ҹ=w?kXQ+W7'#7jRF#oq&yxjj5@4SOuGCeWPYW%\c[6ޗ&B(dDMח_J) Dʌ4s]jYM+ M d$psBR C} @89}8Mx{yE+o+C's5U"β]d2v,Xu< gm/̠\7E,3Εޕ >8Dx : ,]@0{ }X={avrK?`{G0(LRPB`vcw8=%q200eY9KӐRjh6H% ju M#/r^)LR@A)۠𲽑zaѯ4M#U]4)JaO1Y S Ae4.I nq%+N L olIDATeXJdY("R6F_O)47\tóh' 0V8E !rw^YF&8VNzLK|/\MV{ ~(CX[@z@#=w~8KnQ1icH)I҄$1 (bi9݁,j155E&b7☙?Š>H\N^VkkJ1=J aشIӰ #…°e1tiDH ,P* btlڹ6o"1ǗZrZچi=C_K 6HRVtl[f,J<&%RfꁩC#]bH^glləIbtI".A* JN:6AGbp3XHNEtVĽ " }RJs.|(8 ItIdi~uðNLaBn+FA{Q B 4U<#N#^y짬I.(y_ȹe5 OT|7qKUs$U|j4"CQE!a! ig TsQb` ,>te CIJTdzJ Vi<[xFe07|{Ҍ!;+\-FO ۃl?hwY?D>i$xe(Xr ?XLL=`.)k l,QQ}0H=ĺA^fՖ{(;/_O91>8奿"J&0 bݗ>Sĉ taSK2Nʷd!P(t:\Ha費W.p H&Z{~$UTA~qנbNC,A,O9uE50ض8KPЮٴC 7F`뗩؛{b fKj6D߹KQSvuUH 0("|MۈG=oV-ˠ(o<? ݰhڻ3}cW]OexV-B 79:ͧP1f<'F*+c.f RAB>Šg)GvWޅ[p rYNsd:+&(yE8L:ɒeo EN;'Jۤ ћU{BsF ˭;Ѕכ@>%a >O7;:H8W qIM ѱ~f?S뻁eٞor%%LI6au‘4:[Ml;8.=9q!|on~1ELAikSIENDB`pr0m1th3as-datatypes-9c9a8d3/inst/000077500000000000000000000000001522766574100170325ustar00rootroot00000000000000pr0m1th3as-datatypes-9c9a8d3/inst/@cell/000077500000000000000000000000001522766574100200515ustar00rootroot00000000000000pr0m1th3as-datatypes-9c9a8d3/inst/@cell/disp.m000066400000000000000000000021031522766574100211620ustar00rootroot00000000000000## Copyright (C) 2024-2025 Andreas Bertsatos ## ## This file is part of the datatypes package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {datatypes} {} disp (@var{C}) ## ## Customized display of cell arrays. ## ## This function overloads Octave core @code{disp} with respect to cell arrays ## and displays their contents in MATLAB like manner. ## ## @end deftypefn function disp (C) __disp__ (C); endfunction pr0m1th3as-datatypes-9c9a8d3/inst/@cell/display.m000066400000000000000000000022731522766574100217000ustar00rootroot00000000000000## Copyright (C) 2024-2025 Andreas Bertsatos ## ## This file is part of the datatypes package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {datatypes} {} display (@var{C}) ## ## Customized display of cell arrays. ## ## This function overloads Octave core @code{display} with respect to cell ## arrays and displays their contents in MATLAB like manner. ## ## @end deftypefn function display (this) in_name = inputname (1); if (! isempty (in_name)) fprintf ('%s =\n', in_name); endif __disp__ (this, in_name); endfunction pr0m1th3as-datatypes-9c9a8d3/inst/@cell/private/000077500000000000000000000000001522766574100215235ustar00rootroot00000000000000pr0m1th3as-datatypes-9c9a8d3/inst/@cell/private/__disp__.m000066400000000000000000000250641522766574100234430ustar00rootroot00000000000000## Copyright (C) 2024-2025 Andreas Bertsatos ## ## This file is part of the datatypes package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . function __disp__ (C, name = 'ans') if (isempty (C)) str = strjoin (repmat ({'%d'}, 1, ndims (C)), 'x'); str = sprintf ('\n %s empty cell array\n\n', str); fprintf (str, size (C)); elseif (ismatrix (C)) fprintf (' %dx%d cell array\n\n', size (C)); dispcellmatrix (C); else str = strjoin (repmat ({'%d'}, 1, ndims (C)), 'x'); str = sprintf ('\n %s cell array\n\n', str); fprintf (str, size (C)); ## Handle each page separately sz = size (C); high_sz = sz(3:end); high_ixs = {}; for i = 1:numel (high_sz) high_ixs{i} = [1:high_sz(i)]'; endfor page_ixs = combvec (high_ixs); for ix = 1:size (page_ixs, 1) p_ix = page_ixs(ix,:); pagestr = sprintf (strjoin (repmat ({'%d'}, 1, numel (p_ix)), ':'), p_ix); fprintf ('%s(:,:,%s) = \n\n', name, pagestr); page_C = C(:,:,p_ix); dispcellmatrix (page_C); endfor endif endfunction function dispcellmatrix (C) sz = terminal_size (); cols = sz(2) - 4; colgap = " "; dispstr = {}; optLens = []; for iCol = 1:size (C, 2) [outstr, optLen] = mixedcell2str (C(:, iCol), cols); dispstr = [dispstr, outstr]; optLens = [optLens, optLen]; endfor if (sum (optLens + 6) <= cols) # all columns fit in terminal size rowSpat = ""; for iCol = 1:size (C, 2) rowSpat = [rowSpat, sprintf("%%-%ds", optLens(iCol)), colgap]; endfor for iRow = 1:size (C, 1) strrow = sprintf (rowSpat, dispstr{iRow,:}); fprintf (" %s\n", strrow); endfor fprintf ("\n"); else # we need to split rows optLen_cs = cumsum (optLens + 6); startCol = 1; while (! isempty (find (optLen_cs > cols))) stopCol = find (optLen_cs > cols, 1) - 1; ## Just in case a single column exceeds terminal size if (stopCol == 0) stopCol = 1; endif rowSpat = ""; for iCol = 1:stopCol rowSpat = [rowSpat, sprintf("%%-%ds", optLens(iCol)), colgap]; endfor optLens(1:iCol) = []; optLen_cs = cumsum (optLens + 6); stopCol = stopCol + startCol - 1; fprintf ("Columns %d through %d:\n\n", startCol, stopCol); for iRow = 1:size (C, 1) strrow = sprintf (rowSpat, dispstr{iRow,[startCol:stopCol]}); fprintf (" %s\n", strrow); endfor fprintf ("\n"); startCol = stopCol + 1; endwhile if (! isempty (optLens)) for iCol = 1:length (optLens) rowSpat = [rowSpat, sprintf("%%-%ds", optLens(iCol)), colgap]; endfor stopCol = startCol + iCol - 1; if (startCol == stopCol) fprintf ("Column %d:\n\n", startCol); else fprintf ("Columns %d through %d:\n\n", startCol, stopCol); endif for iRow = 1:size (C, 1) strrow = sprintf (rowSpat, dispstr{iRow,[startCol:stopCol]}); fprintf (" %s\n", strrow); endfor fprintf ("\n"); endif endif endfunction ## Special function to convert a mixed cell array to cellstr array ## that keeps MATLAB like formatting for each type of element function [dispstr, optLen] = mixedcell2str (data, cols) dispstr = cell (size (data)); ## Preallocate indexes to avoid truncation when last elements are 0 is_char = logical (zeros (size (data))); is_bool = is_char; is_numeric = is_char; is_object = is_char; is_struct = is_char; has_method = is_char; no_method = is_char; ## Nested cells are printed by size is_cell = cellfun ('iscell', data); sf = @(x) sprintf (strcat ([strjoin(repmat ({'%d'}, 1, ndims (x)), 'x'), ... ' cell']), size (x)); dispstr(is_cell) = cellfun (sf, data(is_cell), "UniformOutput", false); ## Empty cells are printed as doubles is_empty = cellfun (@isempty, data); sf = @(x) sprintf (strcat (repmat ("%dx", 1, ndims (x))(1:end-1), " %s"), ... size (x), class (x)); dispstr(is_empty) = cellfun (sf, data(is_empty), "UniformOutput", false); ## Index remaining scalar and row vector elements ve = cell2mat (cellfun (@(x) isrow (x), data, "UniformOutput", false)) == 1; ve = ve & ! (is_cell | is_empty); ## Index everything else me = cell2mat (cellfun (@(x) isrow (x), data, "UniformOutput", false)) != 1; me = me & ! (is_cell | is_empty); ## Catch 'char' scalars or row vectors is_char(ve) = cellfun ('ischar', data(ve)); sf = @(x) sprintf ("'%s'", x); dispstr(is_char) = cellfun (sf, data(is_char), "UniformOutput", false); ## Catch 'logical' scalars or row vectors is_bool(ve) = cellfun ('islogical', data(ve)); sf = @(x) sprintf ("%s", strtrim (sprintf ("%d ", x))); dispstr(is_bool) = cellfun (sf, data(is_bool), "UniformOutput", false); ## Catch 'numeric' scalars or row vectors is_numeric(ve) = cellfun ('isnumeric', data(ve)); sf = @(x) sprintf ("%s", strtrim (sprintf ("%g ", x))); dispstr(is_numeric) = cellfun (sf, data(is_numeric), "UniformOutput", false); ## Catch 'object' scalars or row vectors is_object(ve) = cellfun ('isobject', data(ve)); ## Handle objects with dispstring method available f = @(x) ismethod (x, 'dispstrings'); has_method(is_object) = cellfun (f, data(is_object)); sf = @(x) sprintf ("%s", strjoin (dispstrings (x), ' ')); dispstr(has_method) = cellfun (sf, data(has_method), "UniformOutput", false); ## Handle objects without no_method(is_object) = ! cellfun (f, data(is_object)); sf = @(x) sprintf (strcat ([strjoin(repmat ({'%d'}, 1, ndims (x)), 'x'), ... ' %s']), size (x), class (x)); dispstr(no_method) = cellfun (sf, data(no_method), "UniformOutput", false); ## Catch scalar elements or row vectors of 'struct' type is_struct(ve) = cellfun ('isstruct', data(ve)); sf = @(x) sprintf (strcat ([strjoin(repmat ({'%d'}, 1, ndims (x)), 'x'), ... ' struct']), size (x)); dispstr(is_struct) = cellfun (sf, data(is_struct), "UniformOutput", false); ## Catch remaining elements containing matrices or arrays of any type sf = @(x) sprintf (strcat ([strjoin(repmat ({'%d'}, 1, ndims (x)), 'x'), ... ' %s']), size (x), class (x)); dispstr(me) = cellfun (sf, data(me), "UniformOutput", false); ## Index numerical and logical values to right alignment pad_B = is_numeric | is_bool; # pad before: sprintf("{%%+%ds}" ## Index array types for bracketing brackets = pad_B | has_method; ## Get optimal length if (all (brackets)) optLen = max (cellfun (@length, dispstr(brackets))) + 2; elseif (any (brackets)) optLen1 = max (cellfun (@length, dispstr(brackets))) + 2; optLen2 = max (cellfun (@length, dispstr(! brackets))); optLen = max (optLen1, optLen2); else optLen = max (cellfun (@length, dispstr(! brackets))); endif ## Make sure a single column does not exceed terminal size if (optLen > cols) for i = 1:sum (is_char) hm_idx = find (is_char, i); sf = @(x) sprintf ("'%s ... '", x(1:cols-10)); dispstr(hm_idx) = cellfun (sf, data(hm_idx), "UniformOutput", false); endfor for i = 1:sum (is_bool) hm_idx = find (is_bool)(i); do_idx = find (cumsum (cellfun ('length', strsplit (dispstr{hm_idx})) ... + 1) > cols - 10, 1) - 1; if (! isempty (do_idx)) sf = @(x) sprintf ("%s ... ", strtrim (sprintf ("%d ", x(1:do_idx)))); dispstr(hm_idx) = cellfun (sf, data(hm_idx), "UniformOutput", false); endif endfor for i = 1:sum (is_numeric) hm_idx = find (is_numeric)(i); do_idx = find (cumsum (cellfun ('length', strsplit (dispstr{hm_idx})) ... + 1) > cols - 8, 1) - 1; if (! isempty (do_idx)) sf = @(x) sprintf ("%s ... ", strtrim (sprintf ("%g ", x(1:do_idx)))); dispstr(hm_idx) = cellfun (sf, data(hm_idx), "UniformOutput", false); endif endfor for i = 1:sum (has_method) hm_idx = find (has_method)(i); do_idx = find (cumsum (cellfun ('length', strsplit (dispstr{hm_idx})) ... + 4) > cols - 6, 1) - 1; if (! isempty (do_idx)) sf = @(x) sprintf ("%s ... ", ... strjoin (dispstrings (x(1:do_idx)), ' ')); dispstr(hm_idx) = cellfun (sf, data(hm_idx), "UniformOutput", false); endif endfor ## Recalculate optimal length if (all (brackets)) optLen = max (cellfun (@length, dispstr(brackets))) + 2; elseif (any (brackets)) optLen1 = max (cellfun (@length, dispstr(brackets))) + 2; optLen2 = max (cellfun (@length, dispstr(! brackets))); optLen = max (optLen1, optLen2); else optLen = max (cellfun (@length, dispstr(! brackets))); endif endif ## Pad data according to optimal length ## numeric and logical is right aligned, everything else is left aligned Ra_wB = sprintf ("{[%%+%ds]}", optLen - 2); fcn = @(x) sprintf (Ra_wB, x); idx = pad_B & brackets; dispstr(idx) = cellfun (fcn, dispstr(idx), "UniformOutput", false); La_wB = sprintf ("{[%%-%ds]}", optLen - 2); fcn = @(x) sprintf (La_wB, x); idx = ! pad_B & brackets; dispstr(idx) = cellfun (fcn, dispstr(idx), "UniformOutput", false); La_nB = sprintf ("{%%-%ds}", optLen); fcn = @(x) sprintf (La_nB, x); idx = (! pad_B & ! brackets) | me; dispstr(idx) = cellfun (fcn, dispstr(idx), "UniformOutput", false); endfunction function out = combvec (vecs) switch (numel (vecs)) case 1 out = vecs{1}(:); case 2 a = vecs{1}(:); b = vecs{2}(:); out = repmat (a, numel (b), 2); i_comb = 1; for i_a = 1:numel (a) for i_b = 1:numel (b) out(i_comb,:) = [a(i_a), b(i_b)]; i_comb = i_comb + 1; endfor endfor otherwise out = []; a = vecs{1}(:); rest = vecs(2:end); rest_combs = combvec (rest); for i = 1:numel (a) out = [out; [repmat(a(i), [size(rest_combs,1), 1]), rest_combs]]; endfor endswitch endfunction pr0m1th3as-datatypes-9c9a8d3/inst/NaT.m000066400000000000000000000106331522766574100176750ustar00rootroot00000000000000## Copyright (C) 2024-2026 Andreas Bertsatos ## ## This file is part of the datatypes package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {datatypes} {@var{T} =} NaT (@var{n}) ## @deftypefnx {datatypes} {@var{T} =} NaT (@var{sz1}, @dots{}, @var{szN}) ## @deftypefnx {datatypes} {@var{T} =} NaT (@var{sz}) ## @deftypefnx {datatypes} {@var{T} =} NaT (@dots{}, @qcode{'Format'}, @var{fmt}) ## @deftypefnx {datatypes} {@var{T} =} NaT (@dots{}, @qcode{'TimeZone'}, @var{tz}) ## ## “Not-a-Time”. Creates missing-valued datetime arrays. ## ## @code{@var{T} = NaT (@var{n})} creates an @math{N*N} datetime matrix with all ## values being Not-a-Time (@qcode{NaT}). When called with no size input ## values, it returns a @qcode{NaT} datetime scalar. ## ## @code{@var{T} = NaT (@var{sz1}, @dots{}, @var{szN})} returns a datetime array ## with @qcode{NaT} values sized according to the input arguments @var{sz1}, ## @dots{}, @var{szN}. Alternatively, individual input size arguments can be ## merged into a single size vector @var{sz}, as in the following syntax ## @code{@var{T} = NaT (@var{sz})}. ## ## @code{@var{T} = NaT (@dots{}, @qcode{'Format'}, @var{fmt})} returns a ## datetime array of @qcode{NaT} values with the specified display format. ## ## @code{@var{T} = NaT (@dots{}, @qcode{'TimeZone'}, @var{tz})} returns a ## datetime array of @qcode{NaT} values in the time zone specified by @var{tz}. ## ## @qcode{NaT} is the @qcode{datetime} equivalent of @qcode{NaN}. It represents ## a missing or invalid value. @qcode{NaT} values never compare equal to, ## greater than, or less than any value, including other @qcode{NaT}s. Doing ## arithmetic with a @qcode{NaT} and any other value results in a @qcode{NaT}. ## ## @seealso{datetime} ## @end deftypefn function T = NaT (varargin) ## Parse optional Name-Value paired arguments optNames = {'Format', 'TimeZone'}; dfValues = {'default', ''}; [Format, TimeZone, args] = parsePairedArguments (optNames, dfValues, ... varargin(:)); ## Check optional Name-Value paired arguments if (! ((ischar (Format) && isvector (Format)) || (isa (Format, "string") && isscalar (Format)))) error (["NaT: 'Format' must be either a character vector or", ... " a string scalar."]); endif if (! ((ischar (TimeZone) && (isvector (TimeZone) || isempty (TimeZone))) || (isa (TimeZone, "string") && isscalar (TimeZone)))) error (["NaT: 'TimeZone' must be either a character vector or", ... " a string scalar."]); endif ## Parse and check SIZE arguments if (nargin == 0) sz = 1; elseif (nargin == 1) if (isscalar (args{1}) && args{1} >= 0 && args{1} == fix (args{1})) sz = [args{1}, args{1}]; elseif (isrow (args{1}) && all (args{1} >= 0) ... && all (args{1} == fix (args{1}))) sz = args{1}; else error (strcat (["NaT: N must be a scalar or a row vector"], ... [" of non-negative integers."])); endif elseif (nargin > 1) posint = cellfun (@(x) (! isscalar (x) || x < 0 || x != fix (x)), args); if (any (posint)) error ("NaT: dimensions must be non-negative integers."); endif sz = [args{:}]; endif ## Construct datetime object with static method T = datetime (nan (sz), 'ConvertFrom', 'datenum', 'Format', Format, ... 'TimeZone', TimeZone); endfunction %!assert_equal (isscalar (NaT), true); %!assert_equal (isnat (NaT), true); %!assert_equal (size (NaT (3)), [3, 3]); %!assert_equal (size (NaT (2, 3, 4)), [2, 3, 4]); %!error ... %! NaT (1, 'Format', 2); %!error ... %! NaT (1, 'TimeZone', 2); pr0m1th3as-datatypes-9c9a8d3/inst/PKG_ADD000066400000000000000000000005071522766574100200500ustar00rootroot00000000000000addpath (fullfile (fileparts (canonicalize_file_name (mfilename ("fullpath"))), "demos")); addpath (fullfile (fileparts (canonicalize_file_name (mfilename ("fullpath"))), "tests")); setenv ("TZDATA", fullfile (fileparts (canonicalize_file_name (mfilename ("fullpath"))), "tzdata")); warning ("off", "Octave:data-file-in-path") pr0m1th3as-datatypes-9c9a8d3/inst/PKG_DEL000066400000000000000000000002751522766574100200660ustar00rootroot00000000000000rmpath (fullfile (fileparts (canonicalize_file_name (mfilename ("fullpath"))), "demos")); rmpath (fullfile (fileparts (canonicalize_file_name (mfilename ("fullpath"))), "tests")); clear -c pr0m1th3as-datatypes-9c9a8d3/inst/array2table.m000066400000000000000000000146011522766574100214220ustar00rootroot00000000000000## Copyright (C) 2024-2026 Andreas Bertsatos ## ## This file is part of the datatypes package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {datatypes} {@var{tbl} =} array2table (@var{A}) ## @deftypefnx {datatypes} {@var{tbl} =} array2table (@var{A}, @var{Name}, @var{Value}) ## ## Convert an array to a table. ## ## @code{@var{tbl} = array2table (@var{A})} converts the 2-D matrix @var{A} to ## the table @var{tbl}, where each column of @var{A} becomes a variable in ## @var{tbl}. ## ## @var{A} can be any type of array supported by @code{table}, including a cell ## array, as long they are constraint to 2 dimensions. However, in the case of ## a cell array @code{array2table} does not extract the contents of its cells, ## resulting to a table with each variable being a column of cells. Use ## @code{cell2table} if you want to create a table from the contents of the ## cells in @var{A}. ## ## @code{@var{tbl} = array2table (@var{A}, @var{Name}, @var{Value})} specifies ## optional parameters for creating the table @var{tbl} with the following ## Name-Value paired arguments. ## ## @multitable @columnfractions 0.23 0.75 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'VariableNames'} @tab A cell array of character vectors or ## a string array defining the variable names of @var{tbl}. The names must be ## valid variable names and unique. ## ## @item @qcode{'RowNames'} @tab A cell array of character vectors or ## a string array defining the row names of @var{tbl}. The names must be unique ## but not necessarily valid variable names. ## ## @item @qcode{'DimensionNames'} @tab A cell array of character vectors or ## a string array defining the dimension names of @var{tbl}. The names must be ## unique and not in conflict with variable names. By default, dimension names ## are @qcode{'Row', 'Variables'}. ## @end multitable ## ## @seealso{cell2table, struct2table, table} ## @end deftypefn function tbl = array2table (A, varargin) ## Check input is a matrix if (ndims (A) > 2) error ("array2table: input array must be a 2-D array."); endif ## Parse optional Name-Value paired arguments optNames = {'VariableNames', 'RowNames', 'DimensionNames'}; dfValues = {{}, {}, {}}; [varNames, rowNames, dimNames, args] = parsePairedArguments ... (optNames, dfValues, varargin); ## Split columns into separate input data arguments for table varN = size (A, 2); varValues = cell (1, varN); for ix = 1:varN varValues{ix} = A(:,ix); endfor ## Handle variable names if (! isempty (varNames)) if (numel (varNames) != varN) error (strcat ("array2table: 'VariableNames' must match the", ... " columns in input array.")); endif else varName = inputname (1); if (isempty (varName)) varName = 'Var'; endif varNames = cell (1, varN); for ix = 1:varN varNames{ix} = sprintf ('%s%d', varName, ix); endfor endif optArgs = {'VariableNames', varNames}; ## Handle remaining paired arguments if (! isempty (rowNames)) if (numel (rowNames) != size (A, 1)) error ("array2table: 'RowNames' must match the rows in input array."); endif optArgs = [optArgs {'RowNames', rowNames}]; endif if (! isempty (dimNames)) if (numel (dimNames) != 2) error ("array2table: 'DimensionNames' must be a two-element vector."); endif optArgs = [optArgs {'DimensionNames', dimNames}]; endif ## Construct table tbl = table (varValues{:}, optArgs{:}); endfunction %!demo %! ## `array2table` wraps the columns of a matrix as table variables. Without %! ## names the variables are auto-labelled `Var1`, `Var2`, ... %! %! A = [38, 71, 176; 43, 69, 163; 40, 67, 133]; %! array2table (A) %!demo %! ## Supply `'VariableNames'` (and optionally `'RowNames'`) to label the result %! ## as you convert — the columns of the matrix keep their order. %! %! A = [38, 71; 43, 69; 40, 67]; %! array2table (A, 'VariableNames', {'Age', 'Height'}, ... %! 'RowNames', {'Li', 'Diaz', 'Brown'}) %!test %! A = [1, 2; 3, 4]; %! tbl = array2table (A); %! assert_equal (tbl.A1, [1; 3]); %! assert_equal (tbl.A2, [2; 4]); %! assert_equal (size (A), size (tbl)); %!test %! tbl = array2table ([1, 2; 3, 4]); %! assert_equal (tbl.Var1, [1; 3]); %! assert_equal (tbl.Var2, [2; 4]); %! assert_equal (size (tbl), [2, 2]); %!test %! tbl = array2table ([1, 2; 3, 4], 'VariableNames', {'A', 'B'}); %! assert_equal (tbl.A, [1; 3]); %! assert_equal (tbl.B, [2; 4]); %! assert_equal (isa (tbl.A, "double"), true); %!test %! tbl = array2table ([1, 2; 3, 4], "RowNames", {'A', 'B'}); %! assert_equal (tbl.Var1, [1; 3]); %! assert_equal (tbl.Var2, [2; 4]); %! assert_equal (tbl.Properties.RowNames, {'A'; 'B'}); %! assert_equal (class (tbl('A', :)), 'table'); %! assert_equal (tbl{'A', :}, [1, 2]); %!test %! tbl = array2table ([1, 2; 3, 4], string ('DimensionNames'), {'A', 'B'}); %! assert_equal (tbl.Var1, [1; 3]); %! assert_equal (tbl.Var2, [2; 4]); %! assert_equal (tbl.A, {}); %! assert_equal (tbl.B, [1, 2; 3, 4]); %!test %! tbl = array2table ([1, 2; 3, 4], "RowNames", {'A', 'B'}, ... %! "DimensionNames", {'A', 'B'}); %! assert_equal (tbl.Var1, [1; 3]); %! assert_equal (tbl.Var2, [2; 4]); %! assert_equal (tbl.A, {'A'; 'B'}); %! assert_equal (tbl.B, [1, 2; 3, 4]); %!error ... %! array2table (ones (3, 3, 3)); %!error ... %! array2table ([1; 2; 3], 'VariableNames', {'A', 'B'}); %!error ... %! array2table ([1; 2; 3], 'RowNames', {'A', 'B'}); %!error ... %! array2table ([1; 2; 3], 'DimensionNames', {'A', 'B', 'C'}); pr0m1th3as-datatypes-9c9a8d3/inst/caldays.m000066400000000000000000000052431522766574100206340ustar00rootroot00000000000000## Copyright (C) 2024-2026 Andreas Bertsatos ## ## This file is part of the datatypes package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {datatypes} {@var{calD} =} caldays (@var{X}) ## ## Calendar duration in days. ## ## @code{@var{calD} = caldays (@var{X})} returns a @qcode{calendarDuration} ## array representing calendar days equivalent to the values in @var{X}, which ## must be a numeric array of integer values. ## ## @code{caldays} is also available as a method for @qcode{calendarDuration} ## arrays, in which case it performs the opposite conversion. ## ## @seealso{calendarDuration, calyears, calquarters, calmonths, calweeks, ## calendarDuration.caldays} ## @end deftypefn function out = caldays (x) ## Check input if (nargin == 0) x = 1; elseif (! isnumeric (x)) error ("caldays: input array must be numeric."); elseif (! isreal (x)) error ("caldays: input array must be real."); endif xx = x; xx(isnan (x)) = 0; if (any (fix (xx(:)) != xx(:))) error ("caldays: input array must contain only integer values."); endif out = calendarDuration (0, 0, double (x)); endfunction %!demo %! ## `caldays` builds a calendar duration of whole days. It is one of the %! ## component builders (`calyears`, `calquarters`, `calmonths`, `calweeks`, %! ## `caldays`) that you combine by addition to assemble a full span. %! %! caldays (10) %! %! ## A numeric array builds one duration per element. %! caldays ([7, 14, 21]) %!test %! X = magic (3); %! D = caldays (X); %! assert_equal (size (D), size (X)); %!test %! D = caldays ([1, 2, 3]); %! assert_equal (caldays (D), [1, 2, 3]); %!test %! D = caldays ([1, 2, NaN, 4]); %! assert_equal (caldays (D), [1, 2, NaN, 4]); %!test %! D = caldays (int16 (1)); %! assert_equal (caldays (D), 1); %!test %! D = caldays (); %! assert_equal (caldays (D), 1); %!error caldays ("asd"); %!error caldays (1+i); %!error ... %! caldays (1.2); pr0m1th3as-datatypes-9c9a8d3/inst/calendarDuration.m000066400000000000000000002446401522766574100225010ustar00rootroot00000000000000## Copyright (C) 2024-2026 Andreas Bertsatos ## ## This file is part of the datatypes package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . classdef calendarDuration ## -*- texinfo -*- ## @deftp {datatypes} calendarDuration ## ## Array representing durations of time using flexible-length calendar ## date/time units. ## ## Each @code{calendarDuration} element stores internally the number of whole ## months, the number of whole days, and a @code{duration} object representing ## hours, minutes, and seconds. It can be used to simplify calculations on ## @code{datetime} arrays involving calendar units. ## ## @code{calendarDuration} arrays can be created through their constructor by ## combining numeric arrays representing individual calendar duration units or ## through the functions @code{caldays}, @code{calweeks}, @code{calmonths}, ## @code{calquarters}, and @code{calyears}, which create calendar durations in ## terms of a single calendar duration unit. These functions are also ## available as methods of @code{calendarDuration} arrays to extract ## individual calendar duration units as numeric arrays. ## ## @seealso{datetime, duration} ## @end deftp properties ## -*- texinfo -*- ## @deftp {duration} {property} Format ## ## Display format ## ## Display format, specified as a character vector or string scalar. If ## specified as a string scalar, it is converted and stored internally as ## a character vector. ## ## @end deftp Format = 'ymdt' endproperties properties (SetAccess = private, Hidden) ## Whole calendar months Months = 0 ## Whole calendar days Days = 0 ## Time as duration Time = duration endproperties methods (Hidden) ## Custom display function display (this) in_name = inputname (1); if (! isempty (in_name)) fprintf ('%s =\n', in_name); endif __disp__ (this, 'calendarDuration', in_name); endfunction ## Custom display function disp (this) __disp__ (this, 'calendarDuration'); endfunction endmethods ################################################################################ ## ** Create and convert 'calendarDuration' type ** ## ################################################################################ ## Available Methods ## ## ## ## 'duration' 'dispstrings' 'cellstr' 'char' ## ## 'datevec' 'time' 'split' 'caldays' ## ## 'calweeks' 'calmonths' 'calquarters' 'calyears' ## ## ## ################################################################################ methods (Access = public) ## -*- texinfo -*- ## @deftypefn {calendarDuration} {@var{calD} =} calendarDuration (@var{X}) ## @deftypefnx {calendarDuration} {@var{calD} =} calendarDuration (@var{Y}, @var{MO}, @var{D}) ## @deftypefnx {calendarDuration} {@var{calD} =} calendarDuration (@var{Y}, @var{MO}, @var{D}, @var{H}, @var{MI}, @var{S}) ## @deftypefnx {calendarDuration} {@var{calD} =} calendarDuration (@var{Y}, @var{MO}, @var{D}, @var{T}) ## @deftypefnx {calendarDuration} {@var{calD} =} calendarDuration (@dots{}, @qcode{'Format'}, @var{FMT}) ## ## Create a new array of calendar durations. ## ## @code{@var{calD} = calendarDuration (@var{X})} returns an array of ## calendar durations from numeric matrix @var{X}, which must have either ## three or six columns, representing years, months, days, hours, minutes, ## and seconds, accordingly. All but seconds must be represented as whole ## duration units by integer values. ## ## @code{@var{calD} = calendarDuration (@var{Y}, @var{MO}, @var{D})} returns ## an array of calendar durations from numeric arrays @var{Y}, @var{MO}, and ## @var{D}, which correspond to years, months, and days, respectively. The ## size of @var{calD} is the common size of the numeric input arguments, ## which must be of the same size or scalars. A scalar input functions as a ## constant array of the same size as the other inputs. ## ## @code{@var{calD} = calendarDuration (@var{Y}, @var{MO}, @var{D}, @var{H}, ## @var{MI}, @var{S})} returns an array of calendar durations from numeric ## arrays @var{Y}, @var{MO}, @var{D}, @var{H}, @var{MI}, and @var{S}, which ## correspond to years, months, days, hours, minutes, and seconds, ## respectively. The size of @var{calD} is the common size of the numeric ## input arguments, which must be of the same size or scalars. A scalar ## input functions as a constant array of the same size as the other inputs. ## ## @code{@var{calD} = calendarDuration (@var{Y}, @var{MO}, @var{D}, ## @var{T})} returns an array of calendar durations from numeric arrays ## @var{Y}, @var{MO}, and @var{D}, which correspond to years, months, and ## days, as well as a time duration array @var{T}. The size of @var{calD} ## is the common size of the data input arguments, which must be of the same ## size or scalars. A scalar input functions as a constant array of the ## same size as the other inputs. ## ## Numeric input arrays @var{Y}, @var{MO}, @var{D}, @var{H}, and @var{MI} ## must contain integer values corresponding to whole calendar units. ## @var{S} can also be contain fractions of seconds. ## ## @code{@var{calD} = calendarDuration (@dots{}, @qcode{'Format'}, ## @var{FMT})} specifies the format in which @var{calD} is displayed. ## @var{FMT} must be a character vector containing the following letters. ## ## @itemize ## @item @qcode{'y'} years ## @item @qcode{'q'} quarters of a year ## @item @qcode{'m'} months ## @item @qcode{'w'} weeks ## @item @qcode{'d'} days ## @item @qcode{'t'} time duration ## @end itemize ## ## Each character must be specified only once in the same order as they ## appear in the above list. @qcode{'m'}, @qcode{'d'}, and @qcode{'t'} ## characters must always be included in the format specification. Any ## characters besides these listed above are ignored. ## ## @code{@var{calD} = calendarDuration ()} returns a scalar array of ## calendar durations with a value of zero days. To create an empty ## calendarDuration array, use @code{calendarDuration ([], [], [])}. ## ## @seealso{calyears, calquarters, calmonths, calweeks, caldays, ## calendarDuration, iscalendarduration, datetime, duration} ## @end deftypefn function this = calendarDuration (varargin) ## Return a scalar calendarDuration object if (nargin == 0) return endif ## Parse optional Name-Value paired arguments optNames = {'Format'}; dfValues = {[]}; [Format, args] = parsePairedArguments (optNames, dfValues, varargin(:)); ## Check optional 'Format' argument if (! isempty (Format)) errmsg = checkFormatString (Format); if (! isempty (errmsg)) error ("calendarDuration: 'Format' %s", errmsg); endif this.Format = Format; endif ## Parse inputs switch (numel (args)) ## this = calendarDuration () case 0 return ## this = calendarDuration (X) case 1 X = args{1}; if (! (isnumeric (X) && ismatrix (X))) error ("calendarDuration: X must be a numeric matrix."); endif if (! isreal (X)) error ("calendarDuration: X must be real."); endif if (size (X, 2) == 3) tmp = X(:); tmp(isnan (tmp)) = 0; if (any (fix (tmp) != tmp)) error (strcat ("calendarDuration: years, months,", ... " and days must be integer values.")); endif Y = X(:,1); M = X(:,2); D = X(:,3); T = duration (zeros (size (X))); elseif (size (X, 2) == 6) tmp = X(:,[1:5]); tmp(isnan (tmp)) = 0; if (any (fix (tmp) != tmp, 'all')) error (strcat ("calendarDuration: years, months, days,", ... " hours, and minutes must be integer values.")); endif Y = X(:,1); M = X(:,2); D = X(:,3); T = duration (X(:,[4:6])); else error ("calendarDuration: X must have either 3 or 6 columns."); endif ## this = calendarDuration (Y, M, D) case 3 [Y, M, D] = args{:}; if (! (isnumeric (Y) && isnumeric (M) && isnumeric (D))) error ("calendarDuration: Y, MO, and D must be a numeric arrays."); endif if (! (isreal (Y) && isreal (M) && isreal (D))) error ("calendarDuration: Y, MO, and D must be real."); endif ## Expand as necessary if (! isscalar (Y) || ! isscalar (M) || ! isscalar (D)) [err, Y, M, D] = common_size (Y, M, D); if (err > 0) error (strcat ("calendarDuration: Y, MO, and D must", ... " be of common size or scalars.")); endif endif tmp = [Y(:), M(:), D(:)]; tmp(isnan (tmp)) = 0; if (any (fix (tmp) != tmp, 'all')) error (strcat ("calendarDuration: years, months,", ... " and days must be integer values.")); endif T = repmat (duration (0, 0, 0), size (Y)); ## this = calendarDuration (Y, M, D, T) case 4 [Y, M, D, T] = args{:}; if (! (isnumeric (Y) && isnumeric (M) && isnumeric (D))) error ("calendarDuration: Y, MO, and D must be a numeric arrays."); endif if (! (isreal (Y) && isreal (M) && isreal (D))) error ("calendarDuration: Y, MO, and D must be real."); endif if (! isa (T, "duration")) error ("calendarDuration: T must be a duration array."); endif ## Expand as necessary t = ones (size (T)); if (! isscalar (Y) || ! isscalar (M) || ! isscalar (D) || ... ! isscalar (t)) [err, Y, M, D, t] = common_size (Y, M, D, t); if (err > 0) error (strcat ("calendarDuration: Y, MO, D, and T", ... " must be of common size or scalars.")); endif if (! isequal (size (T), size (t))) T = repmat (T, size (t)); endif endif tmp = [Y(:), M(:), D(:)]; tmp(isnan (tmp)) = 0; if (any (fix (tmp) != tmp, 'all')) error (strcat ("calendarDuration: years, months,", ... " and days must be integer values.")); endif ## this = calendarDuration (Y, M, D, H, MI, S) case 6 [Y, M, D, H, MI, S] = args{:}; if (! (isnumeric (Y) && isnumeric (M) && isnumeric (D) && isnumeric (H) && isnumeric (MI) && isnumeric (S))) error (strcat ("calendarDuration: Y, MO, D, H, MI,", ... " and S must be numeric arrays.")); endif if (! (isreal (Y) && isreal (M) && isreal (D) && isreal (H) && isreal (MI) && isreal (S))) error ("calendarDuration: numeric input data must be real."); endif ## Expand as necessary if (! isscalar (Y) || ! isscalar (M) || ! isscalar (D) || ! isscalar (H) || ! isscalar (MI) || ! isscalar (S)) [err, Y, M, D, H, MI, S] = common_size (Y, M, D, H, MI, S); if (err > 0) error (strcat ("calendarDuration: Y, MO, D, H, MI, and", ... " S must be of common size or scalars.")); endif endif tmp = [Y(:), M(:), D(:), H(:), MI(:)]; tmp(isnan (tmp)) = 0; if (any (fix (tmp) != tmp, 'all')) error (strcat ("calendarDuration: years, months, days,", ... " hours, and minutes must be integer values.")); endif T = duration (H, MI, S); otherwise error ("calendarDuration: invalid number of input arguments."); endswitch ## Construction this.Months = double (Y) * 12 + double (M); this.Days = double (D); this.Time = T; ## Broadcast NaNs this = broadcastProperties (this); endfunction ## -*- texinfo -*- ## @deftypefn {calendarDuration} {@var{cstr} =} dispstrings (@var{calD}) ## ## Get display formatted strings for each element of a calendarDuration ## array. ## ## @code{@var{cstr} = dispstrings (@var{calD})} returns a cellstr array of ## character vectors, @var{cstr}, which has the same size as the input ## calendarDuration array, @var{calD}. The returned text representations ## in @var{cstr} are formatted according to the @qcode{'Format'} property ## of the input array @var{calD}. ## ## @end deftypefn function cstr = dispstrings (this) ## Process all elements sz = size (this); cstr = cell (sz); for i = 1:prod (sz) calDur = subset (this, i); if (isnan (calDur.Months)) cstr{i} = 'NaN'; elseif (isinf (calDur.Months)) cstr{i} = num2str (calDur.Months); else els = {}; ## Check Format contains 'y' to split between years and months if (! isempty (strfind (calDur.Format, 'y'))) years = fix (calDur.Months / 12); months = rem (calDur.Months, 12); if (years != 0) els{end+1} = sprintf ('%dy', years); endif if (months != 0) ## Check Format contains 'q' to split between quarters and months if (! isempty (strfind (calDur.Format, 'q'))) quarters = fix (months / 3); months = rem (months, 3); if (quarters != 0) els{end+1} = sprintf ('%dq', quarters); endif if (months != 0) els{end+1} = sprintf ('%dmo', months); endif else els{end+1} = sprintf ('%dmo', months); endif endif else if (calDur.Months != 0) ## Check Format contains 'q' to split between quarters and months if (! isempty (strfind (calDur.Format, 'q'))) quarters = fix (calDur.Months / 3); months = rem (calDur.Months, 3); if (quarters != 0) els{end+1} = sprintf ('%dq', quarters); endif if (months != 0) els{end+1} = sprintf ('%dmo', months); endif else els{end+1} = sprintf ('%dmo', calDur.Months); endif endif endif if (calDur.Days != 0) ## Check Format contains 'w' to print whole weeks and subtract ## them from days if (! isempty (strfind (calDur.Format, 'w'))) weeks = fix (calDur.Days / 7); if (weeks != 0) els{end+1} = sprintf ('%dw', weeks); calDur.Days -= weeks * 7; endif endif if (calDur.Days != 0) els{end+1} = sprintf ('%dd', calDur.Days); endif endif millis = milliseconds (calDur.Time); if (abs (millis) > 4e-12) sec = millis / 1000; fracSec = rem (sec,1); x = fix (sec); hours = fix (x / (60 * 60)); x = rem (x, (60 * 60)); minutes = fix (x / 60); x = rem (x, 60); seconds = x; msec = round (fracSec * 1000); if (abs (msec) == 1000) seconds = seconds + (msec / 1000); msec = 0; endif if (msec >= 1) str = sprintf ('%ds', msec / 1000); str(1) = []; els{end+1} = sprintf ('%dh %dm %d%s', hours, minutes, ... seconds, str); elseif (fracSec > 0 && fracSec < 0.001 && seconds == 0) els{end+1} = sprintf ('%dh %dm %0.0es', hours, minutes, fracSec); else els{end+1} = sprintf ('%dh %dm %ds', hours, minutes, seconds); endif endif if (isempty (els)) els = {'0d'}; endif cstr{i} = strjoin (els, ' '); endif endfor endfunction ## -*- texinfo -*- ## @deftypefn {calendarDuration} {@var{cstr} =} cellstr (@var{calD}) ## @deftypefnx {calendarDuration} {@var{cstr} =} cellstr (@var{calD}, @var{FMT}) ## ## Convert calendarDuration array to a cell array of character vectors. ## ## @code{@var{cstr} = cellstr (@var{calD})} returns a cellstr array of ## character vectors, @var{cstr}, which has the same size as the input ## @var{calD}. @var{cstr} contains the string representations of the ## calendar durations in @var{calD}. ## ## @code{@var{cstr} = cellstr (@var{calD}, @var{FMT})} further specifies ## the format of the returned string representations. @var{FMT} must be a ## character vector conforming to the same specifications required by the ## constructor's @qcode{'Format'} property paired argument. Note that ## @code{cellstr} only accepts @var{FMT} as a single argument and not as a ## property paired argument. ## ## @end deftypefn function cstr = cellstr (this, FMT = '') if (! isempty (FMT)) errmsg = checkFormatString (FMT); if (! isempty (errmsg)) error ("calendarDuration.cellstr: FMT %s", errmsg); endif this.Format = FMT; endif cstr = dispstrings (this); endfunction ## -*- texinfo -*- ## @deftypefn {calendarDuration} {@var{cmat} =} char (@var{calD}) ## @deftypefnx {calendarDuration} {@var{cmat} =} char (@var{calD}, @var{FMT}) ## ## Convert calendarDuration array to a character matrix. ## ## @code{@var{cmat} = char (@var{calD})} returns a character matrix with ## one row per element in @var{calD}. The second optional argument, ## @var{FMT}, can be used to specify the format of the returned string ## representations of the calendarDuration input array @var{calD}. ## ## @end deftypefn function cmat = char (this, FMT = '') if (! isempty (FMT)) errmsg = checkFormatString (FMT); if (! isempty (errmsg)) error ("calendarDuration.char: FMT %s", errmsg); endif this.Format = FMT; endif cmat = char (dispstrings (this)); endfunction ## -*- texinfo -*- ## @deftypefn {calendarDuration} {@var{DV} =} datevec (@var{calD}) ## @deftypefnx {calendarDuration} {[@var{Y}, @var{MO}] =} datevec (@var{calD}) ## @deftypefnx {calendarDuration} {[@var{Y}, @var{MO}, @var{D}] =} datevec (@var{calD}) ## @deftypefnx {calendarDuration} {[@var{Y}, @var{MO}, @var{D}, @var{H}] =} datevec (@var{calD}) ## @deftypefnx {calendarDuration} {[@var{Y}, @var{MO}, @var{D}, @var{H}, @var{MI}] =} datevec (@var{calD}) ## @deftypefnx {calendarDuration} {[@var{Y}, @var{MO}, @var{D}, @var{H}, @var{MI}, @var{S}] =} datevec (@var{calD}) ## ## Convert calendarDuration array to date vectors. ## ## @code{@var{DV} = datevec (@var{calD})} returns an @math{N*6} numeric ## matrix, where @math{N} is the number of elements in @var{calD} and the ## columns corresponds to years, months, days, hours, minutes, and seconds, ## respectively. ## ## When @code{datevec} is called with more than one output arguments, then ## it returns the components of the date vectors as individual variables ## @var{Y}, @var{MO}, @var{D}, @var{H}, @var{MI},and @var{S} corresponding ## to years, months, days, hours, minutes, and seconds, respectively. In ## this case, the individual variables have the same size as the input array ## @var{calD}. ## ## @end deftypefn function varargout = datevec (this) [h, m, s] = hms (this.Time); years = fix (this.Months / 12); months = rem (this.Months, 12); DV = [years(:), months(:), this.Days(:), h(:), m(:), s(:)]; if (nargout == 0 || nargout == 1) varargout{1} = DV; elseif (nargout <= 6) varargout{1} = years; varargout{2} = months; if (nargout > 2) varargout{3} = this.Days; endif if (nargout > 3) varargout{4} = h; endif if (nargout > 4) varargout{5} = m; endif if (nargout > 5) varargout{6} = s; endif else error ("calendarDuration.datevec: too many output arguments."); endif endfunction ## -*- texinfo -*- ## @deftypefn {calendarDuration} {@var{T} =} time (@var{calD}) ## ## Return time portion of calendarDuration array. ## ## @code{@var{T} = time (@var{calD})} returns a duration array @var{T} with ## the time portions of the calendarDuration array @var{calD}. ## ## @end deftypefn function T = time (this) T = this.Time; endfunction ## -*- texinfo -*- ## @deftypefn {calendarDuration} {[@dots{}] =} split (@var{calD}, @var{units}) ## ## Split calendarDuration array into numeric and duration units. ## ## @code{[@dots{}] = split (@var{calD}, @var{units})} splits the calendar ## duration units in @var{calD} into separate numeric arrays according to ## date/time units specified in @var{units}, which must be either a cell ## array of character vectors or a string array containing any of the ## following date/time units in descending order. ## ## @enumerate ## @item @qcode{'years'} ## @item @qcode{'quarters'} ## @item @qcode{'months'} ## @item @qcode{'weeks'} ## @item @qcode{'days'} ## @item @qcode{'time'} ## @end enumerate ## ## When a single date/time unit is specified, @var{units} may also be a ## character vector. When @qcode{'time'} is specified in @var{units}, the ## corresponding returned argument is a @code{duration} array. The values ## of years, quarters, and months are computed independently from the values ## of weeks and days in @var{calD}, with larger units taking precedence when ## specified The same applies for duration arrays, when requested. ## ## @end deftypefn function varargout = split (this, units) ## Check input if (nargin < 2) error ("calendarDuration.split: too few input arguments."); endif if (isstring (units) || ischar (units)) units = cellstr (units); elseif (! iscellstr (units)) error ("calendarDuration.split: invalid input type for UNITS."); endif valid_units = {'years', 'quarters', 'months', 'weeks', 'days', 'time'}; idx_units = ismember (tolower (units), valid_units); if (! all (idx_units)) error ("calendarDuration.split: '%s' is not a valid time unit.", ... units{find (! idx_units)}); endif idx_order = cellfun (@(x) find (strcmpi (x, valid_units)), units); if (any (diff (idx_order) < 0)) error (strcat ("calendarDuration.split: UNITS must", ... " be specified in descending order.")); endif ## Check output n_args = numel (units); if (nargout != n_args) error ("calendarDuration.split: wrong number of output arguments."); endif months = this.Months; days = this.Days; for i = 1:n_args unit = units{i}; if (strcmpi (unit, 'years')) years = fix (months / 12); months = months - years * 12; varargout{i} = years; elseif (strcmpi (unit, 'quarters')) quarters = fix (months / 3); months = months - quarters * 3; varargout{i} = quarters; elseif (strcmpi (unit, 'months')) varargout{i} = months; elseif (strcmpi (unit, 'weeks')) weeks = fix (days / 7); days = days - weeks * 7; varargout{i} = weeks; elseif (strcmpi (unit, 'days')) varargout{i} = days; elseif (strcmpi (unit, 'time')) varargout{i} = this.Time; endif endfor endfunction ## -*- texinfo -*- ## @deftypefn {calendarDuration} {@var{X} =} caldays (@var{calD}) ## ## Calendar duration in days. ## ## @code{@var{X} = caldays (@var{calD})} returns a numeric array with the ## number of days as represented in @var{calD}. ## ## @code{caldays} is also available as a function, in which case it performs ## the opposite conversion. ## ## @seealso{calendarDuration.calyears, calendarDuration.calquarters, ## calendarDuration.calmonths, calendarDuration.calweeks, caldays} ## @end deftypefn function out = caldays (this) out = this.Days; endfunction ## -*- texinfo -*- ## @deftypefn {calendarDuration} {@var{X} =} calweeks (@var{calD}) ## ## Calendar duration in weeks. ## ## @code{@var{X} = calweeks (@var{calD})} returns a numeric array with the ## number of weeks as represented in @var{calD}. ## ## @code{calweeks} is also available as a function, in which case it ## performs the opposite conversion. ## ## @seealso{calendarDuration.calyears, calendarDuration.calquarters, ## calendarDuration.calmonths, calendarDuration.caldays, calweeks} ## @end deftypefn function out = calweeks (this) out = fix (this.Days / 7); endfunction ## -*- texinfo -*- ## @deftypefn {calendarDuration} {@var{X} =} calmonths (@var{calD}) ## ## Calendar duration in months. ## ## @code{@var{X} = calmonths (@var{calD})} returns a numeric array with the ## number of months as represented in @var{calD}. ## ## @code{calmonths} is also available as a function, in which case it ## performs the opposite conversion. ## ## @seealso{calendarDuration.calyears, calendarDuration.calquarters, ## calendarDuration.calweeks, calendarDuration.caldays, calmonths} ## @end deftypefn function out = calmonths (this) out = this.Months; endfunction ## -*- texinfo -*- ## @deftypefn {calendarDuration} {@var{X} =} calquarters (@var{calD}) ## ## Calendar duration in quarters. ## ## @code{@var{X} = calquarters (@var{calD})} returns a numeric array with ## the number of quarters as represented in @var{calD}. ## ## @code{calquarters} is also available as a function, in which case it ## performs the opposite conversion. ## ## @seealso{calendarDuration.calyears, calendarDuration.calmonths, ## calendarDuration.calweeks, calendarDuration.caldays, calquarters} ## @end deftypefn function out = calquarters (this) out = fix (this.Months / 3); endfunction ## -*- texinfo -*- ## @deftypefn {calendarDuration} {@var{X} =} calyears (@var{calD}) ## ## Calendar duration in years. ## ## @code{@var{X} = calyears (@var{calD})} returns a numeric array with the ## number of years as represented in @var{calD}. ## ## @code{calyears} is also available as a function, in which case it ## performs the opposite conversion. ## ## @seealso{calendarDuration.calquarters, calendarDuration.calmonths, ## calendarDuration.calweeks, calendarDuration.caldays, calyears} ## @end deftypefn function out = calyears (this) out = fix (this.Months / 12); endfunction endmethods ################################################################################ ## ** Summary Information ** ## ################################################################################ ## Available Methods ## ## ## ## 'size' 'ndims' 'numel' 'nnz' ## ## 'length' 'keyHash' ## ## ## ################################################################################ methods (Access = public) ## -*- texinfo -*- ## @deftypefn {calendarDuration} {@var{sz} =} size (@var{calD}) ## @deftypefnx {calendarDuration} {@var{dim_sz} =} size (@var{calD}, @var{dim}) ## @deftypefnx {calendarDuration} {@var{dim_sz} =} size (@var{calD}, @var{d1}, @var{d2}, @dots{}) ## @deftypefnx {calendarDuration} {[@var{rows}, @var{columns}, @dots{}, @var{dim_n_sz}] =} size (@dots{}) ## ## Size of a calendarDuration array. ## ## @code{@var{sz} = size (@var{calD})} returns a row vector with the size ## (number of elements) of each dimension for the calendar duration array ## @var{calD}. ## ## @code{@var{dim_sz} = size (@var{calD}, @var{dim})} returns the size of ## the corresponding dimension specified in @var{dim}. If @var{dim} is a ## vector, then @var{dim_sz} is a vector of the same length and with each ## element corresponding to a specified dimension. Multiple dimensions may ## also be specified as separate arguments. ## ## With a single output argument, @code{size} returns a row vector. When ## called with multiple output arguments, @code{size} returns the size of ## dimension N in the Nth argument. ## ## @end deftypefn function varargout = size (this, varargin) if (! isempty (varargin)) sz = size (this.Months, varargin{:}); else sz = size (this.Months); endif if (nargout == 0 || nargout == 1) varargout{1} = sz; elseif (numel (sz) != nargout) error (strcat ("calendarDuration.size: number of output arguments", ... " does not match number of requested dimensions.")); else for i = 1:nargout varargout{i} = sz(i); endfor endif endfunction ## -*- texinfo -*- ## @deftypefn {calendarDuration} {@var{out} =} ndims (@var{calD}) ## ## Number of dimensions in a calendarDuration array. ## ## @code{@var{out} = ndims (@var{calD})} returns the number of dimensions ## of the calendar duration array @var{calD}. ## ## @end deftypefn function out = ndims (this) out = ndims (this.Months); endfunction ## -*- texinfo -*- ## @deftypefn {calendarDuration} {@var{out} =} numel (@var{calD}) ## ## Total number of elements in a calendarDuration array. ## ## @code{@var{out} = numel (@var{calD})} returns the number of elements in ## the calendarDuration array @var{calD}. ## ## @end deftypefn function out = numel (this, varargin) out = numel (this.Months); endfunction ## -*- texinfo -*- ## @deftypefn {calendarDuration} {@var{out} =} nnz (@var{calD}) ## ## Number of nonzero elements in calendarDuration array. ## ## @code{@var{out} = nnz (@var{calD})} returns the number of nonzero ## elements in the calendar duration array @var{calD}. ## ## @end deftypefn function out = nnz (this) m = this.Months(:); d = this.Days(:); h = hours (this.Time(:)); out = numel (m) - sum (m == 0 & d == 0 & h == 0); endfunction ## -*- texinfo -*- ## @deftypefn {calendarDuration} {@var{N} =} length (@var{calD}) ## ## Length of a calendarDuration vector. ## ## @code{@var{N} = length (@var{calD})} returns the size of the longest ## dimension of the calendarDuration array @var{calD}, unless any of its ## dimensions has zero length, in which case @code{length (@var{calD})} ## returns 0. ## ## @end deftypefn function N = length (this) if (isempty (this.Months)) N = 0; else N = max (size (this.Months)); endif endfunction ## -*- texinfo -*- ## @deftypefn {calendarDuration} {@var{hey} =} keyHash (@var{calD}) ## @deftypefnx {calendarDuration} {@var{hey} =} keyHash (@var{calD}, @var{base}) ## ## Generate a hash code for a calendarDuration array. ## ## @code{@var{h} = keyHash (@var{calD})} generates a @qcode{uint64} scalar ## that represents the input array @var{calD}. @code{keyHash} utilizes the ## 64-bit FNV-1a variant of the Fowler-Noll-Vo non-cryptographic hash ## function. ## ## @code{@var{h} = keyHash (@var{calD}), @var{base}} also generates a 64-bit ## hash code using @var{base} as the offset basis for the FNV-1a hash ## algorithm. @var{base} must be a @qcode{uint64} integer type scalar. Use ## this syntax to cascade @code{keyHash} on multiple objects for which a ## single hash code is required. ## ## Note that unlike MATLAB, this implementation does not use any random ## seed. As a result, @code{keyHash} will always generate the exact same ## hash key for any particular input across different workers and Octave ## sessions. ## ## @end deftypefn function key = keyHash (this, base = []) ## Initialize string with size and class name size_str = sprintf ('%dx', size (this.Months))(1:end-1); init_str = [size_str 'calendarDuration']; if (base) if (! (isscalar (base) && isa (base, 'uint64'))) error ("calendarDuration.keyHash: BASE must be a UINT64 scalar."); endif key = __ckeyHash__(init_str, base); else key = __ckeyHash__(init_str); endif ## Compute hash with underlying calendarDuration array values if (! isempty (this.Months)) key = __nkeyHash__(this.Months(:), key); key = __nkeyHash__(this.Days(:), key); key = keyHash (this.Time, key); endif endfunction endmethods ################################################################################ ## ** Query Operations ** ## ################################################################################ ## Available Methods ## ## ## ## 'iscolumn' 'isempty' 'isequal' 'isequaln' ## ## 'isfinite' 'isinf' 'ismatrix' 'ismissing' ## ## 'isnan' 'isreal' 'isrow' 'isscalar' ## ## 'isvector' ## ## ## ################################################################################ methods (Access = public) ## -*- texinfo -*- ## @deftypefn {calendarDuration} {@var{TF} =} iscolumn (@var{calD}) ## ## Return true if calendarDuration array is a column vector. ## ## @code{@var{TF} = iscolumn (@var{calD})} returns a logical scalar ## @var{TF}, which is @qcode{true} if the calendar duration array @var{calD} ## is a column vector and @qcode{false} otherwise. A column vector is a 2-D ## array for which @code{size (@var{X})} returns @code{[@var{N}, 1]} with ## non-negative @var{N}. ## ## @end deftypefn function TF = iscolumn (this) TF = iscolumn (this.Months); endfunction ## -*- texinfo -*- ## @deftypefn {calendarDuration} {@var{TF} =} isempty (@var{calD}) ## ## Return true if calendarDuration array is empty. ## ## @code{@var{TF} = isempty (@var{calD})} returns a logical scalar @var{TF}, ## which is @qcode{true} if the calendar duration array @var{calD} is empty ## and @qcode{false} otherwise. ## ## @end deftypefn function TF = isempty (this) TF = isempty (this.Months); endfunction ## -*- texinfo -*- ## @deftypefn {calendarDuration} {@var{TF} =} isequal (@var{calD1}, @var{calD2}) ## @deftypefnx {calendarDuration} {@var{TF} =} isequal (@var{calD1}, @var{calD2}, @dots{}) ## ## Return true if calendarDuration arrays are equal. ## ## @code{@var{TF} = isequal (@var{calD1}, @var{calD2})} returns a logical ## scalar @var{TF}, which is @qcode{true} if the calendar duration arrays ## @var{calD1} and @var{calD2} contain the same values and @qcode{false} ## otherwise. ## ## @code{@var{TF} = isequal (@var{calD1}, @var{calD2}, @dots{})} returns a ## logical scalar @var{TF}, which is @qcode{true} if all input arguments are ## calendar duration arrays with equal values in each corresponding elements ## and @qcode{false} otherwise. ## ## @end deftypefn function TF = isequal (this, varargin) if (numel (varargin) < 1) error ("calendarDuration.isequal: too few input arguments."); endif n_dim = size (this); for i = 1:numel (varargin) tmp = varargin{i}; if (! isa (tmp, 'calendarDuration')) error (strcat ("calendarDuration.isequal: all input arguments", ... " must be calendarDuration arrays.")); endif if (! isequal (n_dim, size (tmp))) TF = false; return; endif if (this == tmp) TF = true; else TF = false; return; endif endfor endfunction ## -*- texinfo -*- ## @deftypefn {calendarDuration} {@var{TF} =} isequaln (@var{calD1}, @var{calD2}) ## @deftypefnx {calendarDuration} {@var{TF} =} isequaln (@var{calD1}, @var{calD2}, @dots{}) ## ## Return true if calendarDuration arrays are equal under the assumption ## that missing elements are equal. ## ## @code{@var{TF} = isequaln (@var{calD1}, @var{calD2})} returns a logical ## scalar @var{TF}, which is @qcode{true} if the calendar duration arrays ## @var{calD1} and @var{calD2} contain the same values or corresponding ## missing elements and @qcode{false} otherwise. ## ## @code{@var{TF} = isequaln (@var{calD1}, @var{calD2}, @dots{})} returns a ## logical scalar @var{TF}, which is @qcode{true} if all input arguments ## are calendar duration arrays with equal values or corresponding missing ## elements and @qcode{false} otherwise. ## ## @end deftypefn function TF = isequaln (this, varargin) if (numel (varargin) < 1) error ("calendarDuration.isequaln: too few input arguments."); endif n_dim = size (this); ## Force NaNs to zeros i_nan = isnan (this); if (any (i_nan, 'all')) this.Months(i_nan) = 0; this.Days(i_nan) = 0; this.Time(i_nan) = duration (0, 0, 0); endif for i = 1:numel (varargin) tmp = varargin{i}; if (! isa (tmp, 'calendarDuration')) error (strcat ("calendarDuration.isequaln: all input arguments", ... " must be calendarDuration arrays.")); endif if (! isequal (n_dim, size (tmp))) TF = false; return; endif ## Force NaNs to zeros i_nan = isnan (tmp); if (any (i_nan, 'all')) tmp.Months(i_nan) = 0; tmp.Days(i_nan) = 0; tmp.Time(i_nan) = duration (0, 0, 0); endif if (this == tmp) TF = true; else TF = false; return; endif endfor endfunction ## -*- texinfo -*- ## @deftypefn {calendarDuration} {@var{TF} =} isfinite (@var{calD}) ## ## Return true for calendar durations that are finite. ## ## @code{@var{TF} = isfinite (@var{calD})} returns a logical array @var{TF} ## of the same size as @var{calD} containing @qcode{true} for each ## corresponding element of @var{calD} that is finite and @qcode{false} ## otherwise. Finite elements are those which are neither infinite nor ## Not-A-Number. ## ## @end deftypefn function TF = isfinite (this) TF = isfinite (this.Months); endfunction ## -*- texinfo -*- ## @deftypefn {calendarDuration} {@var{TF} =} isinf (@var{calD}) ## ## Return true for calendar durations that are infinite. ## ## @code{@var{TF} = isinf (@var{calD})} returns a logical array @var{TF} ## of the same size as @var{calD} containing @qcode{true} for each ## corresponding element of @var{calD} that is either @qcode{Inf} or ## @qcode{-Inf} and @qcode{false} otherwise. ## ## @end deftypefn function TF = isinf (this) TF = isinf (this.Months); endfunction ## -*- texinfo -*- ## @deftypefn {calendarDuration} {@var{TF} =} ismatrix (@var{calD}) ## ## Return true if calendarDuration array is a 2-D array. ## ## @code{@var{TF} = ismatrix (@var{calD})} returns a logical scalar ## @var{TF}, which is @qcode{true} if the calendarDuration array @var{calD} ## is a matrix and @qcode{false} otherwise. A matrix is an array of any ## type where @code{ndims (@var{X}) == 2} and for which ## @code{size (@var{X})} returns @code{[@var{H}, @var{W}]} with non-negative ## @var{H} and @var{W}. ## ## @end deftypefn function TF = ismatrix (this) TF = ismatrix (this.Months); endfunction ## -*- texinfo -*- ## @deftypefn {calendarDuration} {@var{TF} =} ismissing (@var{calD}) ## ## Find missing data in a calendarDuration array. ## ## Missing values in calendarDuration arrays are represented by @qcode{NaN}, ## thus @code{@var{TF} = ismissing (@var{calD})} is equivalent to ## @code{@var{TF} = isnan (@var{calD})}. ## ## Note: @code{ismissing} for calendarDuration arrays does not support a ## second @var{Indicator} argument. ## ## @end deftypefn function TF = ismissing (this, varargin) if (nargin > 1) error ("calendarDuration.ismissing: Indicators are not supported."); endif TF = isnan (this.Months); endfunction ## -*- texinfo -*- ## @deftypefn {calendarDuration} {@var{TF} =} isnan (@var{calD}) ## ## Return true for calendar durations that are Not-A-Number. ## ## @code{@var{TF} = isnan (@var{calD})} returns a logical array @var{TF} ## of the same size as @var{calD} containing @qcode{true} for each ## corresponding element of @var{calD} that is @qcode{NaN} and @qcode{false} ## otherwise. ## ## @end deftypefn function TF = isnan (this) TF = isnan (this.Months); endfunction ## -*- texinfo -*- ## @deftypefn {calendarDuration} {@var{TF} =} isreal (@var{calD}) ## ## Always return true for calendarDuration arrays. ## ## @code{@var{TF} = isreal (@var{calD})} always returns a logical scalar ## @qcode{true} value, if the input argument is a calendarDuration array. ## ## @end deftypefn function TF = isreal (this) TF = true; endfunction ## -*- texinfo -*- ## @deftypefn {calendarDuration} {@var{TF} =} isrow (@var{calD}) ## ## Return true if calendarDuration array is a row vector. ## ## @code{@var{TF} = isrow (@var{calD})} returns a logical scalar @var{TF}, ## which is @qcode{true} if the calendarDuration array @var{calD} is a row ## vector and @qcode{false} otherwise. A row vector is a 2-D array for ## which @code{size (@var{X})} returns @code{[1, @var{N}]} with non-negative ## @var{N}. ## ## @end deftypefn function TF = isrow (this) TF = isrow (this.Months); endfunction ## -*- texinfo -*- ## @deftypefn {calendarDuration} {@var{TF} =} isscalar (@var{calD}) ## ## Return true if calendarDuration array is a scalar. ## ## @code{@var{TF} = isscalar (@var{calD})} returns a logical scalar ## @var{TF}, which is @qcode{true} if the calendarDuration array @var{calD} ## is also a scalar and @qcode{false} otherwise. A scalar is a single ## element object for which @code{size (@var{X})} returns @code{[1, 1]}. ## ## @end deftypefn function TF = isscalar (this) TF = isscalar (this.Months); endfunction ## -*- texinfo -*- ## @deftypefn {calendarDuration} {@var{TF} =} isvector (@var{calD}) ## ## Return true if calendarDuration array is a vector. ## ## @code{@var{TF} = isvector (@var{calD})} returns a logical scalar ## @var{TF}, which is @qcode{true} if the calendarDuration array @var{calD} ## is a vector and @qcode{false} otherwise. A vector is a 2-D array for ## which one of the dimensions is equal to 1 (either @math{1*N} or ## @math{N*1}). By definition, a scalar is also a vector. ## ## @end deftypefn function TF = isvector (this) TF = isvector (this.Months); endfunction endmethods ################################################################################ ## ** Mathematical Operations ** ## ################################################################################ ## Available Methods ## ## ## ## 'minus' 'uminus' 'plus' 'uplus' ## ## 'times' 'mtimes' ## ## ## ################################################################################ methods (Access = public) ## -*- texinfo -*- ## @deftypefn {calendarDuration} {@var{C} =} minus (@var{A}, @var{B}) ## ## Subtraction for calendarDuration arrays. ## ## @code{@var{C} = minus (@var{A}, @var{B})} is the equivalent of the syntax ## @code{@var{C} = @var{A} - @var{B}} and returns the result of subtracting ## the corresponding elements of @var{B} from those of @var{A}. @var{C} is ## a calendarDuration array of the same size as the input arguments after ## the necessary (if required) expansion. @var{A} and @var{B} must be size ## compatible, which translates to they can be the same size, one can be ## scalar, or for every dimension, their dimension sizes must be equal or ## one of them must be 1. ## ## Either @var{A} or @var{B} may also be a duration or a numeric array with ## the latter representing duration days and being internally converted to a ## duration array with the @code{days ()} function. ## ## @end deftypefn function out = minus (A, B) if (isa (A, 'calendarDuration') && isa (B, 'calendarDuration')) out = A; out.Months = A.Months - B.Months; out.Days = A.Days - B.Days; out.Time = A.Time - B.Time; elseif (isa (A, 'calendarDuration') && isa (B, 'duration')) out = A; tmp = zeros (size (B)); out.Months = A.Months - tmp; out.Days = A.Days - tmp; out.Time = A.Time - B; elseif (isa (A, 'calendarDuration') && isnumeric (B)) out = A; tmp = zeros (size (B)); out.Months = out.Months - tmp; out.Days = out.Days - tmp; out.Time = out.Time - days (B); elseif (isnumeric (A) && isa (B, 'calendarDuration')) out = B; tmp = zeros (size (A)); out.Months = -out.Months + tmp; out.Days = -out.Days + tmp; out.Time = -out.Time + days (A); else error (strcat ("calendarDuration: subtraction is not defined", ... " between '%s' and '%s' arrays."), class (A), class (B)); endif out = broadcastProperties (out); endfunction ## -*- texinfo -*- ## @deftypefn {calendarDuration} {@var{C} =} minus (@var{A}) ## ## Unary minus for calendarDuration arrays. ## ## @code{@var{C} = uminus (@var{A})} is the equivalent of the syntax ## @code{@var{C} = -@var{A}} and returns @var{A} after negating its ## elements. @var{C} is a calendarDuration array of the same size as ## @var{A}. ## ## @end deftypefn function out = uminus (A) out = A; out.Months = -A.Months; out.Days = -A.Days; out.Time = -A.Time; endfunction ## -*- texinfo -*- ## @deftypefn {calendarDuration} {@var{C} =} plus (@var{A}, @var{B}) ## ## Addition for calendarDuration arrays. ## ## @code{@var{C} = plus (@var{A}, @var{B})} is the equivalent of the syntax ## @code{@var{C} = @var{A} + @var{B}} and returns the result of adding the ## corresponding elements of @var{A} and @var{B}. @var{C} is a ## calendarDuration array of the same size as the input arguments after the ## necessary (if required) expansion. @var{A} and @var{B} must be size ## compatible, which translates to they can be the same size, one can be ## scalar, or for every dimension, their dimension sizes must be equal or ## one of them must be 1. ## ## Either @var{A} or @var{B} may also be a duration or a numeric array with ## the latter representing duration days and being internally converted to a ## duration array with the @code{days ()} function. ## ## @end deftypefn function out = plus (A, B) if (isa (B, 'datetime')) out = B + A; return; endif if (isa (A, 'calendarDuration') && isa (B, 'calendarDuration')) out = A; out.Months = A.Months + B.Months; out.Days = A.Days + B.Days; out.Time = A.Time + B.Time; out = broadcastProperties (out); elseif (isa (A, 'calendarDuration') && isa (B, 'duration')) out = A; tmp = zeros (size (B)); out.Months = A.Months + tmp; out.Days = A.Days + tmp; out.Time = A.Time + B; elseif (isa (A, 'calendarDuration') && isnumeric (B)) out = A; tmp = zeros (size (B)); out.Months = A.Months + tmp; out.Days = A.Days + tmp; out.Time = A.Time + days (B); elseif (isnumeric (A) && isa (B, 'calendarDuration')) out = B; tmp = zeros (size (A)); out.Months = B.Months + tmp; out.Days = B.Days + tmp; out.Time = B.Time + days (A); else error (strcat ("calendarDuration: addition is not defined", ... " between '%s' and '%s' arrays."), class (A), class (B)); endif endfunction ## -*- texinfo -*- ## @deftypefn {calendarDuration} {@var{C} =} uplus (@var{A}) ## ## Unary plus for calendarDuration arrays. ## ## @code{@var{C} = uplus (@var{A})} is the equivalent of the syntax ## @code{@var{C} = +@var{A}} and returns a copy of @var{A}. @var{C} is a ## calendarDuration array of the same size as @var{A}. ## ## @end deftypefn function out = uplus (A) out = A; out.Months = A.Months; out.Days = A.Days; out.Time = A.Time; endfunction ## -*- texinfo -*- ## @deftypefn {calendarDuration} {@var{C} =} times (@var{A}, @var{B}) ## ## Element-by-element multiplication for calendarDuration arrays. ## ## @code{@var{C} = times (@var{A}, @var{B})} is the equivalent of the syntax ## @code{@var{C} = @var{A} .* @var{B}} and returns the element-by-element ## multiplication product of inputs @var{A} and @var{B}. Either @var{A} or ## @var{B} must be a calendarDuration array and its complement must be a ## double array. ## ## @var{C} is a calendarDuration array of the same size as the input ## arguments after the necessary (if required) expansion. @var{A} and ## @var{B} must be size compatible, which translates to they can be the same ## size, one can be scalar, or for every dimension, their dimension sizes ## must be equal or one of them must be 1. ## ## @end deftypefn function out = times (A, B) if (isa (A, 'calendarDuration') && isnumeric (B)) out = A; tmp = double (B); elseif (isnumeric (A) && isa (B, 'calendarDuration')) out = B; tmp = double (A); else error (strcat ("calendarDuration: multiplication is not defined", ... " between '%s' and '%s' arrays."), class (A), class (B)); endif out.Months = out.Months .* tmp; out.Days = out.Days .* tmp; out.Time = out.Time .* tmp; endfunction ## -*- texinfo -*- ## @deftypefn {calendarDuration} {@var{C} =} mtimes (@var{A}, @var{B}) ## ## Matrix multiplication for calendarDuration arrays. ## ## @code{@var{C} = mtimes (@var{A}, @var{B})} is the equivalent of the ## syntax @code{@var{C} = @var{A} * @var{B}} and returns the matrix ## multiplication product of inputs @var{A} and @var{B}. Either @var{A} or ## @var{B} must be a calendarDuration array and its complement must be a ## double array. ## ## @var{C} is a calendarDuration array of the same size as the input ## arguments after the necessary (if required) expansion. @var{A} and ## @var{B} must be size compatible, which translates to they can be the same ## size, one can be scalar, or for every dimension, their dimension sizes ## must be equal or one of them must be 1. ## ## @end deftypefn function out = mtimes (A, B) if (isa (A, 'calendarDuration') && isnumeric (B)) out = A; tmp = double (B); out.Months = A.Months * tmp; out.Days = A.Days * tmp; out.Time = A.Time * tmp; elseif (isnumeric (A) && isa (B, 'calendarDuration')) out = B; tmp = double (A); out.Months = tmp * B.Months; out.Days = tmp * B.Days; out.Time = tmp * B.Time; else error (strcat ("calendarDuration: matrix multiplication is", ... " not defined between '%s' and '%s' arrays."), ... class (A), class (B)); endif endfunction endmethods ################################################################################ ## ** Equality and Filter Operations ** ## ################################################################################ ## Available Methods ## ## ## ## 'eq' 'ne' 'unique' ## ## ## ################################################################################ methods (Access = public) ## -*- texinfo -*- ## @deftypefn {calendarDuration} {@var{TF} =} eq (@var{A}, @var{B}) ## ## Test for equality between calendarDuration arrays. ## ## @code{@var{TF} = eq (@var{A}, @var{B})} is the equivalent of the syntax ## @code{@var{TF} = @var{A} == @var{B}} and returns a logical array with ## elements set to @qcode{true} where calendarDuration arrays @var{A} and ## @var{B} are equal, otherwise set to @qcode{false}. Missing values are ## not equal to each other. Hence, any @code{NaN} values in @var{A} or ## @var{B} result to @qcode{false} elements in @var{TF}. ## ## @var{A} and @var{B} must be size compatible, which translates to they can ## be the same size, one can be scalar, or for every dimension, their ## dimension sizes must be equal or one of them must be 1. The size of the ## output @var{TF} is the same as the size of input arrays after their ## expansion according to the broadcasting rules. ## ## @end deftypefn function TF = eq (A, B) if (! (iscalendarduration (A) && iscalendarduration (B))) error (strcat ("calendarDuration.eq: equality is not defined", ... " between '%s' and '%s' arrays."), class (A), class (B)); endif TF = A.Months == B.Months & A.Days == B.Days & A.Time == B.Time; endfunction ## -*- texinfo -*- ## @deftypefn {calendarDuration} {@var{TF} =} ne (@var{A}, @var{B}) ## ## Test for inequality between calendarDuration arrays. ## ## @code{@var{TF} = ne (@var{A}, @var{B})} is the equivalent of the syntax ## @code{@var{TF} = @var{A} != @var{B}} and returns a logical array with ## elements set to @qcode{true} where calendarDuration arrays @var{A} and ## @var{B} are not equal, otherwise set to @qcode{false}. Missing values ## are not equal to each other. Hence, any @code{NaN} values in @var{A} or ## @var{B} result to @qcode{true} elements in @var{TF}. ## ## @var{A} and @var{B} must be size compatible, which translates to they can ## be the same size, one can be scalar, or for every dimension, their ## dimension sizes must be equal or one of them must be 1. The size of the ## output @var{TF} is the same as the size of input arrays after their ## expansion according to the broadcasting rules. ## ## @end deftypefn function TF = ne (A, B) if (! (iscalendarduration (A) && iscalendarduration (B))) error (strcat ("calendarDuration.ne: inequality is not defined", ... " between '%s' and '%s' arrays."), class (A), class (B)); endif TF = A.Months != B.Months | A.Days != B.Days | A.Time != B.Time; endfunction ## -*- texinfo -*- ## @deftypefn {calendarDuration} {@var{B} =} unique (@var{A}) ## @deftypefnx {calendarDuration} {@var{B} =} unique (@var{A}, @var{setOrder}) ## @deftypefnx {calendarDuration} {@var{B} =} unique (@var{A}, @var{occurrence}) ## @deftypefnx {calendarDuration} {@var{B} =} unique (@var{A}, @var{setOrder}, @var{occurrence}) ## @deftypefnx {calendarDuration} {@var{B} =} unique (@var{A}, @var{occurrence}, @var{setOrder}) ## @deftypefnx {calendarDuration} {@var{B} =} unique (@var{A}, @dots{}, @qcode{'rows'}) ## @deftypefnx {calendarDuration} {[@var{B}, @var{ixA}, @var{ixB}] =} unique (@dots{}) ## ## Unique values in a calendarDuration array. ## ## @code{@var{B} = unique (@var{A})} returns the unique values of the ## calendarDuration array @var{A} in sorted order. ## ## @code{@var{B} = unique (@var{A}, @var{setOrder})} returns the unique ## values of the calendarDuration array @var{A} in an order as specified by ## @var{setOrder}, which can be either of the following values: ## ## @itemize ## @item @qcode{'sorted'} (default) returns the unique values sorted in ## ascending order. ## @item @qcode{'stable'} returns the unique values according to their order ## of occurrence. ## @end itemize ## ## @code{@var{B} = unique (@var{A}, @var{occurrence})} returns the unique ## values of the calendarDuration array @var{tblA} according to their order ## of occurrence. @var{occurrence} can be either of the following values: ## ## @itemize ## @item @qcode{'first'} (default) returns the first occurrence of each ## unique value, i.e. the lowest possible indices are returned. ## @item @qcode{'last'} returns the last occurrence of each unique value, ## i.e. the highest possible indices are returned. ## @end itemize ## ## You can specify @var{setOrder} and @var{occurrence} arguments together. ## ## @code{@var{B} = unique (@var{A}, @dots{}, @qcode{'rows'})} returns the ## unique rows of @var{A} by treating each row as a single entity. The ## @qcode{'rows'} option can be used alone or in any combination with the ## @var{setOrder} and @var{occurrence} arguments. @qcode{'rows'} can be ## placed at any position in the function's argument list after the input ## array @var{A}. However, this syntax is only valid for 2-dimensional ## calendarDuration arrays. ## ## @code{[@var{tblB}, @var{ixA}, @var{ixB}] = unique (@dots{})} also returns ## index vectors @var{ixA} and @var{ixB} using any of the previous syntaxes. ## @var{ixA} and @var{ixB} map the arrays @var{A} and @var{B} to one another ## such that @qcode{@var{B} = @var{A}(@var{ixA})} and ## @qcode{@var{A} = @var{B}(@var{ixB})}. When the @qcode{'rows'} optional ## argument is specified, then @qcode{@var{B} = @var{A}(@var{ixA},:)} and ## @qcode{@var{tblA} = @var{tblB}(@var{ixB},:)}. ## ## @end deftypefn function [B, ixA, ixB] = unique (A, varargin) ## 'legacy' option is not supported if (any (strcmp ("legacy", varargin))) error ("calendarDuration.unique: 'legacy' option is not supported."); endif ## Handle each property array separately [~, ~, Midx] = __unique__ (A.Months, varargin{:}); [~, ~, Didx] = __unique__ (A.Days, varargin{:}); [~, ~, Tidx] = unique (A.Time, varargin{:}); ## Use indices to find unique calendarDuration values if (any (strcmp ('rows', varargin))) [~, ixA, ixB] = __unique__ ([Midx, Didx, Tidx], varargin{:}); if (any (strcmp ('last', varargin))) [~, ixA, ~] = __unique__ (ixB, 'last'); endif B = subset (A, ixA, ':'); else [~, ixA, ixB] = __unique__ ([Midx, Didx, Tidx], 'rows', varargin{:}); if (any (strcmp ('last', varargin))) [~, ixA, ~] = __unique__ (ixB, 'last'); endif B = subset (A, ixA); endif endfunction endmethods ################################################################################ ## ** Array Operations ** ## ################################################################################ ## Available Methods ## ## ## ## 'cat' 'horzcat' 'vertcat' 'repmat' ## ## 'repelem' 'repelems' 'reshape' 'circshift' ## ## 'permute' 'ipermute' 'transpose' 'ctranspose' ## ## ## ################################################################################ methods (Access = public) ## -*- texinfo -*- ## @deftypefn {calendarDuration} {@var{C} =} cat (@var{dim}, @var{A}, @var{B}, @dots{}) ## ## Concatenate calendarDuration arrays. ## ## @code{@var{C} = cat (@var{dim}, @var{A}, @var{B}, @dots{})} concatenates ## calendarDuration arrays @var{A}, @var{B}, @dots{} along dimension ## @var{dim}. All input arrays must have the same size except along the ## operating dimension @var{dim}. Any of the input arrays may also be ## string arrays or cell arrays of character vectors of compatible size. ## Additionally, an input can be a numeric matrix, which when parsed to the ## constructor will return a calendarDuration array of compatible size. ## ## @end deftypefn function out = cat (dim, varargin) args = varargin; [args{:}] = promote (varargin{:}); out = args{1}; fieldArgs = cellfun (@(obj) obj.Months, args, 'UniformOutput', false); out.Months = cat (dim, fieldArgs{:}); fieldArgs = cellfun (@(obj) obj.Days, args, 'UniformOutput', false); out.Days = cat (dim, fieldArgs{:}); fieldArgs = cellfun (@(obj) obj.Time, args, 'UniformOutput', false); out.Time = cat (dim, fieldArgs{:}); out = broadcastProperties (out); endfunction ## -*- texinfo -*- ## @deftypefn {calendarDuration} {@var{C} =} horzcat (@var{A}, @var{B}, @dots{}) ## ## Horizontal concatenation of calendarDuration arrays. ## ## @code{@var{C} = horzcat (@var{A}, @var{B}, @dots{}} is the equivalent of ## the syntax @code{@var{B} = [@var{A}, @var{B}, @dots{}]} and horizontally ## concatenates the calendarDuration arrays @var{A}, @var{B}, @dots{}. All ## input arrays must have the same size except along the second dimension. ## Any of the input arrays may also be string arrays or cell arrays of ## character vectors of compatible size. Additionally, an input can be a ## numeric matrix, which when parsed to the constructor will return a ## calendarDuration array of compatible size. ## ## @end deftypefn function out = horzcat (varargin) out = cat (2, varargin{:}); endfunction ## -*- texinfo -*- ## @deftypefn {calendarDuration} {@var{C} =} vertcat (@var{A}, @var{B}, @dots{}) ## ## Vertical concatenation of calendarDuration arrays. ## ## @code{@var{C} = vertcat (@var{A}, @var{B}, @dots{}} is the equivalent of ## the syntax @code{@var{B} = [@var{A}; @var{B}; @dots{}]} and vertically ## concatenates the calendarDuration arrays @var{A}, @var{B}, @dots{}. All ## input arrays must have the same size except along the first dimension. ## Any of the input arrays may also be string arrays or cell arrays of ## character vectors of compatible size. Additionally, an input can be a ## numeric matrix, which when parsed to the constructor will return a ## calendarDuration array of compatible size. ## ## @end deftypefn function out = vertcat (varargin) out = cat (1, varargin{:}); endfunction ## -*- texinfo -*- ## @deftypefn {calendarDuration} {@var{B} =} repmat (@var{A}, @var{n}) ## @deftypefnx {calendarDuration} {@var{B} =} repmat (@var{A}, @var{d1}, @dots{}, @var{dN}) ## @deftypefnx {calendarDuration} {@var{B} =} repmat (@var{A}, @var{dimvec}) ## ## Repeat copies of a calendarDuration array. ## ## @code{@var{B} = repmat (@var{A}, @var{n})} returns a calendarDuration ## array @var{B} containing @var{n} copies of the input calendarDuration ## array @var{A} along every dimension of @var{A}. ## ## @code{@var{B} = repmat (@var{A}, @var{d1}, @dots{}, @var{dN})} returns an ## array @var{B} containing copies of @var{A} along the dimensions specified ## by the list of scalar integer values @var{d1}, @dots{}, @var{dN}, which ## specify how many copies of @var{A} are made in each dimension. ## ## @code{@var{B} = repmat (@var{A}, @var{dimvec})} is equivalent to the ## previous syntax with @code{@var{dimvec} = [@var{d1}, @dots{}, @var{dN}]}. ## ## @end deftypefn function this = repmat (this, varargin) this.Months = repmat (this.Months, varargin{:}); this.Days = repmat (this.Days, varargin{:}); this.Time = repmat (this.Time, varargin{:}); endfunction ## -*- texinfo -*- ## @deftypefn {calendarDuration} {@var{B} =} repelem (@var{A}, @var{n}) ## @deftypefnx {calendarDuration} {@var{B} =} repelem (@var{A}, @var{d1}, @dots{}, @var{dN}) ## ## Repeat copies of calendarDuration array elements. ## ## @code{@var{B} = repelem (@var{A}, @var{n})} returns a calendarDuration ## vector @var{B} containing repeated elements of the input @var{A}, which ## must be a calendarDuration vector. If @var{n} is a scalar, each element ## of @var{A} is repeated @var{n} times along the non-singleton dimension of ## @var{A}. If @var{n} is a vector, it must have the same elements as ## @var{A}, in which case it specifies the number of times to repeat each ## corresponding element of @var{A}. ## ## @code{@var{B} = repelem (@var{A}, @var{d1}, @dots{}, @var{dN})} returns ## an array @var{B} with each element of @var{A} repeated according to the ## the list of input arguments @code{@var{d1}, @dots{}, @var{dN}} each ## corresponding to a different dimension @code{1:ndims (@var{A})} of the ## input array @var{A}. @var{d1}, @dots{}, @var{dN} must be either scalars ## or vectors with the same length as the corresponding dimension of ## @var{A} containing non-negative integer values specifying the number of ## repetitions of each element along the corresponding dimension. ## ## @end deftypefn function this = repelem (this, varargin) this.Months = repelem (this.Months, varargin{:}); this.Days = repelem (this.Days, varargin{:}); this.Time = repelem (this.Time, varargin{:}); endfunction ## -*- texinfo -*- ## @deftypefn {calendarDuration} {@var{B} =} repelems (@var{A}, @var{R}) ## ## Construct a vector of repeated elements from a calendarDuration array. ## ## @code{@var{B} = repelems (@var{A}, @var{R})} returns a calendarDuration ## vector @var{B} containing repeated elements of the input @var{A}, which ## must be a calendarDuration vector. @var{R} must be a @math{2*N} matrix ## of integers. Entries in the first row of @var{R} correspond to the ## linear indexing of the elements in @var{A} to be repeated. The ## corresponding entries in the second row of @var{R} specify the repeat ## count of each element. ## ## @end deftypefn function this = repelems (this, R) this.Months = repelems (this.Months, R); this.Days = repelems (this.Days, R); this.Time = repelems (this.Time, R); endfunction ## -*- texinfo -*- ## @deftypefn {calendarDuration} {@var{B} =} reshape (@var{A}, @var{d1}, @dots{}, @var{dN}) ## @deftypefnx {calendarDuration} {@var{B} =} reshape (@var{A}, @dots{}, @qcode{[]}, @dots{}) ## @deftypefnx {calendarDuration} {@var{B} =} reshape (@var{A}, @var{dimvec}) ## ## Reshape calendarDuration array. ## ## @code{@var{B} = reshape (@var{A}, @var{d1}, @dots{}, @var{dN})} returns a ## calendarDuration array @var{B} with specified dimensions @var{d1}, ## @dots{}, @var{dN}, whose elements are taken columnwise from the ## calendarDuration array @var{A}. The product of @var{d1}, @dots{}, ## @var{dN} must equal the total number of elements in @var{A}. ## ## @code{@var{B} = reshape (@var{A}, @dots{}, @qcode{[]}, @dots{})} returns ## a calendarDuration array @var{B} with one dimension unspecified which is ## calculated automatically so that the product of dimensions in @var{B} ## matches the total elements in @var{A}, which must be divisible the ## product of specified dimensions. An empty matrix @qcode{([])} is used to ## flag the unspecified dimension. ## ## @end deftypefn function this = reshape (this, varargin) this.Months = reshape (this.Months, varargin{:}); this.Days = reshape (this.Days, varargin{:}); this.Time = reshape (this.Time, varargin{:}); endfunction ## -*- texinfo -*- ## @deftypefn {calendarDuration} {@var{B} =} circshift (@var{A}, @var{n}) ## @deftypefnx {calendarDuration} {@var{B} =} circshift (@var{A}, @var{n}, @var{dim}) ## ## Circularly shift the elements in a calendarDuration array. ## ## @code{@var{B} = circshift (@var{A}, @var{n})} circularly shifts the ## elements of the calendarDuration array @var{A} according to @var{n}. If ## @var{n} is a nonzero integer scalar, then the elements of @var{A} are ## shifted by @var{n} elements along the first non-singleton dimension of ## @var{A}. If @var{n} is a vector, it must not be longer that the number ## of dimensions of @var{A} with each value of @var{n} corresponding to a ## dimension in @var{A}. The sign of the value(s) in @var{n} specify the ## direction in the elements of @var{A} are shifted. ## ## @code{@var{B} = circshift (@var{A}, @var{n}, @var{dim})} circularly ## shifts the elements of the calendarDuration array @var{A} along the ## dimension specified by @var{dim}. In this case, @var{n} must be a scalar ## integer value. ## ## @end deftypefn function this = circshift (this, varargin) this.Months = circshift (this.Months, varargin{:}); this.Days = circshift (this.Days, varargin{:}); this.Time = circshift (this.Time, varargin{:}); endfunction ## -*- texinfo -*- ## @deftypefn {calendarDuration} {@var{B} =} permute (@var{A}, @var{dims}) ## ## Generalized transpose for a calendarDuration N-D array. ## ## @code{@var{B} = permute (@var{A}, @var{dims})} returns the generalized ## transpose of the calendarDuration array @var{A} by rearranging its ## dimensions according to the permutation vector specified in @var{dims}. ## ## @var{dims} must index all the dimensions @code{1:ndims (@var{A})} of the ## input array @var{A}, in any order, but only once. The @var{N}th ## dimension of @var{A} gets remapped to the dimension in @var{B} specified ## by @code{@var{dims}(@var{N})}. ## ## @end deftypefn function this = permute (this, order) this.Months = permute (this.Months, order); this.Days = permute (this.Days, order); this.Time = permute (this.Time, order); endfunction ## -*- texinfo -*- ## @deftypefn {calendarDuration} {@var{A} =} ipermute (@var{B}, @var{dims}) ## ## Inverse of the generalized transpose for a calendarDuration N-D array. ## ## @code{@var{A} = ipermute (@var{B}, @var{dims})} returns the inverse of ## the generalized transpose performed by the @code{permute} function. The ## expression @code{ipermute (permute (@var{A}, @var{dims}), @var{dims})} ## returns the original array @var{A}. ## ## @var{dims} must index all the dimensions @code{1:ndims (@var{B})} of the ## input array @var{B}, in any order, but only once. The dimension of ## @var{B} specified in @code{@var{dims}(@var{N})} gets remapped to the ## @var{N}th dimension of @var{A}. ## ## @end deftypefn function this = ipermute (this, order) this.Months = ipermute (this.Months, order); this.Days = ipermute (this.Days, order); this.Time = ipermute (this.Time, order); endfunction ## -*- texinfo -*- ## @deftypefn {calendarDuration} {@var{B} =} transpose (@var{A}) ## ## Transpose a calendarDuration matrix. ## ## @code{@var{B} = transpose (@var{A})} is the equivalent of the syntax ## @code{@var{B} = @var{A}.'} and returns the transpose of the ## calendarDuration matrix @var{A}. ## ## @end deftypefn function this = transpose (this) this.Months = transpose (this.Months); this.Days = transpose (this.Days); this.Time = transpose (this.Time); endfunction ## -*- texinfo -*- ## @deftypefn {calendarDuration} {@var{B} =} ctranspose (@var{A}) ## ## Transpose a calendarDuration matrix. ## ## @code{@var{B} = ctranspose (@var{A})} is the equivalent of the syntax ## @code{@var{B} = @var{A}'} and returns the transpose of the ## calendarDuration matrix @var{A}. For calendarDuration arrays, ## @code{ctranspose} is identical to @code{transpose}. ## ## @end deftypefn function this = ctranspose (this) this.Months = ctranspose (this.Months); this.Days = ctranspose (this.Days); this.Time = ctranspose (this.Time); endfunction endmethods ################################################################################ ## ** Reference and Assignment Operations ** ## ################################################################################ ## Available Methods ## ## ## ## 'end' 'subsref' 'subsasgn' ## ## ## ################################################################################ methods (Hidden) ## Overloaded end keyword function last_index = end (this, end_dim, ndim_obj) lastdim = ndims (this); if (end_dim == ndim_obj && ndim_obj == 1) last_index = prod (size (this)); elseif (end_dim == ndim_obj && end_dim < lastdim) last_index = prod (size (this)(end_dim:lastdim)); else last_index = size (this, end_dim); endif endfunction ## Class specific subscripted reference function varargout = subsref (this, s) chain_s = s(2:end); s = s(1); switch (s.type) case '()' out = this; out.Months = this.Months(s.subs{:}); out.Days = this.Days(s.subs{:}); out.Time = this.Time(s.subs{:}); case '{}' error (strcat ("calendarDuration.subsref: '{}' invalid indexing", ... " for referencing values. Use '()' instead.")); case '.' if (! ischar (s.subs)) error (strcat ("calendarDuration.subsref: '.' index", ... " argument must be a character vector.")); endif switch (s.subs) case 'proxyArray' # used by 'table' class out = proxyArray (this); case 'Format' out = this.Format; otherwise error (strcat ("calendarDuration.subsref: unrecognized", ... " property: '%s'"), s.subs); endswitch endswitch ## Chained references if (! isempty (chain_s)) out = subsref (out, chain_s); endif varargout{1} = out; endfunction ## Class specific subscripted assignment function this = subsasgn (this, s, val) if (numel (s) > 1) error ("calendarDuration.subsasgn: chained subscripts not allowed."); endif switch s.type case '()' if (isempty (val)) this.Months(s.subs{:}) = []; this.Days(s.subs{:}) = []; this.Time(s.subs{:}) = []; return; elseif (isnumeric (val)) tmp = zeros (size (val)); this.Months(s.subs{:}) = tmp; this.Days(s.subs{:}) = tmp; this.Time(s.subs{:}) = duration (24 * double (val), 0, 0); this = broadcastProperties (this); elseif (isa (val, "calendarDuration")) this.Months(s.subs{:}) = val.Months; this.Days(s.subs{:}) = val.Days; this.Time(s.subs{:}) = val.Time; this = broadcastProperties (this); elseif (isa (val, "duration")) this.Months(s.subs{:}) = 0; this.Days(s.subs{:}) = 0; this.Time(s.subs{:}) = val; this = broadcastProperties (this); else error (strcat ("calendarDuration.subsasgn: assignment value", ... " must be calendarDuration or duration array", ... " or a numeric array representing 24-hour days.")); endif case '{}' error (strcat ("calendarDuration.subsasgn: '{}' invalid indexing", ... " for assigning values. Use '()' instead.")); case '.' if (! ischar (s.subs)) error (strcat ("calendarDuration.subsasgn: '.' index", ... " argument must be a character vector.")); endif switch (s.subs) case 'Format' errmsg = checkFormatString (val); if (! isempty (errmsg)) error ("calendarDuration.subsasgn: 'Format' %s", errmsg); endif this.Format = val; otherwise error (strcat ("calendarDuration.subsasgn: unrecognized", ... " property: '%s'"), s.subs); endswitch endswitch endfunction endmethods methods (Access = private) ## Return a subset of the array function this = subset (this, varargin) this = this; this.Months = this.Months(varargin{:}); this.Days = this.Days(varargin{:}); this.Time = this.Time(varargin{:}); endfunction ## Broadcast properties function this = broadcastProperties (this) ## Handle NaNs and Infs first is_nan = isnan (this.Months) | isnan (this.Days) | isnan (this.Time); isPinf = Inf == this.Months | Inf == this.Days | Inf == this.Time; isNinf = -Inf == this.Months | -Inf == this.Days | -Inf == this.Time; if (any (is_nan, 'all') || any (isPinf & isNinf, 'all')) is_nan = is_nan | (isPinf & isNinf); isPinf = isPinf & ! is_nan; isNinf = isNinf & ! is_nan; endif ## Broadcast NaNs this.Months(is_nan) = NaN; this.Days(is_nan) = NaN; this.Time(is_nan) = duration ([NaN, NaN, NaN]); ## Broadcast Infs if (any (isPinf, 'all')) this.Months(isPinf) = Inf; this.Days(isPinf) = Inf; this.Time(isPinf) = duration ([Inf, Inf, Inf]); endif if (any (isNinf, 'all')) this.Months(isNinf) = -Inf; this.Days(isNinf) = -Inf; this.Time(isNinf) = duration ([-Inf, -Inf, -Inf]); endif endfunction ## Promote numeric arrays to calendarDuration objects function varargout = promote (varargin) for i = 1:numel (varargin) if (isa (varargin{i}, "calendarDuration")) varargout{i} = varargin{i}; elseif (isa (varargin{i}, "duration")) varargout{i} = calendarDuration (0, 0, 0, varargin{i}); elseif (isnumeric (varargin{i})) if (isempty (varargin{i})) varargout{i} = calendarDuration ([], [], []); else varargout{i} = calendarDuration (0, 0, 0, 24 * varargin{i}, 0, 0); endif else error ("calendarDuration: invalid input to constructor."); endif endfor endfunction ## Create a proxy array for sorting and set operations in tables function out = proxyArray (this) ## Handle shape (for multicolumn calendarDuration matrix) [rows, cols] = size (this); if (cols > 1) out = []; for i = 1:cols dt = hours (this.Time(:,i)); SC = [this.Months(:,i), this.Days(:,i), dt]; out = [out, SC]; endfor else dt = hours (this.Time); out = [this.Months, this.Days, dt]; endif endfunction endmethods endclassdef ## Check 'Format' string function errmsg = checkFormatString (Format) ## Check for character vector if (! (ischar (Format) && isvector (Format))) errmsg = "must be a character vector."; return; endif errmsg = ''; sf = @(x) strfind (Format, x); sp = cellfun (sf, {'y','q','m','w','d','t'}, 'UniformOutput', false); ## Check for duplicate characters if (any (cellfun (@(x) numel (x) > 1, sp))) errmsg = "contains duplicate characters."; ## Check for 'm', 'd', and 't' being present elseif (any (cellfun (@isempty, sp([3,5,6])))) errmsg = "must contain 'm', 'd', and 't'."; ## Check order elseif (any (diff (cell2mat (sp)) < 1)) errmsg = "has invalid order of characters."; endif endfunction pr0m1th3as-datatypes-9c9a8d3/inst/calmonths.m000066400000000000000000000050561522766574100212060ustar00rootroot00000000000000## Copyright (C) 2024-2026 Andreas Bertsatos ## ## This file is part of the datatypes package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {datatypes} {@var{calD} =} calmonths (@var{X}) ## ## Calendar duration in months. ## ## @code{@var{calD} = calmonths (@var{X})} returns a @qcode{calendarDuration} ## array representing calendar months equivalent to the values in @var{X}, which ## must be a numeric array of integer values. ## ## @code{calmonths} is also available as a method for @qcode{calendarDuration} ## arrays, in which case it performs the opposite conversion. ## ## @seealso{calendarDuration, calyears, calquarters, calweeks, caldays, ## calendarDuration.calmonths} ## @end deftypefn function out = calmonths (x) ## Check input if (nargin == 0) x = 1; elseif (! isnumeric (x)) error ("calmonths: input array must be numeric."); elseif (! isreal (x)) error ("calmonths: input array must be real."); endif xx = x; xx(isnan (x)) = 0; if (any (fix (xx(:)) != xx(:))) error ("calmonths: input array must contain only integer values."); endif out = calendarDuration (0, double (x), 0); endfunction %!demo %! ## `calmonths` builds a calendar duration of whole months. Twelve or more %! ## months normalise into years on display. %! %! calmonths (5) %! calmonths (14) %!test %! X = magic (3); %! D = calmonths (X); %! assert_equal (size (D), size (X)); %!test %! D = calmonths ([1, 2, 3]); %! assert_equal (calmonths (D), [1, 2, 3]); %!test %! D = calmonths ([1, 2, NaN, 4]); %! assert_equal (calmonths (D), [1, 2, NaN, 4]); %!test %! D = calmonths (int16 (1)); %! assert_equal (calmonths (D), 1); %!test %! D = calmonths (); %! assert_equal (calmonths (D), 1); %!error calmonths ("asd"); %!error calmonths (1+i); %!error ... %! calmonths (1.2); pr0m1th3as-datatypes-9c9a8d3/inst/calquarters.m000066400000000000000000000052601522766574100215410ustar00rootroot00000000000000## Copyright (C) 2024-2026 Andreas Bertsatos ## ## This file is part of the datatypes package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {datatypes} {@var{calD} =} calquarters (@var{X}) ## ## Calendar duration in quarters. ## ## @code{@var{calD} = calquarters (@var{X})} returns a @qcode{calendarDuration} ## array representing calendar quarters equivalent to the values in @var{X}, ## which must be a numeric array of integer values. ## ## @code{calquarters} is also available as a method for @qcode{calendarDuration} ## arrays, in which case it performs the opposite conversion. ## ## @seealso{calendarDuration, calyears, calmonths, calweeks, caldays, ## calendarDuration.calquarters} ## @end deftypefn function out = calquarters (x) ## Check input if (nargin == 0) x = 1; elseif (! isnumeric (x)) error ("calquarters: input array must be numeric."); elseif (! isreal (x)) error ("calquarters: input array must be real."); endif xx = x; xx(isnan (x)) = 0; if (any (fix (xx(:)) != xx(:))) error ("calquarters: input array must contain only integer values."); endif out = calendarDuration (0, double (x) * 3, 0, 'Format', 'qmwdt'); endfunction %!demo %! ## `calquarters` builds a calendar duration of whole quarters (each quarter is %! ## three months). %! %! calquarters (3) %! %! ## Quarters combine with the other component builders. %! calquarters (2) + calmonths (1) %!test %! X = magic (3); %! D = calquarters (X); %! assert_equal (size (D), size (X)); %!test %! D = calquarters ([1, 2, 3]); %! assert_equal (calquarters (D), [1, 2, 3]); %!test %! D = calquarters ([1, 2, NaN, 4]); %! assert_equal (calquarters (D), [1, 2, NaN, 4]); %!test %! D = calquarters (int16 (1)); %! assert_equal (calquarters (D), 1); %!test %! D = calquarters (); %! assert_equal (calquarters (D), 1); %!error calquarters ("asd"); %!error calquarters (1+i); %!error ... %! calquarters (1.2); pr0m1th3as-datatypes-9c9a8d3/inst/calweeks.m000066400000000000000000000051061522766574100210100ustar00rootroot00000000000000## Copyright (C) 2024-2026 Andreas Bertsatos ## ## This file is part of the datatypes package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {datatypes} {@var{calD} =} calweeks (@var{X}) ## ## Calendar duration in weeks. ## ## @code{@var{calD} = calweeks (@var{X})} returns a @qcode{calendarDuration} ## array representing calendar weeks equivalent to the values in @var{X}, which ## must be a numeric array of integer values. ## ## @code{calweeks} is also available as a method for @qcode{calendarDuration} ## arrays, in which case it performs the opposite conversion. ## ## @seealso{calendarDuration, calyears, calquarters, calmonths, caldays, ## calendarDuration.calweeks} ## @end deftypefn function out = calweeks (x) ## Check input if (nargin == 0) x = 1; elseif (! isnumeric (x)) error ("calweeks: input array must be numeric."); elseif (! isreal (x)) error ("calweeks: input array must be real."); endif xx = x; xx(isnan (x)) = 0; if (any (fix (xx(:)) != xx(:))) error ("calweeks: input array must contain only integer values."); endif out = calendarDuration (0, 0, double (x) * 7, 'Format', 'ymwdt'); endfunction %!demo %! ## `calweeks` builds a calendar duration of whole weeks (each week is 7 days). %! %! calweeks (3) %! %! ## Combine the component builders by adding them. %! calweeks (2) + caldays (3) %!test %! X = magic (3); %! D = calweeks (X); %! assert_equal (size (D), size (X)); %!test %! D = calweeks ([1, 2, 3]); %! assert_equal (calweeks (D), [1, 2, 3]); %!test %! D = calweeks ([1, 2, NaN, 4]); %! assert_equal (calweeks (D), [1, 2, NaN, 4]); %!test %! D = calweeks (int16 (1)); %! assert_equal (calweeks (D), 1); %!test %! D = calweeks (); %! assert_equal (calweeks (D), 1); %!error calweeks ("asd"); %!error calweeks (1+i); %!error ... %! calweeks (1.2); pr0m1th3as-datatypes-9c9a8d3/inst/calyears.m000066400000000000000000000050661522766574100210220ustar00rootroot00000000000000## Copyright (C) 2024-2026 Andreas Bertsatos ## ## This file is part of the datatypes package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {datatypes} {@var{calD} =} calyears (@var{X}) ## ## Calendar duration in years. ## ## @code{@var{calD} = calyears (@var{X})} returns a @qcode{calendarDuration} ## array representing calendar years equivalent to the values in @var{X}, which ## must be a numeric array of integer values. ## ## @code{calyears} is also available as a method for @qcode{calendarDuration} ## arrays, in which case it performs the opposite conversion. ## ## @seealso{calendarDuration, calquarters, calmonths, calweeks, caldays, ## calendarDuration.calyears} ## @end deftypefn function out = calyears (x) ## Check input if (nargin == 0) x = 1; elseif (! isnumeric (x)) error ("calyears: input array must be numeric."); elseif (! isreal (x)) error ("calyears: input array must be real."); endif xx = x; xx(isnan (x)) = 0; if (any (fix (xx(:)) != xx(:))) error ("calyears: input array must contain only integer values."); endif out = calendarDuration (double (x), 0, 0); endfunction %!demo %! ## `calyears` builds a calendar duration of whole years. Add the component %! ## builders together to assemble a full span. %! %! calyears (2) %! calyears (1) + calmonths (6) + caldays (15) %!test %! X = magic (3); %! D = calyears (X); %! assert_equal (size (D), size (X)); %!test %! D = calyears ([1, 2, 3]); %! assert_equal (calyears (D), [1, 2, 3]); %!test %! D = calyears ([1, 2, NaN, 4]); %! assert_equal (calyears (D), [1, 2, NaN, 4]); %!test %! D = calyears (int16 (1)); %! assert_equal (calyears (D), 1); %!test %! D = calyears (); %! assert_equal (calyears (D), 1); %!error calyears ("asd"); %!error calyears (1+i); %!error ... %! calyears (1.2); pr0m1th3as-datatypes-9c9a8d3/inst/categorical.m000066400000000000000000006230011522766574100214670ustar00rootroot00000000000000## Copyright (C) 2024-2026 Andreas Bertsatos ## ## This file is part of the datatypes package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . classdef categorical ## -*- texinfo -*- ## @deftp {datatypes} categorical ## ## Array representing categorical data. ## ## A @code{categorical} array represents an array of values that correspond ## to a finite set of discrete categories, which can be either ordinal (having ## a mathematical ordering) or nominal. It is an efficient way to define ## groups of rows in a table or to other types of variables. ## ## Each @code{categorical} array stores the list of categories as a cell array ## of character vectors and a numeric array of @qcode{uint16} type as indices ## to the categories. The categorical array may also store elements of ## undefined categorical values, which represent the absence of a given ## category and correspond to the @qcode{NaN} value for numeric arrays or in ## general to the missing value for other data types. ## ## @code{categorical} arrays do not have any public properties, which can be ## indexed by using dot notation similarly to structures. However, there are ## several methods which can be used to modify their categories once they are ## constructed. ## ## @end deftp properties (SetAccess = private, Hidden) ## Category Names cats = {} ## Indices to categories code = uint16 ([]) ## elements isMissing = logical ([]) ## Flag for ordinal categories isOrdinal = false ## Flag for protecting category list isProtected = false endproperties methods (Hidden) ## Custom display function display (this) in_name = inputname (1); if (! isempty (in_name)) fprintf ('%s =\n', in_name); endif __disp__ (this, 'categorical', in_name); endfunction ## Custom display function disp (this) __disp__ (this, 'categorical'); endfunction endmethods ################################################################################ ## ** Create and convert 'categorical' type ** ## ################################################################################ ## Available Methods ## ## ## ## 'categorical' 'dispstrings' 'cellstr' 'char' ## ## 'double' 'single' 'int64' 'int32' ## ## 'int16' 'int8' 'uint64' 'uint32' ## ## 'uint16' 'uint8' ## ## ## ################################################################################ methods (Access = public) ## -*- texinfo -*- ## @deftypefn {categorical} {@var{C} =} categorical (@var{A}) ## @deftypefnx {categorical} {@var{C} =} categorical (@var{A}, @var{valueset}) ## @deftypefnx {categorical} {@var{C} =} categorical (@var{A}, @var{valueset}, @var{catnames}) ## @deftypefnx {categorical} {@var{C} =} categorical (@dots{}, @var{Name}, @var{Value}) ## ## Create a new array of categorical values. ## ## @code{@var{C} = categorical (@var{A})} creates a categorical array ## @var{C} from the input array @var{A}, which can be numeric, logical, ## datetime, duration, string, or cell array of character vectors. Input ## @var{A} can also be another categorical array. The categories in @var{C} ## the sorted unique values from the input array @var{A}. When the input ## array is string or cell array of character vectors, any leading or ## trailing white spaces are removed. Missing values in the input array ## correspond to @qcode{} elements in the created categorical ## array. By default, there is no category for undefined values in the ## output array. ## ## @code{@var{C} = categorical (@var{A}, @var{valueset})} creates a ## categorical array from input @var{A} with the categories specified in ## @var{valueset}, which must be a vector of unique values. The data type ## of input array @var{A} and @var{valueset} must be the same, unless they ## are string or cell arrays of character vectors, in which case they can be ## used interchangeably. Similarly to input array @var{A} any leading or ## trailing white spaces are removed, if @var{valueset} is a string or cell ## array of character vectors. ## ## @code{@var{C} = categorical (@var{A}, @var{valueset}, @var{catnames})} ## creates a categorical array from input @var{A} with the categories ## specified in @var{valueset} and named after the corresponding values in ## @var{catnames}, which must be specified either as a string array or a ## cell array of character vectors. If omitted, @code{categorical} uses ## the cellstring representation of @var{valueset} to name the specified ## category names. @var{catnames} must not contain any missing values, it ## may have duplicate names, and it must have the same number of elements as ## @var{valueset}. ## ## @code{@var{C} = categorical (@dots{}, @var{Name}, @var{Value})} further ## specifies additional parameters for creating categorical array @var{C}. ## ## @itemize ## @item @qcode{"Ordinal"} must be a logical scalar specifying that the ## categories in @var{C} have a numeric ordering relationship. By default, ## it is @qcode{false} and @code{categorical} creates a non-ordinal array. ## The elements of unordered categorical arrays can only be compared for ## equality. Any other relational operator cannot be used. Setting ## @qcode{"Ordinal"} to @qcode{true} results in a categorical array with ## mathematically ordered categories. The ordering goes from smallest to ## largest according to the order in @var{valueset} or the order of ## appearance in input array @var{A}, when @var{valueset} is not specified, ## in which case the unique values in @var{A} are not sorted in order to set ## the categories. Ordinal categorical arrays allow for relational ## operators such as @qcode{>=, >, <=, <}, as well as statistical operations ## such as @code{min}, @code{max}, and @code{median}. ## ## @item @qcode{"Protected"} must be a logical scalar specifying that the ## categories in @var{C} are protected. By default, it is @qcode{false} for ## unordered categorical arrays and it is always @qcode{true} for ordinal ## categorical arrays. Setting @qcode{"Protected"} to @qcode{true} prevents ## from assigning new values that do not correspond to existing categories. ## When @qcode{false}, assigning new values to the array automatically ## updates the categories. Hence, categorical arrays with different sets of ## categories can be combined/merged into a new array with set operations. ## @end itemize ## ## @seealso{categories, discretize, iscategorical} ## @end deftypefn function this = categorical (x, varargin) ## Return an empty categorical object if (nargin == 0) return; endif ## Parse optional Name-Value paired arguments optNames = {'Ordinal', 'Protected'}; dfValues = {false, false}; [Ordinal, Protected, args] = parsePairedArguments (optNames, dfValues, ... varargin(:)); ## Check optional Name-Value paired arguments if (! ismember (Ordinal, [0, 1])) error (strcat ("categorical: 'Ordinal' variable indicator", ... " must be either false (0) or true (1).")); endif if (! ismember (Protected, [0, 1])) error (strcat ("categorical: 'Protected' categories indicator", ... " must be either false (0) or true (1).")); endif if (Ordinal) this.isOrdinal = true; this.isProtected = true; elseif (Protected) this.isProtected = true; endif ## Function handle for identifying missing text fmt = @(x) isempty (strtrim (x)); ## Handle categorical input first if (isa (x, 'categorical')) this.cats = x.cats; this.code = x.code; this.isMissing = x.isMissing; ## Set new categories if valueset argument is provided if (numel (args) > 0) ## valueset must be a same type as data input (categorical) catvset = args{1}; if (isa (catvset, 'categorical')) isnanvset = catvset.isMissing; codevset = catvset.code; if (sum (isnanvset) > 1) error ("categorical: VALUESET contains multiple missing values."); elseif (numel (unique (codevset)) != numel (codevset)) error ("categorical: VALUESET contains non-unique values."); endif if (any (isnanvset)) if (numel (args) == 1) error (strcat ("categorical: VALUESET with missing value", ... " requires CATNAMES.")); endif ## Keep index of missing values in x isnanx = this.isMissing; ## Get CATNAMES catnames = args{2}; if (! (iscellstr (catnames) || isa (catnames, 'string'))) error ("categorical: invalid type of CATNAMES."); endif catnames = cellstr (catnames); ## codevset must include 0 since there is an undefined element if (numel (codevset) != numel (catnames)) error (strcat ("categorical: CATNAMES and VALUESET", ... " lengths do not match.")); endif if (any (cellfun (fmt, catnames))) error (strcat ("categorical: CATNAMES contain empty or", ... " missing strings.")); endif ## Separate missing from nonmissing catnames valueset = catvset.cats(codevset(! isnanvset)); ## Set nonmissing values this = setcats (this, valueset); ## Find code value for missing catname, add 1 to all code values ## equal to or greater than the missing code value, and convert ## all initial 0 code values to the new missing catname value. nanvalue = find (isnanvset); this.code(this.code >= nanvalue) += 1; this.code(isnanx) = uint16 (nanvalue); this.isMissing(isnanx) = false; this.cats = catnames(:); else valueset = catvset.cats(codevset); this = setcats (this, valueset); ## Set new category names if catname argument is provided if (numel (args) > 1) catnames = args{2}; if (! (iscellstr (catnames) || isa (catnames, 'string'))) error ("categorical: invalid type of CATNAMES."); endif catnames = cellstr (catnames); if (numel (valueset) != numel (catnames)) error (strcat ("categorical: CATNAMES and VALUESET", ... " lengths do not match.")); endif if (any (cellfun (fmt, catnames))) error (strcat ("categorical: CATNAMES contain empty or", ... " missing strings.")); endif this.cats = catnames(:); endif endif ## In case of duplicate catnames, regrouping is required if (numel (args) > 1 && ... numel (unique (catnames)) != numel (catnames)) ## Generate regrouping index to unique categories index2cat = __grp2idx__ (catnames); idx = ! this.isMissing; this.code(idx) = arrayfun (@(x) index2cat(x), this.code(idx)); this.cats = unique (this.cats, 'stable'); endif else error ("categorical: incompatible types of VALUESET and X."); endif endif return; endif ## Handle input arguments index2cat = []; isnanvset = false; ## Input Array (x) if (iscellstr (x)) classx = 'cellstr'; isnanx = cellfun (fmt, x); elseif (isnumeric (x)) classx = 'numeric'; isnanx = isnan (x); elseif (isa (x, 'datetime')) classx = 'datetime'; isnanx = isnat (x); elseif (isa (x, 'duration')) classx = 'duration'; isnanx = isnan (x); elseif (islogical (x)) classx = 'logical'; isnanx = isnan (x); elseif (isa (x, 'string')) classx = 'string'; isnanx = cellfun (fmt, cellstr (x)); else error ("categorical: invalid input type for X."); endif ## Force isnanx to logical array in case of empty text input isnanx = logical (isnanx); ## Categories (valueset) if (numel (args) > 0) valueset = args{1}; if (iscellstr (valueset)) classv = 'cellstr'; elseif (isnumeric (valueset)) classv = 'numeric'; else classv = class (valueset); endif if (any (strcmp (classx, {'cellstr', 'string'}))) if (! (iscellstr (valueset) || isa (valueset, 'string'))) error ("categorical: incompatible types of VALUESET and X."); endif if (iscellstr (valueset)) isnanvset = cellfun (fmt, valueset); else # string isnanvset = cellfun (fmt, cellstr (valueset)); endif elseif (isequal (classx, classv)) if (strcmp (classv, 'datetime')) isnanvset = isnat (valueset); else # numeric, duration, isnanvset = isnan (valueset); endif else error ("categorical: incompatible types of VALUESET and X."); endif if (any (isnanvset) && numel (args) == 1) error ("categorical: VALUESET with missing value requires CATNAMES."); endif if (sum (isnanvset) > 1) error ("categorical: VALUESET contains multiple missing values."); endif if (prod (size (unique (valueset))) < prod (size (valueset))) error ("categorical: VALUESET contains non-unique values."); endif ## Category names (catnames) if (numel (args) > 1) catnames = args{2}; if (! (iscellstr (catnames) || isa (catnames, 'string'))) error ("categorical: invalid type of CATNAMES."); endif catnames = cellstr (catnames); if (prod (size (valueset)) != numel (catnames)) error ("categorical: CATNAMES and VALUESET lengths do not match."); endif if (any (cellfun (@(x) isempty (strtrim (x)), catnames))) error ("categorical: CATNAMES contain empty or missing strings."); endif ## Create index vector to categories index2cat = __grp2idx__ (catnames); else ## Check valueset for missing or empty elements if (any (strcmp (classv, {'cellstr', 'string'}))) valueset = cellstr (valueset); catnames = valueset; elseif (isnumeric (valueset)) catnames = arrayfun (@num2str, valueset, "UniformOutput", false); elseif (strcmp (classv, 'logical')) if (all (valueset)) catnames = {'true'}; elseif (! any (valueset)) catnames = {'false'}; else catnames = {'false'; 'true'}; endif else # datetime, duration catnames = dispstrings (valueset); endif endif else ## Resolve valueset and catnames from input array if (strcmp (classx, 'numeric')) valueset = unique (x); valueset = valueset(! isnan (valueset)); catnames = arrayfun (@num2str, valueset, "UniformOutput", false); elseif (strcmp (classx, 'logical')) valueset = unique (x); if (all (valueset)) catnames = {'true'}; elseif (! any (valueset)) catnames = {'false'}; else catnames = {'false'; 'true'}; endif elseif (strcmp (classx, 'cellstr')) valueset = unique (x); # does not remove empty valueset = valueset(! cellfun (fmt, valueset)); catnames = valueset; elseif (strcmp (classx, 'string')) valueset = unique (x); # removes missing, but not empty valueset = valueset(! cellfun (fmt, cellstr (valueset))); catnames = cellstr (valueset); elseif (strcmp (classx, 'datetime')) valueset = unique (x); # does not remove NaT valueset = valueset(! isnat (valueset)); catnames = dispstrings (valueset); elseif (strcmp (classx, 'duration')) valueset = unique (x); # does not remove NaN valueset = valueset(! ismissing (valueset)); catnames = dispstrings (valueset); endif endif ## Associate input array with valueset [tf, loc] = ismember (x, valueset); maxc = intmax ('uint16'); maxl = max (loc(:)) + sum (isnanvset); if (maxl > maxc) error (strcat ("categorical: too many categories; categorical", ... " supports up to %d categories; this input has", ... " %d."), maxc, maxl); endif ## If missing value in valueset is associated with a category name ## remove missing values in X from 'tf' and assign the index of the ## valueset that corresponds to the missing value to 'loc' if (any (isnanvset)) tf = tf | isnanx; loc(find (isnanx)) = find (isnanvset); endif ## Reassociate to user defined categories (only when regrouping required) if (! isempty (index2cat)) if (numel (index2cat) != unique (index2cat) || any (isnanvset)) valueset = 1:prod (size (valueset)); fcn = @ (x) index2cat(find (valueset == x)); loc(tf) = arrayfun (fcn, loc(tf)); endif endif ## Add constructor properties this.code = uint16 (loc); this.isMissing = ! tf; if (isempty (catnames)) this.cats = {}; else this.cats = unique (catnames(:), 'stable'); endif endfunction ## -*- texinfo -*- ## @deftypefn {categorical} {@var{cstr} =} dispstrings (@var{C}) ## ## Get display formatted strings for each element of a categorical array. ## ## @code{@var{cstr} = dispstrings (@var{C})} returns a cellstr array of ## character vectors, @var{cstr}, which has the same size as the input ## categorical @var{C}. ## ## @end deftypefn function cstr = dispstrings (this) cstr = cell (size (this)); ix = this.code(! this.isMissing); cstr(! this.isMissing) = this.cats(ix); cstr(this.isMissing) = {''}; endfunction ## -*- texinfo -*- ## @deftypefn {categorical} {@var{cstr} =} cellstr (@var{C}) ## ## Convert categorical array to a cell array of character vectors. ## ## @code{@var{cstr} = cellstr (@var{C})} returns a cellstr array of ## character vectors, @var{cstr}, which has the same size as the input ## categorical @var{C}. ## ## @end deftypefn function cstr = cellstr (this) cstr = dispstrings (this); endfunction ## -*- texinfo -*- ## @deftypefn {categorical} {@var{CM} =} char (@var{C}) ## ## Convert categorical array to a 2-D character matrix. ## ## @code{@var{CM} = char (@var{C})} returns a character matrix @var{CM}, ## which contains @code{numel (@var{C})} rows and each row contains the ## category name for the corresponding element of @code{@var{C}(:)}. ## ## @end deftypefn function CM = char (this) cstr = dispstrings (this); CM = char (cstr(:)); endfunction ## -*- texinfo -*- ## @deftypefn {categorical} {@var{out} =} double (@var{C}) ## ## Convert categorical array to a double array. ## ## @code{@var{out} = double (@var{C})} returns a double array indexing the ## categories in @var{C}. Categorical elements of undefined category are ## returned as @code{NaN}. ## ## @end deftypefn function out = double (this) out = double (this.code); out(out == 0) = NaN; endfunction ## -*- texinfo -*- ## @deftypefn {categorical} {@var{out} =} single (@var{C}) ## ## Convert categorical array to a single array. ## ## @code{@var{out} = single (@var{C})} returns a single array indexing the ## categories in @var{C}. Categorical elements of undefined category are ## returned as @code{NaN}. ## ## @end deftypefn function out = single (this) out = single (this.code); out(out == 0) = NaN; endfunction ## -*- texinfo -*- ## @deftypefn {categorical} {@var{out} =} int64 (@var{C}) ## ## Convert categorical array to a int64 array. ## ## @code{@var{out} = int64 (@var{C})} returns a @qcode{int64} array ## indexing the categories in @var{C}. Categorical elements of undefined ## category are returned as @qcode{0}. ## ## @end deftypefn function out = int64 (this) out = int64 (this.code); endfunction ## -*- texinfo -*- ## @deftypefn {categorical} {@var{out} =} int32 (@var{C}) ## ## Convert categorical array to a int32 array. ## ## @code{@var{out} = int32 (@var{C})} returns a @qcode{int32} array ## indexing the categories in @var{C}. Categorical elements of undefined ## category are returned as @qcode{0}. ## ## @end deftypefn function out = int32 (this) out = int32 (this.code); endfunction ## -*- texinfo -*- ## @deftypefn {categorical} {@var{out} =} int16 (@var{C}) ## ## Convert categorical array to a int16 array. ## ## @code{@var{out} = int16 (@var{C})} returns a @qcode{int16} array ## indexing the categories in @var{C}. Categorical elements of undefined ## category are returned as @qcode{0}. Note that the returned category ## indices saturate to @qcode{intmax ('int16')}, which is 32767. ## ## @end deftypefn function out = int16 (this) out = int16 (this.code); endfunction ## -*- texinfo -*- ## @deftypefn {categorical} {@var{out} =} int8 (@var{C}) ## ## Convert categorical array to a int8 array. ## ## @code{@var{out} = int8 (@var{C})} returns a @qcode{int8} array ## indexing the categories in @var{C}. Categorical elements of undefined ## category are returned as @qcode{0}. Note that the returned category ## indices saturate to @qcode{intmax ('int8')}, which is 127. ## ## @end deftypefn function out = int8 (this) out = int8 (this.code); endfunction ## -*- texinfo -*- ## @deftypefn {categorical} {@var{out} =} uint64 (@var{C}) ## ## Convert categorical array to a uint64 array. ## ## @code{@var{out} = uint64 (@var{C})} returns a @qcode{uint64} array ## indexing the categories in @var{C}. Categorical elements of undefined ## category are returned as @qcode{0}. ## ## @end deftypefn function out = uint64 (this) out = uint64 (this.code); endfunction ## -*- texinfo -*- ## @deftypefn {categorical} {@var{out} =} uint32 (@var{C}) ## ## Convert categorical array to a uint32 array. ## ## @code{@var{out} = uint32 (@var{C})} returns a @qcode{uint32} array ## indexing the categories in @var{C}. Categorical elements of undefined ## category are returned as @qcode{0}. ## ## @end deftypefn function out = uint32 (this) out = uint32 (this.code); endfunction ## -*- texinfo -*- ## @deftypefn {categorical} {@var{out} =} uint16 (@var{C}) ## ## Convert categorical array to a uint16 array. ## ## @code{@var{out} = uint16 (@var{C})} returns a @qcode{uint16} array ## indexing the categories in @var{C}. Categorical elements of undefined ## category are returned as @qcode{0}. ## ## @end deftypefn function out = uint16 (this) out = uint16 (this.code); endfunction ## -*- texinfo -*- ## @deftypefn {categorical} {@var{out} =} uint8 (@var{C}) ## ## Convert categorical array to a int8 array. ## ## @code{@var{out} = uint8 (@var{C})} returns a @qcode{uint8} array ## indexing the categories in @var{C}. Categorical elements of undefined ## category are returned as @qcode{0}. Note that the returned category ## indices saturate to @qcode{intmax ('uint8')}, which is 255. ## ## @end deftypefn function out = uint8 (this) out = uint8 (this.code); endfunction endmethods ################################################################################ ## ** Summary Information ** ## ################################################################################ ## Available Methods ## ## ## ## 'summary' 'categories' 'countcats' 'length' ## ## 'size' 'ndims' 'numel' 'keyHash' ## ## ## ################################################################################ methods (Access = public) ## -*- texinfo -*- ## @deftypefn {categorical} {} summary (@var{C}) ## @deftypefnx {categorical} {} summary (@var{C}, @var{dim}) ## @deftypefnx {categorical} {} summary (@dots{}, @var{Name}, @var{Value}) ## @deftypefnx {categorical} {@var{s} =} summary (@dots{}) ## ## Display summary of categorical array. ## ## @code{summary (@var{C})} displays the number of elements in the ## categorical array @var{C} that are equal to each category of @var{C}. ## Any undefined elements in @var{C} are summed together and displayed as ## @qcode{}. ## ## @end deftypefn function varargout = summary (this, varargin) ## Parse optional Name-Value paired arguments optName = {'Statistics'}; dfValue = {'default'}; [stats, args] = parsePairedArguments (optName, dfValue, varargin(:)); ## Force stats to cellstr stats = cellstr (stats); ## Handle default statistcs didx = strcmpi (stats, 'default'); if (this.isOrdinal) if (any (didx)) default = {'counts', 'nummissing', 'min', 'median', 'max'}; stats(didx) = []; ridx = cellfun (@(x) any (strcmpi (x, default)), stats); stats(ridx) = []; stats = [default, stats]; endif else if (any (didx)) default = {'counts', 'nummissing'}; stats(didx) = []; ridx = cellfun (@(x) any (strcmpi (x, default)), stats); stats(ridx) = []; stats = [default, stats]; endif endif ## Get operating dimension sz = size (this); if (isempty (args)) (dim = find (sz > 1, 1)) || (dim = 1); else if (numel (args) > 1) error ("categorical.summary: unrecognized input argument."); endif dim = args{1}; if (! (isnumeric (dim) && isscalar (dim) && fix (dim) == dim && ... dim > 0)) error ("categorical.summary: DIM must be a positive integer."); endif endif ## Store input size and category names in output struct s.Size = sz; s.Type = 'categorical'; s.Name = inputname (1); s.Categories = this.cats; ## Store 'counts' first (if requested) cidx = strcmpi (stats, 'counts'); NAMES = {}; if (any (cidx)) NAMES = [NAMES; this.cats]; s.Counts = countcats (this, dim); stats(cidx) = []; nidx = strcmpi (stats, 'nummissing'); if (any (nidx)) s.NumMissing = sum (this.isMissing, dim); stats(nidx) = []; NAMES = [NAMES; '']; endif endif ## Store remaining statistics according to their order in 'stats' STATS = {}; while (! isempty (stats)) switch (lower (stats{1})) case 'nummissing' s.NumMissing = sum (this.isMissing, dim); STATS = [STATS; 'NumMissing']; case 'min' if (this.isOrdinal) s.Min = min (this, [], dim); STATS = [STATS; 'Min']; endif case 'median' if (this.isOrdinal) s.Median = median (this, dim); STATS = [STATS; 'Median']; endif case 'max' if (this.isOrdinal) s.Max = max (this, [], dim); STATS = [STATS; 'Max']; endif case 'mode' s.Mode = mode (this, dim); STATS = [STATS; 'Mode']; endswitch stats(1) = []; endwhile ## Return structure or display summary if (nargout > 0) s = rmfield (s, 'Name'); varargout{1} = s; else __summary__ (s, dim, NAMES, STATS); endif endfunction ## -*- texinfo -*- ## @deftypefn {categorical} {@var{cstr} =} categories (@var{C}) ## ## List of categories in categorical array. ## ## @code{@var{cstr} = categories (@var{C})} returns a cell array of ## character vectors with the names of the categories in @var{C}. ## ## @end deftypefn function cstr = categories (this) cstr = this.cats; endfunction ## -*- texinfo -*- ## @deftypefn {categorical} {@var{N} =} countcats (@var{C}) ## @deftypefnx {categorical} {@var{N} =} countcats (@var{C}, @var{dim}) ## ## Count occurrences of categories in a categorical array. ## ## @code{@var{N} = countcats (@var{C})} returns the number of elements for ## each category in @var{C}. If @var{C} is a vector, @var{N} is also a ## vector with one element for each category in @var{C}. If @var{C} is a ## matrix, @var{N} is a matrix with each column containing the category ## counts from each column of @var{C}. For multidimensional arrays, ## @code{countcats} operates along the first non-singleton dimension. ## ## @code{@var{N} = countcats (@var{C}, @var{dim})} operates along the ## dimension @var{dim}. ## ## @end deftypefn function N = countcats (this, dim = []) codes = this.code; sz = size (codes); nc = numel (this.cats); if (isempty (dim)) (dim = find (sz > 1, 1)) || (dim = 1); elseif (! (isnumeric (dim) && isscalar (dim) && fix (dim) == dim && ... dim > 0)) error ("categorical.countcats: DIM must be a positive integer."); endif if (nc == 0) sz(dim) = 0; N = zeros (sz); return; endif if (dim <= ndims (this)) nsz = sz; nsz(dim) = nc; ## Look-up indices. grp = [1:nc]; idx = lookup (grp, codes); ## Zero invalid ones (including NaNs). x < edges(1) are already zero. idx(! (codes <= grp(end))) = 0; iidx = idx; ## In case of matrix input, we adjust the indices. no_vector = (isrow (codes) && dim == 1) || ... (iscolumn (codes) && dim == 2); if (! isvector (codes) || no_vector) nl = prod (sz(1:dim-1)); nn = sz(dim); nu = prod (sz(dim+1:end)); if (nl != 1) iidx = (iidx-1) * nl; iidx += reshape (kron (ones (1, nn*nu), 1:nl), sz); endif if (nu != 1) ne = length (grp); iidx += reshape (kron (nl*ne*(0:nu-1), ones (1, nl*nn)), sz); endif endif ## Select valid elements. iidx = iidx(idx != 0); ## Call accumarray to sum the indexed elements. N = accumarray (iidx(:), 1, nsz); else N = repmat (zeros (size (this)), [ones(1, dim - 1), nc]); totnum = prod (size (this)); linvec = 1:totnum; for i = 1:nc offset = (i - 1) * totnum; N(linvec + offset) = codes == i; endfor endif endfunction ## -*- texinfo -*- ## @deftypefn {categorical} {@var{N} =} length (@var{C}) ## ## Length of a categorical vector. ## ## @code{@var{N} = length (@var{C})} returns the size of the longest ## dimension of the categorical array @var{C}, unless any of its dimensions ## has zero length, in which case @code{length (@var{C})} returns 0. ## ## @end deftypefn function N = length (this) if (isempty (this.code)) N = 0; else N = max (size (this.code)); endif endfunction ## -*- texinfo -*- ## @deftypefn {categorical} {@var{sz} =} size (@var{C}) ## @deftypefnx {categorical} {@var{dim_sz} =} size (@var{C}, @var{dim}) ## @deftypefnx {categorical} {@var{dim_sz} =} size (@var{C}, @var{d1}, @var{d2}, @dots{}) ## @deftypefnx {categorical} {[@var{rows}, @var{columns}, @dots{}, @var{dim_n_sz}] =} size (@dots{}) ## ## Size of a categorical array. ## ## @code{@var{sz} = size (@var{C})} returns a row vector with the size ## (number of elements) of each dimension for the categorical array @var{C}. ## ## @code{@var{dim_sz} = size (@var{C}, @var{dim})} returns the size of ## the corresponding dimension specified in @var{dim}. If @var{dim} is a ## vector, then @var{dim_sz} is a vector of the same length and with each ## element corresponding to a specified dimension. Multiple dimensions may ## also be specified as separate arguments. ## ## With a single output argument, @code{size} returns a row vector. When ## called with multiple output arguments, @code{size} returns the size of ## dimension N in the Nth argument. ## ## @end deftypefn function varargout = size (this, varargin) if (! isempty (varargin)) sz = size (this.code, varargin{:}); else sz = size (this.code); endif if (nargout == 0 || nargout == 1) varargout{1} = sz; elseif (numel (sz) != nargout) error (strcat ("categorical.size: nargout > 1 but does", ... " not match number of requested dimensions.")); else for i = 1:nargout varargout{i} = sz(i); endfor endif endfunction ## -*- texinfo -*- ## @deftypefn {categorical} {@var{out} =} ndims (@var{C}) ## ## Number of dimensions in a categorical array. ## ## @code{@var{out} = ndims (@var{C})} returns the number of dimensions of ## the categorical array @var{C}. ## ## @end deftypefn function out = ndims (this) out = ndims (this.code); endfunction ## -*- texinfo -*- ## @deftypefn {categorical} {@var{out} =} numel (@var{C}) ## ## Total number of elements in a categorical array. ## ## @code{@var{out} = numel (@var{C})} returns the number of elements in the ## categorical array @var{C}. ## ## @end deftypefn function out = numel (this, varargin) out = numel (this.code); endfunction ## -*- texinfo -*- ## @deftypefn {categorical} {@var{hey} =} keyHash (@var{C}) ## @deftypefnx {categorical} {@var{hey} =} keyHash (@var{C}, @var{base}) ## ## Generate a hash code for a categorical array. ## ## @code{@var{h} = keyHash (@var{C})} generates a @qcode{uint64} scalar that ## represents the input array @var{C}. @code{keyHash} utilizes the 64-bit ## FNV-1a variant of the Fowler-Noll-Vo non-cryptographic hash function. ## ## @code{@var{h} = keyHash (@var{C}), @var{base}} also generates a 64-bit ## hash code using @var{base} as the offset basis for the FNV-1a hash ## algorithm. @var{base} must be a @qcode{uint64} integer type scalar. Use ## this syntax to cascade @code{keyHash} on multiple objects for which a ## single hash code is required. ## ## Note that unlike MATLAB, this implementation does not use any random ## seed. As a result, @code{keyHash} will always generate the exact same ## hash key for any particular input across different workers and Octave ## sessions. ## ## @end deftypefn function key = keyHash (this, base = []) ## Initialize string with size, class name, flags, and categories size_str = sprintf ('%dx', size (this.code))(1:end-1); flag_str = sprintf ('-o%d-p%d:', this.isOrdinal, this.isProtected); cats_str = sprintf ('%s', this.cats{:}); init_str = [size_str 'categorical' flag_str cats_str]; if (isempty (this.code)) if (base) if (! (isscalar (base) && isa (base, 'uint64'))) error ("categorical.keyHash: BASE must be a UINT64 scalar."); endif key = __ckeyHash__(init_str, base); else key = __ckeyHash__(init_str); endif else cats = [this.cats(:); '']; code = this.code(:); code(code == 0) = max (code) + 1; cstr = [cats{code}]; if (base) if (! (isscalar (base) && isa (base, 'uint64'))) error ("categorical.keyHash: BASE must be a UINT64 scalar."); endif key = __ckeyHash__([init_str cstr], base); else key = __ckeyHash__([init_str cstr]); endif key = __nkeyHash__(this.isMissing(:), key); endif endfunction endmethods ################################################################################ ## ** Query Operations ** ## ################################################################################ ## Available Methods ## ## ## ## 'iscategory' 'iscolumn' 'isempty' 'isequal' ## ## 'isequaln' 'ismatrix' 'ismember' 'ismissing' ## ## 'isordinal' 'isprotected' 'isrow' 'isscalar' ## ## 'issorted' 'issortedrows' 'isundefined' 'isvector' ## ## ## ################################################################################ methods (Access = public) ## -*- texinfo -*- ## @deftypefn {categorical} {@var{TF} =} iscategory (@var{C}, @var{catnames}) ## ## Test for categories in a categorical array. ## ## @code{@var{TF} = iscategory (@var{C}, @var{catnames})} returns a logical ## array @var{TF} of the same size as @var{catnames} containing @qcode{true} ## for each corresponding element of @var{catnames} that is a category in ## categorical array @var{C} and @qcode{false} otherwise. ## ## @end deftypefn function TF = iscategory (this, catnames) if (nargin < 2) error ("categorical.iscategory: CATNAMES input argument is required."); endif catnames = cellstr (catnames); TF = ismember (catnames, this.cats); endfunction ## -*- texinfo -*- ## @deftypefn {categorical} {@var{TF} =} iscolumn (@var{C}) ## ## Return true if categorical array is a column vector. ## ## @code{@var{TF} = iscolumn (@var{C})} returns a logical scalar @var{TF}, ## which is @qcode{true}, if the categorical array @var{C} is a column ## vector, and @qcode{false} otherwise. A column vector is a 2-D array for ## which @code{size (@var{X})} returns @code{[@var{N}, 1]} with non-negative ## @var{N}. ## ## @end deftypefn function TF = iscolumn (this) TF = iscolumn (this.code); endfunction ## -*- texinfo -*- ## @deftypefn {categorical} {@var{TF} =} isempty (@var{C}) ## ## Return true if categorical array is empty. ## ## @code{@var{TF} = isempty (@var{C})} returns a logical scalar @var{TF}, ## which is @qcode{true}, if the categorical array @var{C} is empty, and ## @qcode{false} otherwise. ## ## @end deftypefn function TF = isempty (this) TF = isempty (this.code); endfunction ## -*- texinfo -*- ## @deftypefn {categorical} {@var{TF} =} isequal (@var{C1}, @var{C2}) ## @deftypefnx {categorical} {@var{TF} =} isequal (@var{C1}, @var{C2}, @dots{}) ## ## Return true if categorical arrays are equal. ## ## @code{@var{TF} = isequal (@var{C1}, @var{C2})} returns a logical scalar ## @var{TF}, which is @qcode{true}, if the categorical arrays @var{C1} and ## @var{C2} contain the same values, and @qcode{false} otherwise. Either ## @var{C1} or @var{C2} may also be a string array, a missing object array, ## a character vector, or a cell array of character vectors, which will be ## promoted to a categorical array prior to comparison. ## ## If categorical arrays @var{C1} and @var{C2} are ordinal, they must have ## the same set and ordering of categories. If neither are ordinal, the ## category names of each pair of elements are compared. Hence, they do not ## need to have the same set of categories. ## ## @code{@var{TF} = isequal (@var{C1}, @var{C2}, @dots{})} returns a logical ## scalar @var{TF}, which is @qcode{true}, if all input arguments are equal, ## and @qcode{false} otherwise. ## ## @end deftypefn function TF = isequal (varargin) args = varargin; [args{:}] = promote (varargin{:}); ## Check for undefined elements (result to false) if (any (cellfun (@(x) any (x.code == 0, 'all'), args))) TF = false; return; endif ## If any categorical value is ordinal, all must be is_ordinal = cellfun (@isordinal, args); if (all (is_ordinal)) ## Check that all categorical arrays have the same categories ## and they are in the same order cats = cellfun (@(x) categories (x), args, 'UniformOutput', false); if (! isequal (cats{:})) TF = false; return; endif fieldArgs = cellfun (@(x) x.code, args, 'UniformOutput', false); TF = isequal (fieldArgs{:}); elseif (any (is_ordinal)) TF = false; else ## Compare the category names of each element fcn = @(x) reshape (x.cats(x.code), size (x.code)); fieldArgs = cellfun (fcn, args, 'UniformOutput', false); TF = isequal (fieldArgs{:}); endif endfunction ## -*- texinfo -*- ## @deftypefn {categorical} {@var{TF} =} isequaln (@var{C1}, @var{C2}) ## @deftypefnx {categorical} {@var{TF} =} isequaln (@var{C1}, @var{C2}, @dots{}) ## ## Return true if categorical arrays are equal under the assumption that ## undefined elements are equal. ## ## @code{@var{TF} = isequaln (@var{C1}, @var{C2})} returns a logical scalar ## @var{TF}, which is @qcode{true}, if the categorical arrays @var{C1} and ## @var{C2} contain the same values or corresponding undefined elements, and ## @qcode{false} otherwise. Either @var{C1} or @var{C2} may also be a ## string array, a missing object array, a character vector, or a cell array ## of character vectors, which will be promoted to a categorical array prior ## to comparison. ## ## If categorical arrays @var{C1} and @var{C2} are ordinal, they must have ## the same set and ordering of categories. If neither are ordinal, the ## category names of each pair of elements are compared. Hence, they do not ## need to have the same set of categories. ## ## @code{@var{TF} = isequaln (@var{C1}, @var{C2}, @dots{})} returns a ## logical scalar @var{TF}, which is @qcode{true}, if all input arguments ## are equal, and @qcode{false} otherwise. ## ## @end deftypefn function TF = isequaln (varargin) args = varargin; [args{:}] = promote (varargin{:}); ## If any categorical value is ordinal, all must be is_ordinal = cellfun (@isordinal, args); if (all (is_ordinal)) ## Check that all categorical arrays have the same categories ## and they are in the same order cats = cellfun (@(x) categories (x), args, 'UniformOutput', false); if (! isequal (cats{:})) TF = false; return; endif fieldArgs = cellfun (@(x) x.code, args, 'UniformOutput', false); TF = isequal (fieldArgs{:}); elseif (any (is_ordinal)) TF = false; else ## Compare the category names of each element fcn = @(x) reshape (([''; x.cats])(x.code + 1), ... size (x.code)); fieldArgs = cellfun (fcn, args, 'UniformOutput', false); TF = isequal (fieldArgs{:}); endif endfunction ## -*- texinfo -*- ## @deftypefn {categorical} {@var{TF} =} ismatrix (@var{C}) ## ## Return true if categorical array is a 2-D array. ## ## @code{@var{TF} = ismatrix (@var{C})} returns a logical scalar @var{TF}, ## which is @qcode{true}, if the categorical array @var{C} is a matrix, and ## @qcode{false} otherwise. A matrix is an array of any type where ## @code{ndims (@var{X}) == 2} and for which @code{size (@var{X})} returns ## @code{[@var{H}, @var{W}]} with non-negative @var{H} and @var{W}. ## ## @end deftypefn function TF = ismatrix (this) TF = ismatrix (this.code); endfunction ## -*- texinfo -*- ## @deftypefn {categorical} {@var{TF} =} ismember (@var{A}, @var{B}) ## @deftypefnx {categorical} {@var{TF} =} ismember (@var{A}, @var{B}, @qcode{'rows'}) ## @deftypefnx {categorical} {[@var{TF}, @var{index}] =} ismember (@dots{}) ## @deftypefnx {categorical} {[@var{TF}, @var{index}] =} ismember (@dots{}, @qcode{'legacy'}) ## ## Find categorical elements in a set. ## ## @code{@var{TF} = ismember (@var{A}, @var{B})} returns a logical array ## @var{TF} of the same size as @var{A} containing @qcode{true} for each ## corresponding element of @var{A} that is in @var{B} and @qcode{false} ## otherwise. @qcode{} elements are not equal with each other ## and always return @qcode{false}. If @var{A} and @var{B} are ordinal, ## they must have the same ordered set of categories. If neither @var{A} ## nor @var{B} are ordinal, then this restriction is relaxed and comparison ## is performed using the category names. Comparison between an ordinal and ## an unordered categorical array is not allowed. Either @var{A} or @var{B} ## may also be a string array, a character vector, or a cell array of ## character vectors containing one or multiple category names to compare ## against an unordered categorical array. ## ## @code{@var{TF} = ismember (@var{A}, @var{B}, @qcode{'rows'})} only ## applies to categorical matrices with the same number of columns, in which ## case the logical vector @var{TF} contains @qcode{true} for each row of ## @var{A} that is also a row in @var{B}. @var{TF} has the same number of ## rows as @var{A}. ## ## @code{[@var{TF}, @var{index}] = ismember (@var{A}, @var{B})} also returns ## an index array of the same size as @var{A} containing the lowest index in ## @var{B} for each element of @var{A} that is a member of @var{B} and 0 ## otherwise. If the @qcode{'rows'} optional argument is used, then the ## returning index is a column vector with the same rows as @var{A} and it ## contains the lowest index in @var{B} for each row of @var{A} that is a ## member of @var{B} and 0 otherwise. If the @qcode{'legacy'} optional ## argument is specified, then the highest index of matched elements is ## returned. Unless multiple matches exist, the @qcode{'legacy'} option has ## no effect on the returned @var{index}. ## ## @end deftypefn function varargout = ismember (A, B, varargin) ## Either A or B contain categorical text representation if ((ischar (A) && isrow (A)) || isstring (A) || iscellstr (A)) if (ischar (A)) A = cellstr (A); endif A = categorical (A); elseif ((ischar (B) && isrow (B)) || isstring (B) || iscellstr (B)) if (ischar (B)) B = cellstr (B); endif B = categorical (B); elseif (! (iscategorical (A) && iscategorical (B))) error (strcat ("categorical.ismember: A and B must be categorical", ... " arrays, or one of them (A or B) can be a string", ... " array, a character vector, or a cell array of", ... " character vectors specifying category names.")); endif ## Check for 'rows' and 'legacy' optional arguments if (! isempty (varargin)) if (! cellfun ('ischar', varargin)); error (strcat ("categorical.ismember: all options must be", ... " character vectors.")); elseif (! all (strcmpi (varargin, 'rows') | ... strcmpi (varargin, 'legacy'))) error (strcat ("categorical.ismember: only 'rows' and", ... " 'legacy' are valid options.")); endif if (any (strcmpi ('rows', varargin))) if (ndims (A) != 2 || ndims (A) != ndims (B)) error (strcat ("categorical.ismember: 'rows' applies only", ... " to 2-D matrices.")); endif if (size (A, 2) != size (B, 2)) error (strcat ("categorical.ismember: 'rows' requires same", ... " number of columns.")); endif endif endif ## Handle empty input array if (isempty (A) || isempty (B)) sz = size (A); varargout{1} = false (sz); if (nargout > 1) varargout{2} = zeros (sz); endif return; endif ## Both ordinal if (isordinal (A) && isordinal (B)) if (! isequal (categories (A), categories (B))) error (strcat ("categorical.ismember: ordinal categorical arrays", ... " must have the same ordered set of categories.")); endif if (nargout > 1) [varargout{1}, varargout{2}] = __ismember__ (double (A), ... double (B), varargin{:}); else varargout{1} = __ismember__ (double (A), double (B), varargin{:}); endif elseif (isordinal (A) || isordinal (B)) error (strcat ("categorical.ismember: both categorical arrays", ... " must be ordinal.")); else ## Compare the category names of each element (except undefined) A_idx = A.code != 0; B_idx = B.code != 0; cats_A = string (cell (size (A.code))); cats_B = string (cell (size (B.code))); cats_A(A_idx) = A.cats(A.code(A_idx)); cats_B(B_idx) = B.cats(B.code(B_idx)); if (nargout > 1) [varargout{1}, varargout{2}] = ismember (cats_A, cats_B, varargin{:}); else varargout{1} = ismember (cats_A, cats_B, varargin{:}); endif endif endfunction ## -*- texinfo -*- ## @deftypefn {categorical} {@var{out} =} ismissing (@var{C}) ## @deftypefnx {categorical} {@var{out} =} ismissing (@var{C}, @var{indicator}) ## ## Find missing elements in categorical array. ## ## @code{@var{TF} = ismissing (@var{C})} returns a logical array @var{TF} ## of the same size as @var{C} containing @qcode{true} for each ## corresponding element of @var{C} that does not have a value from one of ## the categories in @var{C} and @qcode{false} otherwise. ## ## @code{@var{TF} = ismissing (@var{C}, @var{indicator})} also returns a ## logical array @var{TF} containing @qcode{true} for each corresponding ## element of @var{C} that does not have a value from one of the categories ## specified in @var{indicator} and @qcode{false} otherwise. ## ## @var{indicator} must be either a categorical array or a character vector ## or a string vector or a cell vector of character vectors. When the ## @var{indicator} contains text representation, the comparison is based on ## lexicographical equality to the category names of @var{C}. ## ## @end deftypefn function TF = ismissing (this, varargin) if (nargin > 2) error ("categorical.ismissing: too many input arguments."); endif if (! isempty (varargin)) indicator = varargin{1}; TF = false (size (this)); if (isvector (indicator)) if (ischar (indicator)) if (! isrow (indicator)) error (strcat ("categorical.ismissing: INDICATOR specified", ... " as a character vector must be a row vector.")); endif TF(this == indicator) = true; elseif (isstring (indicator) || iscellstr (indicator)) for i = 1:numel (indicator) TF(this == indicator(i)) = true; endfor elseif (isa (indicator, 'categorical')) for i = 1:length (indicator) idx = indicator.code(i); if (idx) cat = indicator.cats{idx}; TF(this == cat) = true; else # code = 0 corresponds to undefined categories TF(this.isMissing) = true; endif endfor else error (strcat ("categorical.ismissing: INDICATOR must be", ... " a text representation of categories or", ... " categorical.")); endif else error ("categorical.ismissing: INDICATOR must be a vector."); endif else TF = this.isMissing; endif endfunction ## -*- texinfo -*- ## @deftypefn {categorical} {@var{TF} =} isordinal (@var{C}) ## ## Test if categorical array is ordinal. ## ## @code{@var{TF} = isordinal (@var{C})} returns a logical scalar @var{TF}, ## which is @qcode{true}, if the categorical array @var{C} is ordinal, and ## @qcode{false} otherwise. ## ## @end deftypefn function TF = isordinal (this) TF = this.isOrdinal; endfunction ## -*- texinfo -*- ## @deftypefn {categorical} {@var{TF} =} isprotected (@var{C}) ## ## Test if categorical array is protected. ## ## @code{@var{TF} = isprotected (@var{C})} returns a logical scalar ## @var{TF}, which is @qcode{true}, if the categorical array @var{C} is ## protected, and @qcode{false} otherwise. ## ## @end deftypefn function TF = isprotected (this) TF = this.isProtected; endfunction ## -*- texinfo -*- ## @deftypefn {categorical} {@var{TF} =} isrow (@var{C}) ## ## Return true if categorical array is a row vector. ## ## @code{@var{TF} = isrow (@var{C})} returns a logical scalar @var{TF}, ## which is @qcode{true}, if the categorical array @var{C} is a row vector, ## and @qcode{false} otherwise. A row vector is a 2-D array for which ## @code{size (@var{X})} returns @code{[1, @var{N}]} with non-negative ## @var{N}. ## ## @end deftypefn function TF = isrow (this) TF = isrow (this.code); endfunction ## -*- texinfo -*- ## @deftypefn {categorical} {@var{TF} =} isscalar (@var{C}) ## ## Return true if categorical array is a scalar. ## ## @code{@var{TF} = isscalar (@var{C})} returns a logical scalar @var{TF}, ## which is @qcode{true}, if the categorical array @var{C} is also a scalar, ## and @qcode{false} otherwise. A scalar is a single element object for ## which @code{size (@var{X})} returns @code{[1, 1]}. ## ## @end deftypefn function TF = isscalar (this) TF = isscalar (this.code); endfunction ## -*- texinfo -*- ## @deftypefn {categorical} {@var{TF} =} issorted (@var{C}) ## @deftypefnx {categorical} {@var{TF} =} issorted (@var{C}, @var{dim}) ## @deftypefnx {categorical} {@var{TF} =} issorted (@var{C}, @var{direction}) ## @deftypefnx {categorical} {@var{TF} =} issorted (@var{C}, @var{dim}, @var{direction}) ## @deftypefnx {categorical} {@var{TF} =} issorted (@dots{}, @qcode{'MissingPlacement'}, @var{MP}) ## ## Return true if categorical array is sorted. ## ## @code{@var{TF} = issorted (@var{C})} returns a logical scalar @var{TF}, ## which is @qcode{true}, if the categorical array @var{C} is sorted in ## ascending order, and @qcode{false} otherwise. ## ## @code{@var{TF} = issorted (@var{C}, @var{dim})} returns a logical scalar ## @var{TF}, which is @qcode{true}, if the categorical array @var{C} is ## sorted in ascending order along the dimension @var{dim}, and ## @qcode{false} otherwise. ## ## @code{@var{TF} = issorted (@var{A}, @var{direction})} returns a logical ## scalar @var{TF}, which is @qcode{true}, if the categorical array @var{C} ## is sorted in the direction specified by @var{direction}, and ## @qcode{false} otherwise. @var{direction} can be one of the following ## options: ## ## @itemize ## @item @qcode{'ascend'}, which is the default, checks is elements are in ## ascending order. ## @item @qcode{'descend'} checks if elements are in descending order. ## @item @qcode{'monotonic'} checks if elements are either in ascending or ## descending order. ## @item @qcode{'strictascend'} checks if elements are in ascending order ## and there are no duplicate or undefined elements. ## @item @qcode{'strictdescend'} checks if elements are in descending order ## and there are no duplicate or undefined elements. ## @item @qcode{'strictmonotonic'} checks if elements are either in ## ascending or descending order and there are no duplicate or undefined ## elements. ## @end itemize ## ## @code{@var{TF} = issorted (@dots{}, @qcode{'MissingPlacement'}, ## @var{MP})} specifies where missing elements (@qcode{}) are ## placed with ## one of the following options specified in @var{MP}: ## ## @itemize ## @item @qcode{'auto'}, which is the default, places missing elements last ## for ascending sort and first for descending sort. ## @item @qcode{'first'} places missing elements first. ## @item @qcode{'last'} places missing elements last. ## @end itemize ## ## @end deftypefn function TF = issorted (this, varargin) ## Single input argument if (nargin == 1) TF = isequaln (this, sort (this)); return; endif ## Get operating dimension cid = cellfun (@isnumeric, varargin); if (any (cid)) dim = varargin{cid}; else sz = size (this); dim = find (sz != 1, 1); if (isempty (dim)) # scalar dim = 1; endif endif ## Parse and validate optional 'MissingPlacement' paired argument optNames = {'MissingPlacement'}; dfValues = {'auto'}; [MP, args] = parsePairedArguments (optNames, dfValues, varargin(:)); if (! ismember (MP, {'auto', 'first', 'last'})) error ("categorical.issorted: invalid value for 'MissingPlacement'."); endif ## Force strings to character vectors [args{:}] = convertStringsToChars (args{:}); ## Get direction cid = cellfun (@(x) ischar (x), args); if (any (cid)) direction = args{cid}; ## Check for type of direction valid_direction = {'ascend', 'descend', 'monotonic', 'strictascend', ... 'strictdescend', 'strictmonotonic'}; if (! ismember (direction, valid_direction)) error ("categorical.issorted: invalid DIRECTION value."); endif switch (direction) case {'ascend', 'descend'} TF = isequaln (this, sort (this, args{:}, 'MissingPlacement', MP)); case {'strictascend', 'strictdescend'} ## Check for missing values first (fast) if (any (this.isMissing, 'all')) TF = false; return; endif args{cid} = strrep (direction, 'strict', ''); if (! strcmp (MP, 'auto')) args = [args, 'MissingPlacement', MP]; endif sorted = sort (this, args{:}); if (any (diff (double (sorted), 1, dim) == 0, 'all')) TF = false; return; endif TF = isequal (this, sorted); case 'monotonic' ## Check for either ascending or descending args{cid} = 'ascend'; TF = isequaln (this, sort (this, args{:}, 'MissingPlacement', MP)); if (TF) return; endif args{cid} = 'descend'; TF = isequaln (this, sort (this, args{:}, 'MissingPlacement', MP)); case 'strictmonotonic' ## Check missing values first (fast) if (any (this.isMissing, 'all')) TF = false; return; endif ## Check for either ascending or descending args{cid} = 'ascend'; sorted = sort (this, args{:}, 'MissingPlacement', MP); if (any (diff (double (sorted), 1, dim) == 0, 'all')) TF = false; return; endif TF = isequal (this, sorted); if (TF) return; endif args{cid} = 'descend'; sorted = sort (this, args{:}, 'MissingPlacement', MP); if (any (diff (double (sorted), 1, dim) == 0, 'all')) TF = false; return; endif TF = isequal (this, sorted); endswitch else ## No DIRECTION input argument TF = isequaln (this, sort (this, args{:}, 'MissingPlacement', MP)); endif endfunction ## -*- texinfo -*- ## @deftypefn {categorical} {@var{TF} =} issortedrows (@var{C}) ## @deftypefnx {categorical} {@var{TF} =} issortedrows (@var{C}, @var{col}) ## @deftypefnx {categorical} {@var{TF} =} issortedrows (@var{C}, @var{direction}) ## @deftypefnx {categorical} {@var{TF} =} issortedrows (@var{C}, @var{col}, @var{direction}) ## @deftypefnx {categorical} {@var{TF} =} issortedrows (@dots{}, @qcode{'MissingPlacement'}, @var{MP}) ## ## Return true if categorical matrix rows are sorted. ## ## @code{@var{TF} = issortedrows (@var{C})} returns a logical scalar ## @var{TF}, which is @qcode{true}, if the rows in the 2-D categorical array ## @var{C} are sorted in ascending order, and @qcode{false} otherwise. ## ## @code{@var{TF} = issortedrows (@var{C}, @var{col})} returns a logical ## scalar @var{TF}, which is @qcode{true}, if the categorical array @var{C} ## is sorted according to the columns specified by the vector @var{col}, and ## @qcode{false} otherwise. @var{col} must explicitly contain non-zero ## integers whose absolute values index existing columns in @var{C}. ## Positive elements sort the corresponding columns in ascending order, ## while negative elements sort the corresponding columns in descending ## order. ## ## @code{@var{TF} = issortedrows (@var{C}, @var{direction})} checks if the ## rows in @var{C} are sorted according to the specified direction, which ## can be one of the following options: ## ## @itemize ## @item @qcode{'ascend'}, which is the default, checks is elements are in ## ascending order. ## @item @qcode{'descend'} checks if elements are in descending order. ## @item @qcode{'monotonic'} checks if elements are either in ascending or ## descending order. ## @item @qcode{'strictascend'} checks if elements are in ascending order ## and there are no duplicate or undefined elements. ## @item @qcode{'strictdescend'} checks if elements are in descending order ## and there are no duplicate or undefined elements. ## @item @qcode{'strictmonotonic'} checks if elements are either in ## ascending or descending order and there are no duplicate or undefined ## elements. ## @end itemize ## ## Alternatively, @var{direction} can be a cell array of character ## vectors specifying the sorting direction for each individual column of ## @var{C}, in which case the number of elements in @var{direction} must ## equal the number of columns in @var{C}. ## ## @code{@var{TF} = issortedrows (@var{C}, @var{col}, @var{direction})} ## checks if the rows in the categorical array @var{C} are sorted according ## to the columns specified in @var{col} using the corresponding sorting ## direction specified in @var{direction}. In this case, the sign of the ## values in @var{col} is ignored. @var{col} and @var{direction} must have ## the same length, but not necessarily the same number of elements as the ## columns in @var{C}. ## ## @code{@var{TF} = issortedrows (@dots{}, @qcode{'MissingPlacement'}, ## @var{MP})} specifies where missing elements (@qcode{}) are ## placed with one of the following options specified in @var{MP}: ## ## @itemize ## @item @qcode{'auto'}, which is the default, places missing elements last ## for ascending sort and first for descending sort. ## @item @qcode{'first'} places missing elements first. ## @item @qcode{'last'} places missing elements last. ## @end itemize ## ## @end deftypefn function TF = issortedrows (this, varargin) ## Single input argument if (nargin == 1) TF = isequaln (this, sortrows (this)); return; endif ## Force strings to character vectors or cell arrays of character vectors [varargin{:}] = convertStringsToChars (varargin{:}); ## Get valid direction(s) from input argument list valid = {'ascend', 'descend', 'monotonic', 'strictascend', ... 'strictdescend', 'strictmonotonic'}; fcn = @(x) (ischar (x) && ismember (x, valid)) || iscellstr (x); cid = cellfun (fcn, varargin); if (any (cid)) direction = cellstr (varargin{cid}); ## Check for valid type of directions in cellstring if (! all (cellfun (@(x) ismember (x, valid), direction))) error ("duration.issortedrows: invalid DIRECTION value."); endif ## Handle non-strict modes first if (all (cellfun (@(x) ismember (x, {'ascend', 'descend'}), direction))) TF = isequaln (this, sortrows (this, varargin{:})); return; endif simple_types = {'ascend', 'descend', 'monotonic'}; if (all (cellfun (@(x) ismember (x, simple_types), direction))) idx = strcmp (direction, 'monotonic'); direction{idx} = 'ascend'; varargin{cid} = direction; TF = isequaln (this, sortrows (this, varargin{:})); if (TF) return; endif direction{idx} = 'descend'; varargin{cid} = direction; TF = isequaln (this, sortrows (this, varargin{:})); return; endif ## Handle strict modes. Determine the sort-key column order and the ## sort position holding the first strict direction, then derive the ## prefix (the columns sorted up to and including that position, on ## which the duplicate test operates) and the binding strict column ## (the single column on which the missing test operates). A missing ## value in the binding column, or two consecutive sorted rows equal ## across the prefix columns, rules out a strict ordering; strict ## positions after the first are subsumed by it. strict_types = {'strictascend', 'strictdescend', 'strictmonotonic'}; sflag = cellfun (@(x) ismember (x, strict_types), direction); nc = size (this, 2); nummask = cellfun (@isnumeric, varargin); if (any (nummask)) col = varargin{nummask}; ocols = abs (col(:)'); if (isscalar (direction)) sflag = repmat (sflag, 1, numel (col)); elseif (numel (direction) != numel (col)) error ("categorical.issortedrows: COL and DIRECTION mismatch."); endif else ocols = 1:nc; if (isscalar (direction)) sflag = repmat (sflag, 1, nc); endif endif K = find (sflag, 1); prefix = ocols(1:K); bindcol = ocols(K); ## A missing value in the binding strict column rules out strictness. if (any (this.isMissing(:,bindcol), 'all')) TF = false; return; endif ## Replace strict modes with their plain counterparts for the sort. direction = strrep (direction, 'strict', ''); varargin{cid} = direction; if (all (cellfun (@(x) ismember (x, {'ascend', 'descend'}), direction))) sorted = sortrows (this, varargin{:}); ## No two consecutive rows may tie across the prefix columns. tmpcol = sorted.code(:, prefix); if (any (all (diff (tmpcol, 1, 1) == 0, 2))) TF = false; return; endif TF = isequaln (this, sorted); else # a 'monotonic' position also exists idx = strcmp (direction, 'monotonic'); direction(idx) = {'ascend'}; varargin{cid} = direction; sorted = sortrows (this, varargin{:}); tmpcol = sorted.code(:, prefix); if (any (all (diff (tmpcol, 1, 1) == 0, 2))) TF = false; return; endif TF = isequaln (this, sorted); if (TF) return; endif direction(idx) = {'descend'}; varargin{cid} = direction; sorted = sortrows (this, varargin{:}); tmpcol = sorted.code(:, prefix); if (any (all (diff (tmpcol, 1, 1) == 0, 2))) TF = false; return; endif TF = isequaln (this, sorted); endif else ## No DIRECTION input argument TF = isequaln (this, sortrows (this, varargin{:})); endif endfunction ## -*- texinfo -*- ## @deftypefn {categorical} {@var{out} =} isundefined (@var{C}) ## ## Test for undefined elements in categorical array. ## ## @code{@var{TF} = isundefined (@var{C})} returns a logical array @var{TF} ## of the same size as @var{C} containing @qcode{true} for each ## corresponding element of @var{C} that does not have a value from one of ## the categories in @var{C} and @qcode{false} otherwise. ## @qcode{} is the equivalent of @qcode{NaN} in numeric arrays. ## ## @end deftypefn function TF = isundefined (this) TF = this.isMissing; endfunction ## -*- texinfo -*- ## @deftypefn {categorical} {@var{TF} =} isvector (@var{C}) ## ## Return true if categorical array is a vector. ## ## @code{@var{TF} = isvector (@var{C})} returns a logical scalar @var{TF}, ## which is @qcode{true} if the categorical array @var{C} is a vector and ## @qcode{false} otherwise. A vector is a 2-D array for which one of the ## dimensions is equal to 1 (either @math{1*N} or @math{N*1}). By ## definition, a scalar is also a vector. ## ## @end deftypefn function TF = isvector (this) TF = isvector (this.code); endfunction endmethods ################################################################################ ## ** Category Operations ** ## ################################################################################ ## Available Methods ## ## ## ## 'addcats' 'mergecats' 'removecats' 'renamecats' ## ## 'reordercats' 'setcats' 'times' ## ## ## ################################################################################ methods (Access = public) ## -*- texinfo -*- ## @deftypefn {categorical} {@var{B} =} addcats (@var{A}, @var{newcats}) ## @deftypefnx {categorical} {@var{B} =} addcats (@dots{}, @qcode{'After'}, @var{catname}) ## @deftypefnx {categorical} {@var{B} =} addcats (@dots{}, @qcode{'Before'}, @var{catname}) ## ## Add categories to categorical array. ## ## @code{@var{B} = addcats (@var{A}, @var{newcats})} appends new categories ## specified in @var{newcats} to the categorical array @var{A} at the end of ## any existing categories. The output categorical array @var{B} does not ## contain elements that belong to the newly added categories. ## ## @code{@var{B} = addcats (@dots{}, @qcode{'After'}, @var{catname})} adds ## the categories after the existing category specified by @var{catname}. ## ## @code{@var{B} = addcats (@dots{}, @qcode{'Before'}, @var{catname})} adds ## the categories before the existing category specified by @var{catname}. ## ## @var{catname} must be either a character vector, a cellstr scalar or a ## string scalar. @var{newcats} can be a string array, a cell array of ## character vectors, or any type of array that can be converted to a cell ## array of character vectors with the @code{cellstr} function, as long as ## it does not contain any duplicate names or references existing category ## in @var{A}. ## ## When adding a single category, @var{newcats} can also be specified as a ## character vector. ## ## @end deftypefn function B = addcats (A, newcats, varargin) ## Check input arguments if (nargin < 2) error ("categorical.addcats: too few input arguments."); elseif (isempty (newcats)) error ("categorical.addcats: NEWCATS cannot be empty."); elseif (isnumeric (newcats) || islogical (newcats)) error ("categorical.addcats: NEWCATS cannot be numeric or logical."); endif ## Convert to cellstring if (! iscellstr (newcats)) try newcats = cellstr (newcats); catch error (strcat ("categorical.addcats: NEWCATS cannot be", ... " converted to cellstr.")); end_try_catch endif ## Force to column vector newcats = newcats(:); ## New catnames must be unique and non-existing if (numel (newcats) != numel (unique (newcats))) error ("categorical.addcats: duplicate category names in NEWCATS."); endif TF = ismember (newcats, A.cats); if (any (TF, 'all')) error ("categorical.addcats: new category names already present."); endif ## Parse optional Name-Value paired arguments optNames = {'After', 'Before'}; dfValues = {[], []}; [After, Before] = parsePairedArguments (optNames, dfValues, varargin(:)); ## Check optional Name-Value paired arguments if (! isempty (After) && ! isempty (Before)) error (strcat ("categorical.addcats: cannot specify both", ... " 'After' and 'Before'.")); endif ## Add categories if (! isempty (After)) maxcode = numel (A.cats); idxcode = find (strcmp (After, A.cats)); if (isempty (idxcode)) error (strcat ("categorical.addcats: 'After' indexes a", ... " non-existing category.")); elseif (idxcode == maxcode) B = A; B.cats = [B.cats; newcats]; else B = A; B.cats = [B.cats(1:idxcode); newcats; B.cats(idxcode+1:end)]; idx_add = idxcode+1:maxcode; n_cats = numel (newcats); for i = numel (idx_add):-1:1 B.code(B.code == idx_add(i)) += n_cats; endfor endif elseif (! isempty (Before)) maxcode = numel (A.cats); idxcode = find (strcmp (Before, A.cats)); if (isempty (idxcode)) error (strcat ("categorical.addcats: 'Before' indexes a", ... " non-existing category.")); elseif (idxcode == 1) B = A; B.cats = [newcats; B.cats]; B.code(! B.isMissing) += numel (newcats); else B = A; B.cats = [B.cats(1:idxcode-1); newcats; B.cats(idxcode:end)]; idx_add = idxcode:maxcode; n_cats = numel (newcats); for i = numel (idx_add):-1:1 B.code(B.code == idx_add(i)) += n_cats; endfor endif else B = A; B.cats = [B.cats; newcats]; endif endfunction ## -*- texinfo -*- ## @deftypefn {categorical} {@var{B} =} mergecats (@var{A}, @var{oldcats}) ## @deftypefnx {categorical} {@var{B} =} mergecats (@var{A}, @var{oldcats}, @var{newcat}) ## ## Merge categories in categorical array. ## ## @code{@var{B} = mergecats (@var{A}, @var{oldcats})} merges two or more ## categories specified by @var{oldcats} into a single category with the ## same name as @qcode{@var{oldcats}(1)}. In case of ordinal categorical ## arrays, the categories listed in @var{oldcats} must be in consecutive ## order. All elements of @var{A} corresponding to the categories listed in ## @var{oldcats} are re-indexed to correspond to @qcode{@var{oldcats}(1)} in ## @var{B}. ## ## @code{@var{B} = mergecats (@var{A}, @var{oldcats}, @var{newcat})} merges ## the categories listed in @var{oldcats} into a single new category named ## as specified by @var{newcat}. ## ## @var{newcat} must be either a character vector, a cellstr scalar or a ## string scalar. @var{oldcats} can be string array, a cell array of ## character vectors, or any type of array that can be converted to a cell ## array of character vectors with the @code{cellstr} function. Any names ## in @var{oldcats} that do not reference an existing category are ignored. ## ## @end deftypefn function B = mergecats (A, oldcats, varargin) ## Check input arguments if (nargin < 2) error ("categorical.mergecats: too few input arguments."); elseif (isempty (oldcats)) error ("categorical.mergecats: OLDCATS cannot be empty."); elseif (isnumeric (oldcats) || islogical (oldcats)) error ("categorical.mergecats: OLDCATS cannot be numeric or logical."); elseif (ischar (oldcats)) error ("categorical.mergecats: OLDCATS cannot be a character vector."); endif ## Convert to cellstring if (! iscellstr (oldcats)) try oldcats = cellstr (oldcats); catch error (strcat ("categorical.mergecats: OLDCATS cannot be", ... " converted to cellstr.")); end_try_catch endif ## Force to column vector oldcats = oldcats(:); ## Check for optional third argument if (nargin < 3) newcat = oldcats{1}; else newcat = varargin{1}; if (isstring (newcat) && isscalar (newcat)) newcat = cellstr (newcat); elseif (ischar (newcat) && (isrow (newcat) || isempty (newcat))) newcat = cellstr (newcat); elseif (! (iscellstr (newcat) && isscalar (newcat))) error (strcat ("categorical.mergecats: NEWCAT must be either", ... " a character vector, a cellstring scalar, or", ... " a string scalar.")); endif newcat = cellstr (varargin{1}); if (cellfun ('isempty', newcat)) error ("categorical.mergecats: NEWCAT cannot be empty."); endif endif ## Keep old cat names that reference existing categories, ignore the rest [TF, index] = ismember (oldcats, A.cats); if (! any (TF, 'all')) B = A; return; endif index(! TF) = []; ## Only consecutive categories can be merged in ordinal arrays if (A.isOrdinal && any (diff (sort (index)) != 1)) error (strcat ("categorical.mergecats: only consecutive categories", ... " can be merged in ordinal categorical arrays.")); endif ## Merge categories TF = ismember (A.code, index); [Findex, Fidx] = min (index); index(Fidx) = []; B = A; B.code(TF) = Findex; B.cats(Findex) = newcat; B.cats(index) = []; ## Fix codes due to removed categories index = sort (index, 'descend'); for idx = index(:)' B.code(B.code > idx) -= 1; endfor endfunction ## -*- texinfo -*- ## @deftypefn {categorical} {@var{B} =} removecats (@var{A}) ## @deftypefnx {categorical} {@var{B} =} removecats (@var{A}, @var{oldcats}) ## ## Remove categories from categorical array. ## ## @code{@var{B} = removecats (@var{A})} removes all unused categories from ## categorical array @var{A}. The output categorical array @var{B} has the ## same size and values as @var{A}, but potentially fewer categories. ## ## @code{@var{B} = removecats (@var{A}, @var{oldcats})} removes the ## categories specified by @var{oldcats}. The elements of @var{B} that ## correspond to the removed categories are undefined. ## ## @var{oldcats} can be a string array, a cell array of character vectors, ## or any type of array that can be converted to a cell array of character ## vectors with the @code{cellstr} function. Any names in @var{oldcats} ## that do not reference an existing category are ignored. ## ## When removing a single category, @var{oldcats} can also be specified as a ## character vector. ## ## @end deftypefn function B = removecats (A, varargin) ## Remove unused categories if (nargin == 1) usedcodes = __unique__ (A.code); usedcodes(usedcodes == 0) = []; remcodes = setdiff (1:numel (A.cats), usedcodes); B = A; B.cats(remcodes) = []; remcodes = sort (remcodes, 'descend'); for code = remcodes(:)' B.code(B.code > code) -= 1; endfor else oldcats = varargin{1}; ## Some input validation if (isempty (oldcats)) ## Handle MATLAB compatibility here if (iscell (oldcats)) B = A; return; endif error ("categorical.removecats: OLDCATS cannot be empty."); elseif (isnumeric (oldcats) || islogical (oldcats)) error (strcat ("categorical.removecats: OLDCATS cannot be", ... " numeric or logical.")); endif ## Convert to cellstring if (! iscellstr (oldcats)) try oldcats = cellstr (oldcats); catch error (strcat ("categorical.removecats: OLDCATS", ... " cannot be converted to cellstr.")); end_try_catch endif ## Keep old cat names that reference existing categories, ignore the ## rest [TF, remcodes] = ismember (oldcats, A.cats); if (! any (TF, 'all')) B = A; return; endif remcodes(! TF) = []; [TF] = ismember (A.code, remcodes); B = A; B.code(TF) = 0; B.cats(remcodes) = []; B.isMissing = B.isMissing | TF; remcodes = sort (remcodes, 'descend'); for code = remcodes(:)' B.code(B.code > code) -= 1; endfor endif endfunction ## -*- texinfo -*- ## @deftypefn {categorical} {@var{B} =} renamecats (@var{A}, @var{newnames}) ## @deftypefnx {categorical} {@var{B} =} renamecats (@var{A}, @var{oldnames}, @var{newnames}) ## ## Rename categories in categorical array. ## ## @code{@var{B} = renamecats (@var{A}, @var{newnames})} renames all the ## categories in @var{A}, without changing any of its values, with the names ## specified in @var{newnames}. @var{newnames} can be specified as a string ## array, a cell array of character vectors, or any type of array that can ## be converted to a cell array of character vectors with the @code{cellstr} ## function, as long as it has the same number of elements as the categories ## in @var{A}. ## ## @code{@var{B} = renamecats (@var{A}, @var{oldnames}, @var{newnames})} ## renames the categories of @var{A} specified in @var{oldnames} with the ## names specified in @var{newnames}. Both @var{oldnames} and ## @var{newnames} can be a string array, a cell array of character vectors, ## or any type of array that can be converted to a cell array of character ## vectors with the @code{cellstr} function, as long as they have the same ## number of elements. @var{oldnames} must specify a subset of existing ## categories in @var{A}. ## ## When renaming a single category, both @var{oldnames} and @var{newnames} ## can also be specified as character vectors. ## ## @end deftypefn function B = renamecats (A, varargin) ## Check input arguments if (nargin < 2) error ("categorical.renamecats: too few input arguments."); endif ## Process 2 input arguments if (nargin == 2) oldnames = A.cats; newnames = varargin{1}; ## Some input validation if (isempty (newnames)) ## Handle MATLAB compatibility here if (iscell (newnames)) B = A; return; endif error ("categorical.renamecats: NEWNAMES cannot be empty."); elseif (isnumeric (newnames) || islogical (newnames)) error (strcat ("categorical.renamecats: NEWNAMES cannot be", ... " numeric or logical.")); endif ## Convert to cellstring if (! iscellstr (newnames)) try newnames = cellstr (newnames); catch error (strcat ("categorical.renamecats: NEWNAMES", ... " cannot be converted to cellstr.")); end_try_catch endif ## Make sure categories match if (numel (oldnames) != numel (newnames)) error (strcat ("categorical.renamecats: NEWNAMES must equal the", ... " number of existing categories in input array.")); endif ## Process 3 input arguments else oldnames = varargin{1}; newnames = varargin{2}; ## OLDNAMES input validation if (isempty (oldnames) && ! iscell (oldnames)) error ("categorical.renamecats: OLDNAMES cannot be empty."); elseif (isnumeric (oldnames) || islogical (oldnames)) error (strcat ("categorical.renamecats: OLDNAMES cannot be", ... " numeric or logical.")); endif ## Convert to cellstring if (! iscellstr (oldnames)) try oldnames = cellstr (oldnames); catch error (strcat ("categorical.renamecats: OLDNAMES", ... " cannot be converted to cellstr.")); end_try_catch endif ## NEWNAMES input validation if (isempty (newnames) && ! iscell (newnames)) error ("categorical.renamecats: NEWNAMES cannot be empty."); elseif (isnumeric (newnames) || islogical (newnames)) error (strcat ("categorical.renamecats: NEWNAMES cannot be", ... " numeric or logical.")); endif ## Convert to cellstring if (! iscellstr (newnames)) try newnames = cellstr (newnames); catch error (strcat ("categorical.renamecats: NEWNAMES", ... " cannot be converted to cellstr.")); end_try_catch endif ## Make sure category names match if (numel (oldnames) != numel (newnames)) error (strcat ("categorical.renamecats: OLDNAMES and NEWNAMES", ... " must have the same number of elements.")); endif endif ## Find and rename existing categories [TF, index] = ismember (oldnames, A.cats); if (! all (TF)) error (strcat ("categorical.renamecats: OLDNAMES must be", ... " a subset of existing categories.")); endif B = A; B.cats(index) = newnames; endfunction ## -*- texinfo -*- ## @deftypefn {categorical} {@var{B} =} reordercats (@var{A}) ## @deftypefnx {categorical} {@var{B} =} reordercats (@var{A}, @var{neworder}) ## ## Reorder categories in categorical array. ## ## @code{@var{B} = reordercats (@var{A})} reorders the categories of @var{A} ## in alphanumeric order. ## ## @code{@var{B} = reordercats (@var{A}, @var{neworder})} reorders the ## categories of @var{A} according to the order specified by @var{neworder}, ## which must define a permutation of @code{categories (@var{A})}. ## ## @var{neworder} can be a numeric vector, a string array, a cell array of ## character vectors, or any type of array that can be converted to a cell ## array of character vectors with the @code{cellstr} function as long as it ## indexes all existing categories in @var{A}. ## ## @end deftypefn function B = reordercats (A, varargin) ## Get NEWORDER if (nargin == 1) neworder = sort (A.cats); else neworder = varargin{1}; ## NEWORDER input validation if (isnumeric (neworder)) catidx = 1:numel (A.cats); if (! (isvector (neworder) && neworder > 0 && ... fix (neworder) == neworder)) error (strcat ("categorical.reordercats: NEWORDER numeric", ... " input must be a vector of positive integers.")); endif if (! isequal (sort (neworder)(:), catidx(:))) error (strcat ("categorical.reordercats: NEWORDER numeric", ... " input must index a permutation of existing", ... " categories.")); endif neworder = A.cats(neworder); elseif (isempty (neworder)) error ("categorical.reordercats: NEWORDER cannot be empty."); elseif (islogical (neworder)) error ("categorical.reordercats: NEWORDER cannot be logical."); elseif (ischar (neworder)) error (strcat ("categorical.reordercats: NEWORDER cannot be", ... " a character vector.")); endif ## Convert to cellstring if (! iscellstr (neworder)) try neworder = cellstr (varargin{1}); catch error (strcat ("categorical.reordercats: NEWORDER", ... " cannot be converted to cellstr.")); end_try_catch endif ## Make sure categories match if (! all (ismember (neworder, A.cats))) error (strcat ("categorical.reordercats: NEWORDER must contain", ... " the same set with the existing categories.")); endif endif ## Reorder [~, newidx] = ismember (A.cats, neworder); B = A; B.code(! B.isMissing) = newidx(A.code(! A.isMissing)); B.cats = neworder(:); endfunction ## -*- texinfo -*- ## @deftypefn {categorical} {@var{B} =} setcats (@var{A}, @var{newcats}) ## ## Set categories in categorical array. ## ## @code{@var{B} = setcats (@var{A}, @var{newcats})} sets categories in the ## categorical array @var{B} according to the elements of the input array ## @var{A} and the categories specified by @var{newcats} according to the ## following rules: ## ## @itemize ## @item Any element of @var{A} that corresponds to a category listed in ## @var{newcats} is copied to @var{B} with the same categorical value. ## @item Any categories of @var{A} not listed in @var{newcats} are not ## copied to @var{B} and the corresponding elements of @var{B} are ## undefined. ## @item New categories listed in @var{newcats} that are not present in ## @var{A} are added in @var{B}, but without any elements equal to these ## new categories. ## @end itemize ## ## @var{newcats} can be a string array, a cell array of character vectors ## or any type of array that can be converted to a cell array of character ## vectors with the @code{cellstr} function. When setting a single new ## category, @var{newcats} can also be specified as a character vector. ## ## @end deftypefn function B = setcats (A, newcats) ## Check input arguments if (nargin < 2) error ("categorical.setcats: too few input arguments."); endif ## NEWCATS input validation if (isempty (newcats)) ## Empty cell removes all categories if (iscell (newcats)) B = A; B.cats = {}; sz = size (A); B.code = uint16 (zeros (sz)); B.isMissing = true (sz); return; endif error ("categorical.setcats: NEWCATS cannot be empty."); elseif (isnumeric (newcats) || islogical (newcats)) error ("categorical.setcats: NEWCATS cannot be numeric or logical."); endif ## Convert to cellstring if (! iscellstr (newcats)) try newcats = cellstr (newcats); catch error (strcat ("categorical.setcats: NEWCATS cannot be", ... " converted to cellstr.")); end_try_catch endif ## Find existing categories [TF, index] = ismember (A.cats, newcats); ## Remove and reorder categories if (all (TF, 'all')) B = A; else B = removecats (A, A.cats(! TF)); endif [TF, index] = ismember (newcats, B.cats); index(! TF) = []; B = reordercats (B, B.cats(index)); [TF, index] = ismember (newcats, B.cats); index(! TF) = []; ## For each new category added before previously existing categories ## we need to increase the code indexing respectively for i = 1:numel (TF) if (! TF(i)) B.code(B.code >= i) += 1; endif endfor B.cats = newcats(:); endfunction ## -*- texinfo -*- ## @deftypefn {categorical} {@var{C} =} times (@var{A}, @var{B}) ## ## Combine categorical arrays. ## ## @code{@var{C} = times (@var{A}, @var{B})} is the equivalent of the syntax ## @code{@var{C} = @var{A} .* @var{B}} and returns a categorical array whose ## categories are the Cartesian product of the categories in @var{A} and ## @var{B} and each element is indexed to a new category which is the ## combination of the categories of the corresponding elements in @var{A} ## and @var{B}. ## ## @var{A} and @var{B} must be of common size or scalar categorical arrays. ## ## @end deftypefn function C = times (A, B) ## Check input arguments if (nargin < 2) error ("categorical.times: too few input arguments."); endif if (! isa (A, 'categorical') || ! isa (B, 'categorical')) error ("categorical.times: A and B must be categorical arrays."); endif newcats = {}; n_Bcats = numel (B.cats); for i = 1:numel (A.cats) for j = 1:n_Bcats newcats = [newcats; strjoin({A.cats{i}, B.cats{j}})]; endfor endfor C = addcats (categorical, newcats); C.code = A.code .* n_Bcats - (n_Bcats - B.code); C.isMissing = A.isMissing | B.isMissing; endfunction endmethods ################################################################################ ## ** Relational Operations ** ## ################################################################################ ## Available Methods ## ## ## ## 'eq' 'ge' 'gt' 'le' ## ## 'lt' 'ne' ## ## ## ################################################################################ methods (Access = public) ## -*- texinfo -*- ## @deftypefn {categorical} {@var{TF} =} eq (@var{A}, @var{B}) ## ## Equality for categorical arrays. ## ## @code{@var{TF} = eq (@var{A}, @var{B})} is the equivalent of the syntax ## @code{@var{TF} = @var{A} == @var{B}} and returns a logical array of the ## same size as the largest input with its elements set to @qcode{true} ## where the corresponding elements of @var{A} and @var{B} are equal and set ## to @qcode{false} where they are not. @var{A} and @var{B} must be size ## compatible, which translates to they can be the same size, one can be ## scalar, or for every dimension, their dimension sizes must be equal or ## one of them must be 1. ## ## If categorical arrays @var{A} and @var{B} are ordinal, they must have ## the same set and ordering of categories. If neither are ordinal, the ## category names of each pair of elements are compared. Hence, they do ## not need to have the same set of categories. ## ## One of the input arguments can also be a character vector, a cellstr ## scalar or a string scalar as long as the other is a categorical array. ## In this case, a logical array of the same size as the categorical array ## is returned in which every element is tested for equality by comparing ## its category with that specified by the string argument. ## ## Undefined elements always return @qcode{false}, since they are not ## comparable to any other categorical values including other undefined ## elements. ## ## @end deftypefn function TF = eq (A, B) if (iscellstr (B) || isa (B, 'string') || ischar (B)) B = cellstr (B); if (! isscalar (B)) error (strcat ("categorical.eq: any text representation must", ... " specify a single categorical value.")); endif TF = strcmp (B, cellstr (A)); elseif (iscellstr (A) || isa (A, 'string') || ischar (A)) A = cellstr (A); if (! isscalar (A)) error (strcat ("categorical.eq: any text representation must", ... " specify a single categorical value.")); endif TF = strcmp (A, cellstr (B)); elseif (iscategorical (A) && iscategorical (B)) if (A.isOrdinal && B.isOrdinal) ## Check that both categorical arrays have the same categories ## and they are in the same order cats = cellfun (@(x) categories (x), {A, B}, 'UniformOutput', false); if (! isequal (cats{:})) error (strcat ("categorical.eq: comparison between ordinal", ... " arrays requires that both have the same", ... " categories, which must be ordered in the", ... " same way.")); endif TF = double (A) == double (B); elseif (A.isOrdinal || B.isOrdinal) error (strcat ("categorical.eq: cannot compare a categorical", ... " array that is ordinal with one that is not.")); else [A, B, err] = csize_expand (A, B); if (err) error ("categorical.eq: arrays have incompatible sizes."); endif TF = strcmp (cellstr (A), cellstr (B)); endif else error (strcat ("categorical.eq: comparison is not defined between", ... " '%s' and '%s' arrays."), class (A), class (B)); endif endfunction ## -*- texinfo -*- ## @deftypefn {categorical} {@var{TF} =} ge (@var{A}, @var{B}) ## ## Greater than or equal to for ordinal categorical arrays. ## ## @code{@var{TF} = ge (@var{A}, @var{B})} is the equivalent of the syntax ## @code{@var{TF} = @var{A} >= @var{B}} and returns a logical array of the ## same size as the largest input with its elements set to @qcode{true} ## where the corresponding elements of @var{A} are greater than or equal to ## @var{B} and set to @qcode{false} where they are not. @var{A} and @var{B} ## must be size compatible, which translates to they can be the same size, ## one can be scalar, or for every dimension, their dimension sizes must be ## equal or one of them must be 1. ## ## If categorical arrays @var{A} and @var{B} are both ordinal, they must ## have the same set and ordering of categories. Unordered categorical ## arrays cannot be compared for greater than or equal to inequality. ## ## One of the input arguments can also be a character vector, a cellstr ## scalar or a string scalar as long as the other is a categorical array. ## In this case, a logical array of the same size as the categorical array ## is returned in which every element is tested for greater than or equal to ## inequality by comparing its category with that specified by the string ## argument. ## ## Undefined elements always return @qcode{false}, since they are not ## comparable to any other categorical values including other undefined ## elements. ## ## @end deftypefn function TF = ge (A, B) if (iscellstr (B) || isa (B, 'string') || ischar (B)) if (! A.isOrdinal) error ("categorical.ge: categorical array is not ordinal."); endif B = cellstr (B); if (! isscalar (B)) error (strcat ("categorical.ge: any text representation must", ... " specify a single categorical value.")); endif code = find (ismember (categories (A), B)); if (isempty (code)) error ("categorical.ge: category does not exist in array."); endif TF = double (A) >= code; elseif (iscellstr (A) || isa (A, 'string') || ischar (A)) if (! B.isOrdinal) error ("categorical.ge: categorical array is not ordinal."); endif A = cellstr (A); if (! isscalar (A)) error (strcat ("categorical.ge: any text representation must", ... " specify a single categorical value.")); endif code = find (ismember (categories (B), A)); if (isempty (code)) error ("categorical.ge: category does not exist in array."); endif TF = code >= double (B); elseif (! iscategorical (A) || ! iscategorical (B)) error (strcat ("categorical.ge: relational comparison is not", ... " defined between '%s' and '%s' arrays."), ... class (A), class (B)); elseif (! A.isOrdinal || ! B.isOrdinal) error (strcat ("categorical.ge: relational comparison is not", ... " allowed between non-ordinal categorical arrays.")); else TF = double (A) >= double (B); endif endfunction ## -*- texinfo -*- ## @deftypefn {categorical} {@var{TF} =} gt (@var{A}, @var{B}) ## ## Greater than for ordinal categorical arrays. ## ## @code{@var{TF} = gt (@var{A}, @var{B})} is the equivalent of the syntax ## @code{@var{TF} = @var{A} > @var{B}} and returns a logical array of the ## same size as the largest input with its elements set to @qcode{true} ## where the corresponding elements of @var{A} are greater than @var{B} and ## set to @qcode{false} where they are not. @var{A} and @var{B} must be ## size compatible, which translates to they can be the same size, one can ## be scalar, or for every dimension, their dimension sizes must be equal or ## one of them must be 1. ## ## If categorical arrays @var{A} and @var{B} are both ordinal, they must ## have the same set and ordering of categories. Unordered categorical ## arrays cannot be compared for greater than inequality. ## ## One of the input arguments can also be a character vector, a cellstr ## scalar or a string scalar as long as the other is a categorical array. ## In this case, a logical array of the same size as the categorical array ## is returned in which every element is tested for greater than inequality ## by comparing its category with that specified by the string argument. ## ## Undefined elements always return @qcode{false}, since they are not ## comparable to any other categorical values including other undefined ## elements. ## ## @end deftypefn function TF = gt (A, B) if (iscellstr (B) || isa (B, 'string') || ischar (B)) if (! A.isOrdinal) error ("categorical.gt: categorical array is not ordinal."); endif B = cellstr (B); if (! isscalar (B)) error (strcat ("categorical.gt: any text representation must", ... " specify a single categorical value.")); endif code = find (ismember (categories (A), B)); if (isempty (code)) error ("categorical.gt: category does not exist in array."); endif TF = double (A) > code; elseif (iscellstr (A) || isa (A, 'string') || ischar (A)) if (! B.isOrdinal) error ("categorical.gt: categorical array is not ordinal."); endif A = cellstr (A); if (! isscalar (A)) error (strcat ("categorical.gt: any text representation must", ... " specify a single categorical value.")); endif code = find (ismember (categories (B), A)); if (isempty (code)) error ("categorical.gt: category does not exist in array."); endif TF = code > double (B); elseif (! iscategorical (A) || ! iscategorical (B)) error (strcat ("categorical.gt: relational comparison is not", ... " defined between '%s' and '%s' arrays."), ... class (A), class (B)); elseif (! A.isOrdinal || ! B.isOrdinal) error (strcat ("categorical.gt: relational comparison is not", ... " allowed between non-ordinal categorical arrays.")); else TF = double (A) > double (B); endif endfunction ## -*- texinfo -*- ## @deftypefn {categorical} {@var{TF} =} le (@var{A}, @var{B}) ## ## Less than or equal to for ordinal categorical arrays. ## ## @code{@var{TF} = le (@var{A}, @var{B})} is the equivalent of the syntax ## @code{@var{TF} = @var{A} <= @var{B}} and returns a logical array of the ## same size as the largest input with its elements set to @qcode{true} ## where the corresponding elements of @var{A} are less than or equal to ## @var{B} and set to @qcode{false} where they are not. @var{A} and @var{B} ## must be size compatible, which translates to they can be the same size, ## one can be scalar, or for every dimension, their dimension sizes must be ## equal or one of them must be 1. ## ## If categorical arrays @var{A} and @var{B} are both ordinal, they must ## have the same set and ordering of categories. Unordered categorical ## arrays cannot be compared for less than or equal to inequality. ## ## One of the input arguments can also be a character vector, a cellstr ## scalar or a string scalar as long as the other is a categorical array. ## In this case, a logical array of the same size as the categorical array ## is returned in which every element is tested for less than or equal to ## inequality by comparing its category with that specified by the string ## argument. ## ## Undefined elements always return @qcode{false}, since they are not ## comparable to any other categorical values including other undefined ## elements. ## ## @end deftypefn function TF = le (A, B) if (iscellstr (B) || isa (B, 'string') || ischar (B)) if (! A.isOrdinal) error ("categorical.le: categorical array is not ordinal."); endif B = cellstr (B); if (! isscalar (B)) error (strcat ("categorical.le: any text representation must", ... " specify a single categorical value.")); endif code = find (ismember (categories (A), B)); if (isempty (code)) error ("categorical.le: category does not exist in array."); endif TF = double (A) <= code; elseif (iscellstr (A) || isa (A, 'string') || ischar (A)) if (! B.isOrdinal) error ("categorical.le: categorical array is not ordinal."); endif A = cellstr (A); if (! isscalar (A)) error (strcat ("categorical.le: any text representation must", ... " specify a single categorical value.")); endif code = find (ismember (categories (B), A)); if (isempty (code)) error ("categorical.le: category does not exist in array."); endif TF = code <= double (B); elseif (! iscategorical (A) || ! iscategorical (B)) error (strcat ("categorical.le: relational comparison is not", ... " defined between '%s' and '%s' arrays."), ... class (A), class (B)); elseif (! A.isOrdinal || ! B.isOrdinal) error (strcat ("categorical.le: relational comparison is not", ... " allowed between non-ordinal categorical arrays.")); else TF = double (A) <= double (B); endif endfunction ## -*- texinfo -*- ## @deftypefn {categorical} {@var{TF} =} lt (@var{A}, @var{B}) ## ## Less than for ordinal categorical arrays. ## ## @code{@var{TF} = lt (@var{A}, @var{B})} is the equivalent of the syntax ## @code{@var{TF} = @var{A} < @var{B}} and returns a logical array of the ## same size as the largest input with its elements set to @qcode{true} ## where the corresponding elements of @var{A} are less than @var{B} and ## set to @qcode{false} where they are not. @var{A} and @var{B} must be ## size compatible, which translates to they can be the same size, one can ## be scalar, or for every dimension, their dimension sizes must be equal or ## one of them must be 1. ## ## If categorical arrays @var{A} and @var{B} are both ordinal, they must ## have the same set and ordering of categories. Unordered categorical ## arrays cannot be compared for less than inequality. ## ## One of the input arguments can also be a character vector, a cellstr ## scalar or a string scalar as long as the other is a categorical array. ## In this case, a logical array of the same size as the categorical array ## is returned in which every element is tested for less than inequality ## by comparing its category with that specified by the string argument. ## ## Undefined elements always return @qcode{false}, since they are not ## comparable to any other categorical values including other undefined ## elements. ## ## @end deftypefn function TF = lt (A, B) if (iscellstr (B) || isa (B, 'string') || ischar (B)) if (! A.isOrdinal) error ("categorical.lt: categorical array is not ordinal."); endif B = cellstr (B); if (! isscalar (B)) error (strcat ("categorical.lt: any text representation must", ... " specify a single categorical value.")); endif code = find (ismember (categories (A), B)); if (isempty (code)) error ("categorical.lt: category does not exist in array."); endif TF = double (A) < code; elseif (iscellstr (A) || isa (A, 'string') || ischar (A)) if (! B.isOrdinal) error ("categorical.lt: categorical array is not ordinal."); endif A = cellstr (A); if (! isscalar (A)) error (strcat ("categorical.lt: any text representation must", ... " specify a single categorical value.")); endif code = find (ismember (categories (B), A)); if (isempty (code)) error ("categorical.lt: category does not exist in array."); endif TF = code < double (B); elseif (! iscategorical (A) || ! iscategorical (B)) error (strcat ("categorical.lt: relational comparison is not", ... " defined between '%s' and '%s' arrays."), ... class (A), class (B)); elseif (! A.isOrdinal || ! B.isOrdinal) error (strcat ("categorical.lt: relational comparison is not", ... " allowed between non-ordinal categorical arrays.")); else TF = double (A) < double (B); endif endfunction ## -*- texinfo -*- ## @deftypefn {categorical} {@var{TF} =} ne (@var{A}, @var{B}) ## ## Not equal for categorical arrays. ## ## @code{@var{TF} = ne (@var{A}, @var{B})} is the equivalent of the syntax ## @code{@var{TF} = @var{A} != @var{B}} and returns a logical array of the ## same size as the largest input with its elements set to @qcode{true} ## where the corresponding elements of @var{A} and @var{B} are not equal and ## set to @qcode{false} where they are equal. @var{A} and @var{B} must be ## size compatible, which translates to they can be the same size, one can ## be scalar, or for every dimension, their dimension sizes must be equal or ## one of them must be 1. ## ## If categorical arrays @var{A} and @var{B} are ordinal, they must have ## the same set and ordering of categories. If neither are ordinal, the ## category names of each pair of elements are compared. Hence, they do ## not need to have the same set of categories. ## ## One of the input arguments can also be a character vector, a cellstr ## scalar or a string scalar as long as the other is a categorical array. ## In this case, a logical array of the same size as the categorical array ## is returned in which every element is tested for inequality by comparing ## its category with that specified by the string argument. ## ## Undefined elements always return @qcode{true}, since they are not ## comparable to any other categorical values including other undefined ## elements. ## ## @end deftypefn function TF = ne (A, B) if (iscellstr (B) || isa (B, 'string') || ischar (B)) B = cellstr (B); if (! isscalar (B)) error (strcat ("categorical.ne: any text representation must", ... " specify a single categorical value.")); endif TF = ! strcmp (B, cellstr (A)); elseif (iscellstr (A) || isa (A, 'string') || ischar (A)) A = cellstr (A); if (! isscalar (A)) error (strcat ("categorical.ne: any text representation must", ... " specify a single categorical value.")); endif TF = ! strcmp (A, cellstr (B)); elseif (iscategorical (A) && iscategorical (B)) if (A.isOrdinal && B.isOrdinal) ## Check that both categorical arrays have the same categories ## and they are in the same order cats = cellfun (@(x) categories (x), {A, B}, 'UniformOutput', false); if (! isequal (cats{:})) error (strcat ("categorical.ne: comparison between ordinal", ... " arrays requires that both have the same", ... " categories, which must be ordered in the", ... " same way.")); endif TF = double (A) != double (B); elseif (A.isOrdinal || B.isOrdinal) error (strcat ("categorical.ne: cannot compare a categorical", ... " array that is ordinal with one that is not.")); else [A, B, err] = csize_expand (A, B); if (err) error ("categorical.ne: arrays have incompatible sizes."); endif TF = ! strcmp (cellstr (A), cellstr (B)); endif else error (strcat ("categorical.ne: comparison is not defined between", ... " '%s' and '%s' arrays."), class (A), class (B)); endif endfunction endmethods ################################################################################ ## ** Arithmetic Operations ** ## ################################################################################ ## Available Methods ## ## ## ## 'min' 'max' 'median' 'mode' ## ## 'histcounts' ## ## ## ################################################################################ methods (Access = public) ## -*- texinfo -*- ## @deftypefn {categorical} {@var{C} =} min (@var{A}) ## @deftypefnx {categorical} {[@var{C}, @var{index}] =} min (@var{A}) ## @deftypefnx {categorical} {@var{C} =} min (@var{A}, @qcode{[]}, @var{dim}) ## @deftypefnx {categorical} {@var{C} =} min (@var{A}, @qcode{[]}, @var{vecdim}) ## @deftypefnx {categorical} {@var{C} =} min (@var{A}, @qcode{[]}, @qcode{'all'}) ## @deftypefnx {categorical} {[@var{C}, @var{index}] =} min (@var{A}, @qcode{[]}, @qcode{'linear'}) ## @deftypefnx {categorical} {[@var{C}, @var{index}] =} min (@var{A}, @qcode{[]}, @dots{}, @qcode{'linear'}) ## @deftypefnx {categorical} {@var{C} =} min (@var{A}, @var{B}) ## @deftypefnx {categorical} {[@dots{}] =} min (@dots{}, @var{missingflag}) ## ## Smallest elements in ordinal categorical arrays. ## ## @code{@var{C} = min (@var{A})} returns the smallest element in ordinal ## categorical vector @var{A}. If @var{A} is a matrix, @code{min (@var{A})} ## returns a row vector with the smallest element from each column. For ## multidimensional arrays, @code{min (@var{A})} operates along the first ## non-singleton dimension. ## ## @code{[@var{C}, @var{index}] = min (@var{A})} also returns the indices of ## the minimum values in @var{index}, which has the same size as @var{C}. ## When the operating dimension contains more than one minimal elements, the ## index of the first one is returned. ## ## @code{@var{C} = min (@var{A}, @qcode{[]}, @var{dim})} operates along the ## dimension specified by @var{dim}. ## ## @code{@var{C} = min (@var{A}, @qcode{[]}, @var{vecdim})} operates on all ## the elements contained in the dimensions specified by @var{vecdim}, which ## must be a numeric vector of non-repeating positive integers. Any values ## in @var{vecdim} indexing dimensions larger that the actual array @var{A} ## are ignored. ## ## @code{@var{C} = min (@var{A}, @qcode{[]}, @qcode{'all'})} operates on all ## dimensions and returns the smallest element in @var{A}. ## ## @code{[@var{C}, @var{index}] = min (@var{A}, @qcode{[]}, @dots{})} also ## returns the first index of the minimum values in @var{index}. The second ## output is only valid when @code{min} operates on a single input array. ## Setting the @qcode{'linear'} flag returns the linear index to the ## corresponding minimum values in @var{A}. ## ## @code{@var{C} = min (@var{A}, @var{B})} returns an ordinal categorical ## array @var{C} with the smallest elements from @var{A} and @var{B}, which ## both must be ordinal categorical arrays of compatible sizes with the same ## set and ordering of categories. Compatible size means that @var{A} and ## @var{B} can be the same size, one can be scalar, or for every dimension, ## their dimension sizes must be equal or one of them must be 1. ## ## @code{[@dots{}] = min (@dots{}, @var{missingflag})} specifies how to ## handle undefined elements in any of the previous syntaxes. ## @var{missingflag} must be a character vector or a string scalar with one ## of the following values: ## ## @itemize ## @item @qcode{'omitundefined'}, which is the default, ignores all ## undefined elements and returns the minimum of the remaining elements. ## If all elements along the operating dimension are undefined, then it ## returns an undefined element. @qcode{'omitnan'} can also be used as ## equivalent to @qcode{'omitundefined'}. ## @item @qcode{'includeundefined'} returns an undefined element if there ## are any undefined elements along the operating dimension. ## @qcode{'includenan'} can also be used as equivalent to ## @qcode{'includeundefined'}. ## @end itemize ## ## @end deftypefn function [C, index] = min (A, B = [], varargin) ## Check for ordinal categorical array if (! A.isOrdinal) error ("categorical.min: categorical array A is not ordinal."); endif if (numel (varargin) > 3) error ("categorical.min: too many input arguments."); endif ## Handle categorical specific missing flag idx = strcmp ('omitundefined', varargin); if (any (idx)) varargin(idx) = 'omitnan'; endif idx = strcmp ('includeundefined', varargin); if (any (idx)) varargin(idx) = 'includenan'; endif ## Create output array C = A; ## Minimum of one array if (isempty (B) && isa (B, 'double')) A_d = double (A); if (nargin == 1) [C_d, index] = min (A_d); else [C_d, index] = min (A_d, [], varargin{:}); endif ## Fix missing codes C.isMissing = isnan (C_d); C.code = uint16 (C_d); ## Minimum of two arrays else ## No second output allowed if (nargout > 1) error (strcat ("categorical.min: two output arguments are", ... " not supported for two input arrays.")); endif ## Check for ordinal categorical array if (! isa (B, 'categorical')) error ("categorical.min: input array B must be categorical."); endif if (! B.isOrdinal) error ("categorical.min: categorical array B must be ordinal."); endif ## Check for same categories (including their order) Acats = categories (A); Bcats = categories (B); if (! isequal (Acats, Bcats)) error (strcat ("categorical.min: categorical arrays must have", ... " the same set of categories, including their", ... " order.")); endif ## Process codes and missing values A_d = double (A); B_d = double (B); C_d = min (A_d, B_d, varargin{:}); ## Fix missing codes C.isMissing = isnan (C_d); C.code = uint16 (C_d); endif endfunction ## -*- texinfo -*- ## @deftypefn {categorical} {@var{C} =} max (@var{A}) ## @deftypefnx {categorical} {[@var{C}, @var{index}] =} max (@var{A}) ## @deftypefnx {categorical} {@var{C} =} max (@var{A}, @qcode{[]}, @var{dim}) ## @deftypefnx {categorical} {@var{C} =} max (@var{A}, @qcode{[]}, @var{vecdim}) ## @deftypefnx {categorical} {@var{C} =} max (@var{A}, @qcode{[]}, @qcode{'all'}) ## @deftypefnx {categorical} {[@var{C}, @var{index}] =} max (@var{A}, @qcode{[]}, @qcode{'linear'}) ## @deftypefnx {categorical} {[@var{C}, @var{index}] =} max (@var{A}, @qcode{[]}, @dots{}, @qcode{'linear'}) ## @deftypefnx {categorical} {@var{C} =} max (@var{A}, @var{B}) ## @deftypefnx {categorical} {[@dots{}] =} max (@dots{}, @var{missingflag}) ## ## Largest elements in ordinal categorical arrays. ## ## @code{@var{C} = max (@var{A})} returns the largest element in ordinal ## categorical vector @var{A}. If @var{A} is a matrix, @code{max (@var{A})} ## returns a row vector with the largest element from each column. For ## multidimensional arrays, @code{max (@var{A})} operates along the first ## non-singleton dimension. ## ## @code{[@var{C}, @var{index}] = max (@var{A})} also returns the indices of ## the maximum values in @var{index}, which has the same size as @var{C}. ## When the operating dimension contains more than one maximal elements, the ## index of the first one is returned. ## ## @code{@var{C} = max (@var{A}, @qcode{[]}, @var{dim})} operates along the ## dimension specified by @var{dim}. ## ## @code{@var{C} = max (@var{A}, @qcode{[]}, @var{vecdim})} operates on all ## the elements contained in the dimensions specified by @var{vecdim}, which ## must be a numeric vector of non-repeating positive integers. Any values ## in @var{vecdim} indexing dimensions larger that the actual array @var{A} ## are ignored. ## ## @code{@var{C} = max (@var{A}, @qcode{[]}, @qcode{'all'})} operates on all ## dimensions and returns the largest element in @var{A}. ## ## @code{[@var{C}, @var{index}] = max (@var{A}, @qcode{[]}, @dots{})} also ## returns the first index of the maximum values in @var{index}. The second ## output is only valid when @code{max} operates on a single input array. ## Setting the @qcode{'linear'} flag returns the linear index to the ## corresponding maximum values in @var{A}. ## ## @code{@var{C} = max (@var{A}, @var{B})} returns an ordinal categorical ## array @var{C} with the largest elements from @var{A} and @var{B}, which ## both must be ordinal categorical arrays of compatible sizes with the same ## set and ordering of categories. Compatible size means that @var{A} and ## @var{B} can be the same size, one can be scalar, or for every dimension, ## their dimension sizes must be equal or one of them must be 1. ## ## @code{[@dots{}] = max (@dots{}, @var{missingflag})} specifies how to ## handle undefined elements in any of the previous syntaxes. ## @var{missingflag} must be a character vector or a string scalar with one ## of the following values: ## ## @itemize ## @item @qcode{'omitundefined'}, which is the default, ignores all ## undefined elements and returns the maximum of the remaining elements. If ## all elements along the operating dimension are undefined, then it returns ## an undefined element. @qcode{'omitnan'} may also be used as equivalent ## to @qcode{'omitundefined'}. ## @item @qcode{'includeundefined'} returns an undefined element if there ## any undefined elements along the operating dimension. ## @qcode{'includenan'} may also be used as equivalent to ## @qcode{'includeundefined'}. ## @end itemize ## ## @end deftypefn function [C, index] = max (A, B = [], varargin) ## Check for ordinal categorical array if (! A.isOrdinal) error ("categorical.max: categorical array A is not ordinal."); endif if (numel (varargin) > 3) error ("categorical.max: too many input arguments."); endif ## Handle categorical specific missing flag idx = strcmp ('omitundefined', varargin); if (any (idx)) varargin(idx) = 'omitnan'; endif idx = strcmp ('includeundefined', varargin); if (any (idx)) varargin(idx) = 'includenan'; endif ## Create output array C = A; ## Minimum of one array if (isempty (B) && isa (B, 'double')) A_d = double (A); if (nargin == 1) [C_d, index] = max (A_d); else [C_d, index] = max (A_d, [], varargin{:}); endif ## Fix missing codes C.isMissing = isnan (C_d); C.code = uint16 (C_d); ## Minimum of two arrays else ## No second output allowed if (nargout > 1) error (strcat ("categorical.max: two output arguments are", ... " not supported for two input arrays.")); endif ## Check for ordinal categorical array if (! isa (B, 'categorical')) error ("categorical.max: input array B must be categorical."); endif if (! B.isOrdinal) error ("categorical.max: categorical array B must be ordinal."); endif ## Check for same categories (including their order) Acats = categories (A); Bcats = categories (B); if (! isequal (Acats, Bcats)) error (strcat ("categorical.max: categorical arrays must have", ... " the same set of categories, including their", ... " order.")); endif ## Process codes and missing values A_d = double (A); B_d = double (B); C_d = max (A_d, B_d, varargin{:}); ## Fix missing codes C.isMissing = isnan (C_d); C.code = uint16 (C_d); endif endfunction ## -*- texinfo -*- ## @deftypefn {categorical} {@var{B} =} median (@var{A}) ## @deftypefnx {categorical} {@var{B} =} median (@var{A}, @var{dim}) ## @deftypefnx {categorical} {@var{B} =} median (@var{A}, @var{vecdim}) ## @deftypefnx {categorical} {@var{B} =} median (@var{A}, @qcode{'all'}) ## @deftypefnx {categorical} {@var{B} =} median (@dots{}, @var{missingflag}) ## ## Median value of an ordinal categorical array. ## ## @code{@var{B} = median (@var{A})} returns the median of the elements in ## ordinal categorical vector @var{A}. If @var{A} is a matrix, ## @code{median (@var{A})} returns a row vector with the median element ## from each column. For multidimensional arrays, @code{median (@var{A})} ## operates along the first non-singleton dimension. @var{B} is also ## ordinal with the same ordered categories as @var{A}. For even number of ## elements along the operating dimension, the returned median value is ## either the midway category between the two middle elements or the larger ## of the two categories midway between the two middle elements. ## ## @code{@var{B} = median (@var{A}, @var{dim})} operates along the dimension ## specified by @var{dim}. ## ## @code{@var{B} = median (@var{A}, @var{vecdim})} operates on all the ## elements contained in the dimensions specified by @var{vecdim}, which ## must be a numeric vector of non-repeating positive integers. Any values ## in @var{vecdim} indexing dimensions larger that the actual array @var{A} ## are ignored. ## ## @code{@var{C} = median (@var{A}, @qcode{[]}, @qcode{'all'})} operates on ## all dimensions and returns the median element in @var{A}. ## ## @code{@var{C} = median (@dots{}, @var{missingflag})} specifies how to ## handle undefined elements in any of the previous syntaxes. ## @var{missingflag} must be a character vector or a string scalar with one ## of the following values: ## ## @itemize ## @item @qcode{'omitundefined'} ignores all undefined elements and returns ## the median of the remaining elements. If all elements along the ## operating dimension are undefined, then it returns an undefined element. ## @qcode{'omitnan'} may also be used as equivalent to ## @qcode{'omitundefined'}. ## @item @qcode{'includeundefined'}, which is the default, returns an ## undefined element if there any undefined elements along the operating ## dimension. @qcode{'includenan'} may also be used as equivalent to ## @qcode{'includeundefined'}. ## @end itemize ## ## @end deftypefn function B = median (A, varargin) ## Check for ordinal categorical array if (! A.isOrdinal) error ("categorical.median: categorical array A is not ordinal."); endif if (numel (varargin) > 2) error ("categorical.median: too many input arguments."); endif ## Handle categorical specific missing flag idx = strcmp ('omitundefined', varargin); if (any (idx)) varargin(idx) = 'omitnan'; endif idx = strcmp ('includeundefined', varargin); if (any (idx)) varargin(idx) = 'includenan'; endif ## Compute median B_d = median (double (A), varargin{:}); ## Save median to output array B = A; B.isMissing = isnan (B_d); B.code = uint16 (round (B_d)); endfunction ## -*- texinfo -*- ## @deftypefn {categorical} {@var{M} =} mode (@var{A}) ## @deftypefnx {categorical} {[@var{M}, @var{F}] =} mode (@var{A}) ## @deftypefnx {categorical} {[@var{M}, @var{F}, @var{C}] =} mode (@var{A}) ## @deftypefnx {categorical} {[@dots{}] =} mode (@var{A}, @var{dim}) ## @deftypefnx {categorical} {[@dots{}] =} mode (@var{A}, @var{vecdim}) ## @deftypefnx {categorical} {[@dots{}] =} mode (@var{A}, @qcode{'all'}) ## ## Most frequent element in a categorical array. ## ## @code{@var{M} = mode (@var{A})} returns the most frequent element in the ## categorical vector @var{A}. If @var{A} is a matrix, ## @code{mode (@var{A})} returns a row vector with the most frequent element ## from each column. For multidimensional arrays, @code{mode (@var{A})} ## operates along the first non-singleton dimension. @var{B} is also a ## categorical array with the same categories as @var{A}. For multiple ## elements with the same maximum frequency along the operating dimension, ## the element from the category that occurs first in @var{A} is returned. ## ## @code{[@var{M}, @var{F}] = mode (@var{A})} also returns a numeric array ## @var{F}, which has the same size as @var{M} and it contains the number of ## occurrences of each corresponding element of @var{M}. ## ## @code{[@var{M}, @var{F}, @var{C}] = mode (@var{A})} also returns a cell ## array @var{C}, which has the same size as @var{M} and each element is a ## sorted categorical vector of all the values with the same maximum ## frequency of the corresponding element of @var{M}. ## ## @code{@var{B} = mode (@var{A}, @var{dim})} operates along the dimension ## specified by @var{dim}. ## ## @code{@var{B} = mode (@var{A}, @var{vecdim})} operates on all the ## elements contained in the dimensions specified by @var{vecdim}, which ## must be a numeric vector of non-repeating positive integers. Any values ## in @var{vecdim} indexing dimensions larger that the actual array @var{A} ## are ignored. ## ## @code{@var{C} = mode (@var{A}, @qcode{[]}, @qcode{'all'})} operates on ## all dimensions and returns the most frequent element in @var{A}. ## ## @end deftypefn function [M, F, C] = mode (A, dim = []) ## Handle 'all' option first if (strcmpi (dim, 'all')) A = subset (A, ':'); dim = []; endif ## Simple case, only one input argument (or 'all' flag used) if (isempty (dim)) [m, F, c] = mode (double (A)); M = A; M.code = uint16 (m); M.isMissing = isnan (m); C = cell (size (c)); for i = 1:numel (c) tmp = A; tmp.code = uint16 (c{i}); tmp.isMissing = isnan (c{i}); C(i) = tmp; endfor ## Second input argument is either DIM or VECDIM else [m, F, c] = mode (double (A), dim); M = A; M.code = uint16 (m); M.isMissing = isnan (m); C = cell (size (c)); for i = 1:numel (c) tmp = A; tmp.code = uint16 (c{i}); tmp.isMissing = isnan (c{i}); C(i) = tmp; endfor endif endfunction ## -*- texinfo -*- ## @deftypefn {categorical} {@var{N} =} histcounts (@var{A}) ## @deftypefnx {categorical} {@var{N} =} histcounts (@var{A}, @var{cats}) ## @deftypefnx {categorical} {@var{N} =} histcounts (@dots{}, @qcode{'Normalization'}, @var{normtype}) ## @deftypefnx {categorical} {[@var{N}, @var{cats}] =} histcounts (@dots{}) ## ## Histogram bin counts of a categorical array. ## ## @code{@var{N} = histcounts (@var{A})} returns a numeric vector @var{N} ## with the number of elements of each category in @var{A}. @var{A} can be ## a categorical array of any dimensions, but it is converted internally to ## a single column vector. ## ## @code{@var{N} = histcounts (@var{A}, @var{cats})} returns the number of ## elements only for the categories of @var{A} specified in @var{cats}, ## which may be a categorical array, a string array, or a cell array of ## character vectors, as long as it specifies unique existing categories in ## @var{A}. ## ## @code{@var{N} = histcounts (@dots{}, @qcode{'Normalization'}, ## @var{normtype})} specifies how to normalize the histogram values returned ## in @var{N} with any of the following options specified in @var{normtype}: ## ## @itemize ## @item @qcode{'count'}, which is the default, returns the number of ## elements in each category. ## @item @qcode{'countdensity'} is the same as @qcode{'count'}, since the ## bin width in categorical arrays is always equal to 1. ## @item @qcode{'probability'} returns the number of elements in each ## category relative to the total number of elements in @var{A}. ## @item @qcode{'pdf'} is the same as @qcode{'probability'}, since the bin ## width in categorical arrays is always equal to 1. ## @item @qcode{'cumcount'} returns the cumulative number of elements in ## each category and all previous categories. ## @item @qcode{'cdf'} returns the cumulative number of elements in ## each category and all previous categories relative to the total number of ## elements in @var{A}. ## @end itemize ## ## @code{[@var{N}, @var{cats}] = histcounts (@dots{})} also returns the ## corresponding categories of @var{A} for each count in @var{N}. ## @var{cats} is a cell array of character vectors with the same size as ## @var{N}. ## ## @end deftypefn function [N, cats] = histcounts (A, varargin) ## Parse and validate optional Name-Value paired argument optNames = {'Normalization'}; dfValues = {'count'}; [normtype, args] = parsePairedArguments (optNames, dfValues, varargin(:)); vnt = {'count', 'countdensity', 'probability', 'pdf', 'cumcount', 'cdf'}; if (! ismember (normtype, vnt)) error ("categorical.histcounts: invalid 'Normalization' type."); endif ## Check for selected categories if (! isempty (args)) cats = args{1}; if (isa (cats, 'categorical')) cats = categories (cats); elseif (isa (cats, 'string')) cats = cellstr (cats); elseif (! iscellstr (cats)) error (strcat ("categorical.histcounts: invalid", ... " type for CATS input argument.")); endif if (! all (ismember (cats, A.cats))) error (strcat ("categorical.histcounts: input argument", ... " CATS references non-existing categories in A.")); endif else cats = A.cats; endif ## Force cats to row vector cats = cats(:)'; ## Count elements in selected categories codes = A.code(:); ccats = find (ismember (A.cats, cats)); ncats = numel (ccats); N = zeros (1, ncats); for i = 1:ncats N(i) = sum (codes == ccats(i)); endfor ## Apply normalization scheme if (any (strcmp (normtype, {'probability', 'pdf'}))) N = N ./ numel (codes); elseif (strcmp (normtype, 'cumcount')) N = cumsum (N); elseif (strcmp (normtype, 'cdf')) N = cumsum (N ./ numel (codes)); endif endfunction endmethods ################################################################################ ## ** Sort, Filter, and Set Operations ** ## ################################################################################ ## Available Methods ## ## ## ## 'sort' 'sortrows' 'topkrows' 'unique' ## ## 'intersect' 'setdiff' 'setxor' 'union' ## ## ## ################################################################################ methods (Access = public) ## -*- texinfo -*- ## @deftypefn {categorical} {@var{B} =} sort (@var{A}) ## @deftypefnx {categorical} {@var{B} =} sort (@var{A}, @var{dim}) ## @deftypefnx {categorical} {@var{B} =} sort (@var{A}, @var{direction}) ## @deftypefnx {categorical} {@var{B} =} sort (@var{A}, @var{dim}, @var{direction}) ## @deftypefnx {categorical} {@var{B} =} sort (@dots{}, @qcode{'MissingPlacement'}, @var{MP}) ## @deftypefnx {categorical} {[@var{B}, @var{index}] =} sort (@var{A}, @dots{}) ## ## Sort elements in a categorical array. ## ## @code{@var{B} = sort (@var{A})} sorts the categorical array @var{A} in ## ascending order. The sorted array @var{B} has the same categories as ## @var{A}. If @var{A} is a matrix, @code{sort (@var{A})} sorts each column ## of @var{A} in ascending order. For multidimensional arrays, ## @code{mode (@var{A})} sorts along the first non-singleton dimension. ## ## @code{@var{B} = sort (@var{A}, @var{dim})} sorts along the dimension ## specified by @var{dim}. ## ## @code{@var{B} = sort (@var{A}, @var{direction})} also specifies the ## sorting direction, which can be either @qcode{'ascend'} (default) or ## @qcode{'descend'}. ## ## @code{@var{B} = sort (@dots{}, @qcode{'MissingPlacement'}, @var{MP})} ## specifies where to place the missing elements (@qcode{}) ## returned in @var{B} with any of the following options specified in ## @var{MP}: ## ## @itemize ## @item @qcode{'auto'}, which is the default, places missing elements last ## for ascending sort and first for descending sort. ## @item @qcode{'first'} places missing elements first. ## @item @qcode{'last'} places missing elements last. ## @end itemize ## ## @code{[@var{B}, @var{index}] = sort (@var{A}, @dots{})} also returns a ## sorting index containing the original indices of the elements in the ## sorted array. ## ## @itemize ## @item If @var{A} is a vector, then @var{index} contains the original ## linear indices of the elements in the sorted vector @var{B} such that ## @code{@var{B} = @var{A}(@var{index})}. ## @item If @var{A} is an @math{M*N} matrix and @qcode{@var{dim} = 1}, then ## @var{index} contains the original row indices of the elements in the ## sorted vector @var{B} such that for @qcode{j = 1:N}, ## @code{@var{B}(:,j) = @var{A}(@var{index}(:,j),j)}. ## @end itemize ## ## @end deftypefn function [B, index] = sort (A, varargin) ## Parse and validate optional 'MissingPlacement' paired argument optNames = {'MissingPlacement'}; dfValues = {'auto'}; [MP, args] = parsePairedArguments (optNames, dfValues, varargin(:)); if (! ismember (MP, {'auto', 'first', 'last'})) error ("categorical.sort: invalid value for 'MissingPlacement'."); endif ## Force strings to character vectors [args{:}] = convertStringsToChars (args{:}); ## Get direction cid = cellfun (@ischar, args); if (any (cid)) dir = args{cid}; else dir = 'ascend'; endif ## Since codes are positive integers and 0 is used for undefined elements, ## we assume A.code == 0 for NaN. However, in ascending order NaNs go to ## the end, while 0s at the very top and vice versa. So we have to ## mitigate this in tandem with the 'MissingPlacement' option, by setting ## 0s to max(code) + 1 for certain combinations of options. code = A.code; is_nan = code == 0; if (any (is_nan(:))) if ((strcmp (dir, 'ascend') && any (strcmp (MP, {'auto', 'last'}))) || (strcmp (dir, 'descend') && any (strcmp (MP, {'auto', 'first'})))) ## Get new value for missing elements code nan_code = max (code(:)) + 1; code(is_nan) = nan_code; ## Sort values [code, index] = sort (code, args{:}); ## Get indices of missing values and change them back to 0 is_nan = code == nan_code; code(is_nan) = 0; else ## ## Sort values without swaping code for undefined elements [code, index] = sort (code, args{:}); is_nan = code == 0; endif else ## Sort values with no undefined elements [code, index] = sort (code, args{:}); endif ## Populate output categorical array B = A; B.code = uint16 (code); B.isMissing = is_nan; endfunction ## -*- texinfo -*- ## @deftypefn {categorical} {@var{B} =} sortrows (@var{A}) ## @deftypefnx {categorical} {@var{B} =} sortrows (@var{A}, @var{col}) ## @deftypefnx {categorical} {@var{B} =} sortrows (@var{A}, @var{direction}) ## @deftypefnx {categorical} {@var{B} =} sortrows (@var{A}, @var{col}, @var{direction}) ## @deftypefnx {categorical} {@var{B} =} sortrows (@dots{}, @qcode{'MissingPlacement'}, @var{MP}) ## @deftypefnx {categorical} {[@var{B}, @var{index}] =} sortrows (@var{A}, @dots{}) ## ## Sort rows in a categorical array. ## ## @code{@var{B} = sortrows (@var{A})} sorts the rows of the 2-D categorical ## array @var{A} in ascending order. The sorted array @var{B} has the same ## categories as @var{A}. ## ## @code{@var{B} = sortrows (@var{A}, @var{col})} sorts @var{A} according to ## to the columns specified by the numeric vector @var{col}, which must ## explicitly contain non-zero integers whose absolute values index existing ## columns in @var{A}. Positive elements sort the corresponding columns in ## ascending order, while negative elements sort the corresponding columns ## in descending order. ## ## @code{@var{B} = sortrows (@var{A}, @var{direction})} also specifies the ## sorting direction, which can be either @qcode{'ascend'} (default) or ## @qcode{'descend'} applying to all columns in @var{A}. Alternatively, ## @var{direction} can be a cell array of character vectors specifying ## the sorting direction for each individual column of @var{A}, in which ## case the number of elements in @var{direction} must equal the number of ## columns in @var{A}. ## ## @code{@var{B} = sortrows (@var{A}, @var{col}, @var{direction})} sorts the ## categorical array @var{A} according to the columns specified in @var{col} ## using the corresponding sorting direction specified in @var{direction}. ## In this case, the sign of the values in @var{col} is ignored. @var{col} ## and @var{direction} must have the same length, but not necessarily the ## same number of elements as the columns in @var{A}. ## ## @code{@var{B} = sortrows (@dots{}, @qcode{'MissingPlacement'}, @var{MP})} ## specifies where to place the missing elements (@qcode{}) ## returned in @var{B} with any of the following options specified in ## @var{MP}: ## ## @itemize ## @item @qcode{'auto'}, which is the default, places missing elements last ## for ascending sort and first for descending sort. ## @item @qcode{'first'} places missing elements first. ## @item @qcode{'last'} places missing elements last. ## @end itemize ## ## @code{[@var{B}, @var{index}] = sortrows (@var{A}, @dots{})} also returns ## an index vector containing the original row indices of @var{A} in the ## sorted matrix @var{B} such that @code{@var{B} = @var{A}(@var{index},:)}. ## ## @end deftypefn function [B, index] = sortrows (A, varargin) if (ndims (A) != 2) error ("categorical.sortrows: A must be a 2-D matrix."); endif ## Parse and validate optional 'MissingPlacement' paired argument optNames = {'MissingPlacement'}; dfValues = {'auto'}; [MP, args] = parsePairedArguments (optNames, dfValues, varargin(:)); if (! any (strcmp (MP, {'auto', 'first', 'last'}))) error ("categorical.sort: invalid value for 'MissingPlacement'."); endif ## Parse COL / DIRECTION input nc = size (A, 2); col = [1:nc]; # default ascending direction dir_flag = false; if (numel (args) > 2) error ("categorical.sortrows: too many input arguments."); endif if (numel (args) > 0) col = args{1}; if (isnumeric (col)) if (! isvector (col) || fix (col) != col) error (strcat ("categorical.sortrows: COL must be a vector", ... " of nonzero integers indexing columns in A.")); endif if (max (abs (col)) > nc) error ("categorical.sortrows: COL indexes non-existing column."); endif elseif (isvector (col) && (ischar (col) || iscellstr (col) || isa (col, 'string'))) direction = cellstr (col); if (! all (ismember (direction, {'ascend', 'descend'}))) error (strcat ("categorical.sortrows: DIRECTION input must", ... " contain either 'ascend' or 'descend' values.")); endif ## Apply scalar expansion if (isscalar (direction)) direction = repmat (direction, 1, nc); endif if (numel (direction) != nc) error (strcat ("categorical.sortrows: DIRECTION", ... " does not match the columns in A.")); endif ## Assign DIRECTION to COL col = [1:nc]; idx = strcmp (direction, 'descend'); col(idx) = - col(idx); dir_flag = true; else error ("categorical.sortrows: invalid type for COL argument."); endif endif if (numel (args) > 1) if (dir_flag) error ("categorical.sortrows: invalid third input argument."); endif if ((isvector (args{2}) && ischar (args{2})) || isa (args{2}, 'string')) direction = cellstr (args{2}); elseif (isvector (args{2}) && iscellstr (args{2})) direction = args{2}; else error ("categorical.sortrows: invalid type for DIRECTION argument."); endif if (! all (ismember (direction, {'ascend', 'descend'}))) error (strcat ("categorical.sortrows: DIRECTION input must", ... " contain either 'ascend' or 'descend' values.")); endif ## Assign DIRECTION to COL if (isscalar (direction) && strcmp (direction, 'ascend')) col = abs (col); elseif (isscalar (direction) && strcmp (direction, 'descend')) col = - abs (col); else if (numel (direction) != numel (col)) error (strcat ("categorical.sortrows: DIRECTION", ... " does not match the elements in COL.")); endif col = abs (col); idx = strcmp (direction, 'descend'); col(idx) = - col(idx); endif endif ## Since codes are positive integers and 0 is used for undefined elements, ## we assume A.code == 0 for NaN. However, in ascending order NaNs go to ## the end, while 0s at the very top and vice versa. So we have to ## mitigate this in tandem with the 'MissingPlacement' option, by setting ## 0s to max(code) + 1 for certain combinations of options. code = A.code; is_nan = code == 0; if (any (is_nan(:))) ## Get new value for missing elements code nan_code = max (code(:)) + 1; asc_cols = []; des_cols = []; if (any (strcmp (MP, {'auto', 'last'}))) ## Change codes only in ascending columns asc_cols = col(col > 0); if (! isempty (asc_cols)) asc_code = code(:, asc_cols); a_is_nan = asc_code == 0; asc_code(a_is_nan) = nan_code; code(:, asc_cols) = asc_code; endif endif if (any (strcmp (MP, {'auto', 'first'}))) ## Change codes only in descending columns des_cols = abs (col(col < 0)); if (! isempty (des_cols)) des_code = code(:, des_cols); d_is_nan = des_code == 0; des_code(d_is_nan) = nan_code; code(:, des_cols) = des_code; endif endif ## Sort values [code, index] = sortrows (code, col); ## Get indices of missing values and change them back to 0 adcols = [asc_cols, des_cols]; if (! isempty (adcols)) adcode = code(:, [asc_cols, des_cols]); is_nan = adcode == nan_code; adcode(is_nan) = 0; code(:, [asc_cols, des_cols]) = adcode; endif ## Re-index missing values after sorting is_nan = code == 0; else ## Sort values with no undefined elements [code, index] = sortrows (code, col); endif ## Populate output categorical array B = A; B.code = uint16 (code); B.isMissing = is_nan; endfunction ## -*- texinfo -*- ## @deftypefn {categorical} {@var{B} =} topkrows (@var{A}, @var{K}) ## @deftypefnx {categorical} {@var{B} =} topkrows (@var{A}, @var{K}, @var{col}) ## @deftypefnx {categorical} {@var{B} =} topkrows (@var{A}, @var{K}, @var{direction}) ## @deftypefnx {categorical} {@var{B} =} topkrows (@var{A}, @var{K}, @var{col}, @var{direction}) ## ## Top K sorted rows of categorical array. ## ## @code{@var{B} = topkrows (@var{A}, @var{K})} returns the top @var{K} rows ## of the 2-D categorical array @var{A} sorted in descending order as a ## group. ## ## @code{@var{B} = topkrows (@var{A}, @var{K}, @var{col})} returns the top ## @var{K} rows of the 2-D categorical array @var{A} sorted according to the ## columns specified by the numeric vector @var{col}, which must explicitly ## contain non-zero integers whose absolute values index existing columns in ## @var{A}. Positive elements sort the corresponding columns in ascending ## order, while negative elements sort the corresponding columns in ## descending order. ## ## @code{@var{B} = topkrows (@var{A}, @var{K}, @var{direction})} returns the ## top @var{K} rows of the 2-D categorical array @var{A} sorted according to ## @var{direction}, which can be either @qcode{'ascend'} (default) or ## @qcode{'descend'} applying to all columns in @var{A}. Alternatively, ## @var{direction} can be a cell array of character vectors specifying ## the sorting direction for each individual column of @var{A}, in which ## case the number of elements in @var{direction} must equal the number of ## columns in @var{A}. ## ## @code{@var{B} = topkrows (@var{A}, @var{K}, @var{col}, @var{direction})} ## returns the top @var{K} rows of the 2-D categorical array @var{A} sorted ## according to the columns specified in @var{col} using the corresponding ## sorting direction specified in @var{direction}. In this case, the sign ## of the values in @var{col} is ignored. @var{col} and @var{direction} ## must have the same length, but not necessarily the same number of ## elements as the columns in @var{A}. ## ## @end deftypefn function [B, index] = topkrows (A, K, varargin) ## Check input argument if (nargin < 2) error ("categorical.topkrows: too few input arguments."); endif if (! (isscalar (K) && fix (K) == K && K > 0)) error ("categorical.topkrows: K must be a positive integer scalar."); endif ## Sort rows if (numel (varargin) == 0) [B, index] = sortrows (A, 'descend'); else [B, index] = sortrows (A, varargin{:}); endif ## Return top K rows if (K < numel (index)) B = subset (B, 1:K, ':'); index = index(1:K); endif endfunction ## -*- texinfo -*- ## @deftypefn {categorical} {@var{B} =} unique (@var{A}) ## @deftypefnx {categorical} {@var{B} =} unique (@var{A}, @var{setOrder}) ## @deftypefnx {categorical} {@var{B} =} unique (@var{A}, @var{occurrence}) ## @deftypefnx {categorical} {@var{B} =} unique (@var{A}, @var{setOrder}, @var{occurrence}) ## @deftypefnx {categorical} {@var{B} =} unique (@var{A}, @var{occurrence}, @var{setOrder}) ## @deftypefnx {categorical} {@var{B} =} unique (@var{A}, @dots{}, @qcode{'rows'}) ## @deftypefnx {categorical} {[@var{B}, @var{ixA}, @var{ixB}] =} unique (@dots{}) ## ## Unique values in a categorical array. ## ## @code{@var{B} = unique (@var{A})} returns the unique values of the ## categorical array @var{A} in the categorical vector @var{B} sorted ## according to the order of categories in @var{A}. @var{B} retains the ## same categories as @var{A}. If @var{A} is a row vector, then @var{B} ## is also a row vector, otherwise @code{unique} returns a column vector. ## ## @code{@var{B} = unique (@var{A}, @var{setOrder})} returns the unique ## values of the categorical array @var{A} in an order as specified by ## @var{setOrder}, which can be either of the following values: ## ## @itemize ## @item @qcode{'sorted'} (default) returns the unique values sorted in ## ascending order. ## @item @qcode{'stable'} returns the unique values according to their order ## of occurrence. ## @end itemize ## ## @code{@var{B} = unique (@var{A}, @var{occurrence})} returns the unique ## values of the categorical array @var{A} according to their order of ## occurrence. @var{occurrence} can be either of the following values: ## ## @itemize ## @item @qcode{'first'} (default) returns the first occurrence of each ## unique value, i.e. the lowest possible indices are returned. ## @item @qcode{'last'} returns the last occurrence of each unique value, ## i.e. the highest possible indices are returned. ## @end itemize ## ## You can specify @var{setOrder} and @var{occurrence} arguments together. ## ## @code{@var{B} = unique (@var{A}, @dots{}, @qcode{'rows'})} returns the ## unique rows of @var{A} by treating each row as a single entity. The ## @qcode{'rows'} option can be used alone or in any combination with the ## @var{setOrder} and @var{occurrence} arguments. @qcode{'rows'} can be ## placed at any position in the function's argument list after the input ## array @var{A}. However, this syntax is only valid for 2-dimensional ## categorical arrays. ## ## @code{[@var{B}, @var{ixA}, @var{ixB}] = unique (@dots{})} also returns ## index vectors @var{ixA} and @var{ixB} using any of the previous syntaxes. ## @var{ixA} and @var{ixB} map the arrays @var{A} and @var{B} to one another ## such that @qcode{@var{B} = @var{A}(@var{ixA})} and ## @qcode{@var{A} = @var{B}(@var{ixB})}. When the @qcode{'rows'} optional ## argument is specified, then @qcode{@var{B} = @var{A}(@var{ixA},:)} and ## @qcode{@var{A} = @var{B}(@var{ixB},:)}. ## ## @end deftypefn function [B, ixA, ixB] = unique (A, varargin) ## 'legacy' option is not supported if (any (strcmp ('legacy', varargin))) error ("duration.unique: 'legacy' option is not supported."); endif code = double (A); [~, ixA, ixB] = __unique__ (code, varargin{:}); if (any (strcmp ('rows', varargin))) B = subset (A, ixA, ':'); else B = subset (A, ixA); endif endfunction ## -*- texinfo -*- ## @deftypefn {categorical} {@var{C} =} intersect (@var{A}, @var{B}) ## @deftypefnx {categorical} {@var{C} =} intersect (@var{A}, @var{B}, @qcode{'rows'}) ## @deftypefnx {categorical} {[@var{C}, @var{ixA}, @var{ixB}] =} intersect (@dots{}) ## @deftypefnx {categorical} {@dots{} =} intersect (@dots{}, @var{order}) ## ## Set intersection of two categorical arrays. ## ## @code{@var{C} = intersect (@var{A}, @var{B})} returns the unique common ## values of the categorical arrays @var{A} and @var{B}. Either @var{A} or ## @var{B} input arguments may be a character vector, a string array, or a ## cell array of character vectors, which is promoted to a categorical array ## prior to set intersection. If both @var{A} and @var{B} are row vectors, ## then @var{C} is also a row vector, otherwise @code{intersect} returns a ## column vector. ## ## If categorical arrays @var{A} and @var{B} are ordinal, they must have ## the same set and ordering of categories, which is transfered to @var{C}. ## If neither are ordinal, the category names of each pair of elements are ## compared (they do not need to have the same set of categories) in which ## case the categories in @var{C} are the sorted union of the categories in ## @var{A} and @var{B}. ## ## @code{@var{C} = intersect (@var{A}, @var{B}, @qcode{'rows'}} returns the ## unique common rows of the categorical matrices @var{A} and @var{B}, which ## must have the same number of columns. By default, the rows in ## categorical matrix @var{C} are in sorted order. ## ## @code{[@var{C}, @var{ixA}, @var{ixB}] = intersect (@dots{})} also returns ## index vectors @var{ixA} and @var{ixB} such that ## @code{@var{C} = @var{A}(@var{ixA})} and ## @code{@var{C} = @var{B}(@var{ixB})}, unless the @qcode{'rows'} optional ## argument is given, in which case @code{@var{C} = @var{A}(@var{ixA},:)} ## and @code{@var{C} = @var{B}(@var{ixB},:)}. ## ## @code{@dots{} = intersect (@dots{}, @var{order})} also specifies the ## order of the returned unique values. @var{order} may be either ## @qcode{'sorted'}, which is the default behavior, or @qcode{'stable'}, ## in which case the unique values are returned in order of appearance. ## ## @end deftypefn function [C, ixA, ixB] = intersect (A, B, varargin) [C, ixA, ixB] = setop (A, B, 'intersect', varargin{:}); if (! iscategorical (C)) error (C); endif endfunction ## -*- texinfo -*- ## @deftypefn {categorical} {@var{C} =} setdiff (@var{A}, @var{B}) ## @deftypefnx {categorical} {@var{C} =} setdiff (@var{A}, @var{B}, @qcode{'rows'}) ## @deftypefnx {categorical} {[@var{C}, @var{ixA}] =} setdiff (@dots{}) ## @deftypefnx {categorical} {@dots{} =} setdiff (@dots{}, @var{order}) ## ## Set difference of two categorical arrays. ## ## @code{@var{C} = setdiff (@var{A}, @var{B})} returns the unique common ## values of the categorical arrays @var{A} and @var{B}. Either @var{A} or ## @var{B} input arguments may be a character vector, a string array, or a ## cell array of character vectors, which is promoted to a categorical array ## prior to set difference. If both @var{A} and @var{B} are row vectors, ## then @var{C} is also a row vector, otherwise @code{intersect} returns a ## column vector. ## ## If categorical arrays @var{A} and @var{B} are ordinal, they must have ## the same set and ordering of categories, which is transfered to @var{C}. ## If neither are ordinal, the category names of each pair of elements are ## compared (they do not need to have the same set of categories) in which ## case the categories in @var{C} are the sorted union of the categories in ## @var{A} and @var{B}. ## ## @code{@var{C} = setdiff (@var{A}, @var{B}, @qcode{'rows'}} returns the ## unique common rows of the categorical matrices @var{A} and @var{B}, which ## must have the same number of columns. By default, the rows in ## categorical matrix @var{C} are in sorted order. ## ## @code{[@var{C}, @var{ixA}] = setdiff (@dots{})} also returns the index ## vector @var{ixA} such that @code{@var{C} = @var{A}(@var{ixA})}, unless ## the @qcode{'rows'} optional argument is given, in which case ## @code{@var{C} = @var{A}(@var{ixA},:)}. ## ## @code{@dots{} = setdiff (@dots{}, @var{order})} also specifies the ## order of the returned unique values. @var{order} may be either ## @qcode{'sorted'}, which is the default behavior, or @qcode{'stable'}, ## in which case the unique values are returned in order of appearance. ## ## @end deftypefn function [C, ixA] = setdiff (A, B, varargin) [C, ixA] = setop (A, B, 'setdiff', varargin{:}); if (! iscategorical (C)) error (C); endif endfunction ## -*- texinfo -*- ## @deftypefn {categorical} {@var{C} =} setxor (@var{A}, @var{B}) ## @deftypefnx {categorical} {@var{C} =} setxor (@var{A}, @var{B}, @qcode{'rows'}) ## @deftypefnx {categorical} {[@var{C}, @var{ixA}, @var{ixB}] =} setxor (@dots{}) ## @deftypefnx {categorical} {@dots{} =} setxor (@dots{}, @var{order}) ## ## Set exclusive-or of two categorical arrays. ## ## @code{@var{C} = setxor (@var{A}, @var{B})} returns the unique common ## values of the categorical arrays @var{A} and @var{B}. Either @var{A} or ## @var{B} input arguments may be a character vector, a string array, or a ## cell array of character vectors, which is promoted to a categorical array ## prior to set exclusive-or. If both @var{A} and @var{B} are row vectors, ## then @var{C} is also a row vector, otherwise @code{setxor} returns a ## column vector. ## ## If categorical arrays @var{A} and @var{B} are ordinal, they must have ## the same set and ordering of categories, which is transfered to @var{C}. ## If neither are ordinal, the category names of each pair of elements are ## compared (they do not need to have the same set of categories) in which ## case the categories in @var{C} are the sorted union of the categories in ## @var{A} and @var{B}. ## ## @code{@var{C} = setxor (@var{A}, @var{B}, @qcode{'rows'}} returns the ## unique common rows of the categorical matrices @var{A} and @var{B}, which ## must have the same number of columns. By default, the rows in ## categorical matrix @var{C} are in sorted order. ## ## @code{[@var{C}, @var{ixA}, @var{ixB}] = setxor (@dots{})} also returns ## index vectors @var{ixA} and @var{ixB} such that ## @code{@var{C} = @var{A}(@var{ixA})} and ## @code{@var{C} = @var{B}(@var{ixB})}, unless the @qcode{'rows'} optional ## argument is given, in which case @code{@var{C} = @var{A}(@var{ixA},:)} ## and @code{@var{C} = @var{B}(@var{ixB},:)}. ## ## @code{@dots{} = setxor (@dots{}, @var{order})} also specifies the ## order of the returned unique values. @var{order} may be either ## @qcode{'sorted'}, which is the default behavior, or @qcode{'stable'}, ## in which case the unique values are returned in order of appearance. ## ## @end deftypefn function [C, ixA, ixB] = setxor (A, B, varargin) [C, ixA, ixB] = setop (A, B, 'setxor', varargin{:}); if (! iscategorical (C)) error (C); endif endfunction ## -*- texinfo -*- ## @deftypefn {categorical} {@var{C} =} union (@var{A}, @var{B}) ## @deftypefnx {categorical} {@var{C} =} union (@var{A}, @var{B}, @qcode{'rows'}) ## @deftypefnx {categorical} {[@var{C}, @var{ixA}, @var{ixB}] =} union (@dots{}) ## @deftypefnx {categorical} {@dots{} =} union (@dots{}, @var{order}) ## ## Set union of two categorical arrays. ## ## @code{@var{C} = union (@var{A}, @var{B})} returns the unique common ## values of the categorical arrays @var{A} and @var{B}. Either @var{A} or ## @var{B} input arguments may be a character vector, a string array, or a ## cell array of character vectors, which is promoted to a categorical array ## prior to set exclusive-or. If both @var{A} and @var{B} are row vectors, ## then @var{C} is also a row vector, otherwise @code{union} returns a ## column vector. ## ## If categorical arrays @var{A} and @var{B} are ordinal, they must have ## the same set and ordering of categories, which is transfered to @var{C}. ## If neither are ordinal, the category names of each pair of elements are ## compared (they do not need to have the same set of categories) in which ## case the categories in @var{C} are the sorted union of the categories in ## @var{A} and @var{B}. ## ## @code{@var{C} = union (@var{A}, @var{B}, @qcode{'rows'}} returns the ## unique common rows of the categorical matrices @var{A} and @var{B}, which ## must have the same number of columns. By default, the rows in ## categorical matrix @var{C} are in sorted order. ## ## @code{[@var{C}, @var{ixA}, @var{ixB}] = union (@dots{})} also returns ## index vectors @var{ixA} and @var{ixB} such that ## @code{@var{C} = @var{A}(@var{ixA})} and ## @code{@var{C} = @var{B}(@var{ixB})}, unless the @qcode{'rows'} optional ## argument is given, in which case @code{@var{C} = @var{A}(@var{ixA},:)} ## and @code{@var{C} = @var{B}(@var{ixB},:)}. ## ## @code{@dots{} = union (@dots{}, @var{order})} also specifies the ## order of the returned unique values. @var{order} may be either ## @qcode{'sorted'}, which is the default behavior, or @qcode{'stable'}, ## in which case the unique values are returned in order of appearance. ## ## @end deftypefn function [C, ixA, ixB] = union (A, B, varargin) [C, ixA, ixB] = setop (A, B, 'union', varargin{:}); if (! iscategorical (C)) error (C); endif endfunction endmethods ################################################################################ ## ** Array Operations ** ## ################################################################################ ## Available Methods ## ## ## ## 'cat' 'horzcat' 'vertcat' 'repmat' ## ## 'repelem' 'repelems' 'reshape' 'circshift' ## ## 'permute' 'ipermute' 'transpose' 'ctranspose' ## ## ## ################################################################################ methods (Access = public) ## -*- texinfo -*- ## @deftypefn {categorical} {@var{C} =} cat (@var{dim}, @var{A}, @var{B}, @dots{}) ## ## Concatenate categorical arrays. ## ## @code{@var{C} = cat (@var{dim}, @var{A}, @var{B}, @dots{})} concatenates ## categorical arrays @var{A}, @var{B}, @dots{} along dimension @var{dim}. ## All input arrays must have the same size except along the operating ## dimension @var{dim}. Any of the input arrays may also be string arrays ## or cell arrays of character vectors of compatible size. ## ## If any input array is an ordinal categorical array, then all inputs must ## be ordinal categorical arrays with the same set and ordering of ## categories. In this case, @var{C} is also an ordinal categorical array ## with the same set and ordering of categories. If none of the input ## arrays are ordinal, then they do not need to have the same set of ## categories. In this case, categorical array @var{C} contains the union ## of the categories from all input arrays. Protected categorical arrays ## can only be concatenated with other arrays that have the same set of ## categories but not necessarily in the same order. ## ## @end deftypefn function out = cat (dim, varargin) ## Remove empty cell inputs varargin(cellfun (@(x) isempty (x) && iscell (x), varargin)) = []; if (numel (varargin) == 1) out = varargin{1}; return; endif ## Identify non-categorical inputs for reordering categories firstcatid = find (cellfun (@(x) iscategorical (x), varargin), 1); ## Promote everything into categorical arrays args = varargin; [args{:}] = promote (varargin{:}); ## Check that all dimensions except DIM are equal if (fix (dim) != dim | dim < 1) error ("categorical.cat: DIM must be a valid dimension."); endif sz_dim = cellfun (@(x) size (x), args, "UniformOutput", false); n_dims = cellfun (@(x) ndims (x), args, "UniformOutput", false); if (! isequal (n_dims{:})) error ("categorical.cat: dimensions mismatch."); elseif (dim <= n_dims{1}) idx = 1:n_dims{1}; idx(dim) = []; rem_dims = cellfun (@(x) (x(idx)), sz_dim, "UniformOutput", false); if (! isequal (rem_dims{:})) error ("categorical.cat: size mismatch."); endif endif ## If any categorical value is ordinal, all must be is_ordinal = cellfun (@isordinal, args); if (all (is_ordinal)) ## Check that all categorical arrays have the same categories ## and they are in the same order cats = cellfun (@(x) categories (x), args, 'UniformOutput', false); if (! isequal (cats{:})) error (strcat ("categorical.cat: cannot concatenate ordinal", ... " categorical arrays unless they have the same", ... " ordered set of categories.")); endif out = args{1}; fieldArgs = cellfun (@(x) x.code, args, 'UniformOutput', false); out.code = cat (dim, fieldArgs{:}); fieldArgs = cellfun (@(x) x.isMissing, args, 'UniformOutput', false); out.isMissing = cat (dim, fieldArgs{:}); return; elseif (any (is_ordinal)) error (strcat ("categorical.cat: cannot concatenate ordinal", ... " with non-ordinal categorical arrays.")); endif ## If any categorical array is protected, all must have the same ## categories is_protected = cellfun (@isprotected, args); if (any (is_protected)) ## Check that all categorical arrays have the same categories ## but they are not necessarily in the same order cats = cellfun (@(x) categories (x), args, 'UniformOutput', false); if (! all (ismember (cats{:}))) error (strcat ("categorical.cat: cannot concatenate protected", ... " categorical arrays that do not have the same", ... " set of categories.")); endif out = args{1}; out.isProtected = true; # returning array must also be protected ## Go through remaining categorical arrays and reorder codes accordingly idx = cell (1, numel (cats{1})); for i = 2:numel (args) for j = 1:numel (out.cats) new_code = find (strcmp (args{i}.cats, out.cats(j))); idx{j} = args{i}.code == new_code; endfor for j = 1:numel (out.cats) args{i}.code(idx{j}) = j; endfor endfor fieldArgs = cellfun (@(x) x.code, args, 'UniformOutput', false); out.code = cat (dim, fieldArgs{:}); fieldArgs = cellfun (@(x) x.isMissing, args, 'UniformOutput', false); out.isMissing = cat (dim, fieldArgs{:}); return; endif ## No constrains, add new categories as necessary and bump code indexing ## to reflect the changes in category list of concatenated output array out = args{1}; for i = 2:numel (args) n_cats = numel (categories (args{i})); idx = cell (2, n_cats); newcat = {}; n_code = 0; for j = 1:n_cats new_code = find (strcmp (out.cats, args{i}.cats(j))); if (! isempty (new_code)) idx{1,j} = args{i}.code == j; idx{2,j} = new_code; else idx{1,j} = args{i}.code == j; idx{2,j} = numel (out.cats) + n_code + 1; n_code += 1; newcat = [newcat; args{i}.cats(j)]; endif endfor out.cats = [out.cats; newcat]; for j = 1:n_cats args{i}.code(idx{1,j}) = idx{2,j}; endfor fieldArgs = cellfun (@(x) x.code, args, 'UniformOutput', false); out.code = cat (dim, fieldArgs{:}); fieldArgs = cellfun (@(x) x.isMissing, args, 'UniformOutput', false); out.isMissing = cat (dim, fieldArgs{:}); endfor ## The categories of the first categorical input array need go first if (firstcatid > 1) catstostart = categories (args{firstcatid}); catsremoved = out.cats(! ismember (out.cats, catstostart)); catsreorder = [catstostart; catsremoved]; out = reordercats (out, catsreorder); endif endfunction ## -*- texinfo -*- ## @deftypefn {categorical} {@var{C} =} horzcat (@var{A}, @var{B}, @dots{}) ## ## Horizontal concatenation of categorical arrays. ## ## @code{@var{C} = horzcat (@var{A}, @var{B}, @dots{}} is the equivalent of ## the syntax @code{@var{B} = [@var{A}, @var{B}, @dots{}]} and horizontally ## concatenates the categorical arrays @var{A}, @var{B}, @dots{}. All input ## arrays must have the same size except along the second dimension. Any of ## the input arrays may also be string arrays or cell arrays of character ## vectors of compatible size. ## ## If any input array is an ordinal categorical array, then all inputs must ## be ordinal categorical arrays with the same set and ordering of ## categories. In this case, @var{C} is also an ordinal categorical array ## with the same set and ordering of categories. If none of the input ## arrays are ordinal, then they do not need to have the same set of ## categories. In this case, categorical array @var{C} contains the union ## of the categories from all input arrays. Protected categorical arrays ## can only be concatenated with other arrays that have the same set of ## categories but not necessarily in the same order. ## ## @end deftypefn function out = horzcat (varargin) out = cat (2, varargin{:}); endfunction ## -*- texinfo -*- ## @deftypefn {categorical} {@var{C} =} vertcat (@var{A}, @var{B}, @dots{}) ## ## Vertical concatenation of categorical arrays. ## ## @code{@var{C} = vertcat (@var{A}, @var{B}, @dots{}} is the equivalent of ## the syntax @code{@var{B} = [@var{A}; @var{B}; @dots{}]} and vertically ## concatenates the categorical arrays @var{A}, @var{B}, @dots{}. All input ## arrays must have the same size except along the first dimension. Any of ## the input arrays may also be string arrays or cell arrays of character ## vectors of compatible size. ## ## If any input array is an ordinal categorical array, then all inputs must ## be ordinal categorical arrays with the same set and ordering of ## categories. In this case, @var{C} is also an ordinal categorical array ## with the same set and ordering of categories. If none of the input ## arrays are ordinal, then they do not need to have the same set of ## categories. In this case, categorical array @var{C} contains the union ## of the categories from all input arrays. Protected categorical arrays ## can only be concatenated with other arrays that have the same set of ## categories but not necessarily in the same order. ## ## @end deftypefn function out = vertcat (varargin) out = cat (1, varargin{:}); endfunction ## -*- texinfo -*- ## @deftypefn {categorical} {@var{B} =} repmat (@var{A}, @var{n}) ## @deftypefnx {categorical} {@var{B} =} repmat (@var{A}, @var{d1}, @dots{}, @var{dN}) ## @deftypefnx {categorical} {@var{B} =} repmat (@var{A}, @var{dimvec}) ## ## Repeat copies of a categorical array. ## ## @code{@var{B} = repmat (@var{A}, @var{n})} returns a categorical array ## @var{B} containing @var{n} copies of the input categorical array @var{A} ## along every dimension of @var{A}. ## ## @code{@var{B} = repmat (@var{A}, @var{d1}, @dots{}, @var{dN})} returns an ## array @var{B} containing copies of @var{A} along the dimensions specified ## by the list of scalar integer values @var{d1}, @dots{}, @var{dN}, which ## specify how many copies of @var{A} are made in each dimension. ## ## @code{@var{B} = repmat (@var{A}, @var{dimvec})} is equivalent to the ## previous syntax with @code{@var{dimvec} = [@var{d1}, @dots{}, @var{dN}]}. ## ## @end deftypefn function this = repmat (this, varargin) this.code = repmat (this.code, varargin{:}); this.isMissing = repmat (this.isMissing, varargin{:}); endfunction ## -*- texinfo -*- ## @deftypefn {categorical} {@var{B} =} repelem (@var{A}, @var{n}) ## @deftypefnx {categorical} {@var{B} =} repelem (@var{A}, @var{d1}, @dots{}, @var{dN}) ## ## Repeat copies of categorical array elements. ## ## @code{@var{B} = repelem (@var{A}, @var{n})} returns a categorical vector ## @var{B} containing repeated elements of the input @var{A}, which must be ## a categorical vector. If @var{n} is a scalar, each element of @var{A} is ## repeated @var{n} times along the non-singleton dimension of @var{A}. If ## @var{n} is a vector, it must have the same elements as @var{A}, in which ## case it specifies the number of times to repeat each corresponding ## element of @var{A}. ## ## @code{@var{B} = repelem (@var{A}, @var{d1}, @dots{}, @var{dN})} returns ## an array @var{B} with each element of @var{A} repeated according to the ## the list of input arguments @code{@var{d1}, @dots{}, @var{dN}} each ## corresponding to a different dimension @code{1:ndims (@var{A})} of the ## input array @var{A}. @var{d1}, @dots{}, @var{dN} must be either scalars ## or vectors with the same length as the corresponding dimension of ## @var{A} containing non-negative integer values specifying the number of ## repetitions of each element along the corresponding dimension. ## ## @end deftypefn function this = repelem (this, varargin) this.code = repelem (this.code, varargin{:}); this.isMissing = repelem (this.isMissing, varargin{:}); endfunction ## -*- texinfo -*- ## @deftypefn {categorical} {@var{B} =} repelems (@var{A}, @var{R}) ## ## Construct a vector of repeated elements from a categorical array. ## ## @code{@var{B} = repelems (@var{A}, @var{R})} returns a categorical vector ## @var{B} containing repeated elements of the input @var{A}, which must be ## a categorical vector. @var{R} must be a @math{2*N} matrix of integers. ## Entries in the first row of @var{R} correspond to the linear indexing of ## the elements in @var{A} to be repeated. The corresponding entries in the ## second row of @var{R} specify the repeat count of each element. ## ## @end deftypefn function this = repelems (this, R) this.code = repelems (this.code, R); this.isMissing = repelems (this.isMissing, R); endfunction ## -*- texinfo -*- ## @deftypefn {categorical} {@var{B} =} reshape (@var{A}, @var{d1}, @dots{}, @var{dN}) ## @deftypefnx {categorical} {@var{B} =} reshape (@var{A}, @dots{}, @qcode{[]}, @dots{}) ## @deftypefnx {categorical} {@var{B} =} reshape (@var{A}, @var{dimvec}) ## ## Repeat copies of categorical array elements. ## ## @code{@var{B} = reshape (@var{A}, @var{d1}, @dots{}, @var{dN})} returns a ## categorical array @var{B} with specified dimensions @var{d1}, @dots{}, ## @var{dN}, whose elements are taken columnwise from the categorical array ## @var{A}. The product of @var{d1}, @dots{}, @var{dN} must equal the total ## number of elements in @var{A}. ## ## @code{@var{B} = reshape (@var{A}, @dots{}, @qcode{[]}, @dots{})} returns ## a categorical array @var{B} with one dimension unspecified which is ## calculated automatically so that the product of dimensions in @var{B} ## matches the total elements in @var{A}, which must be divisible the ## product of specified dimensions. An empty matrix @qcode{([])} is used to ## flag the unspecified dimension. ## ## @end deftypefn function this = reshape (this, varargin) this.code = reshape (this.code, varargin{:}); this.isMissing = reshape (this.isMissing, varargin{:}); endfunction ## -*- texinfo -*- ## @deftypefn {categorical} {@var{B} =} circshift (@var{A}, @var{n}) ## @deftypefnx {categorical} {@var{B} =} circshift (@var{A}, @var{n}, @var{dim}) ## ## Circularly shift the elements in a categorical array. ## ## @code{@var{B} = circshift (@var{A}, @var{n})} circularly shifts the ## elements of the categorical array @var{A} according to @var{n}. If ## @var{n} is a nonzero integer scalar, then the elements of @var{A} are ## shifted by @var{n} elements along the first non-singleton dimension of ## @var{A}. If @var{n} is a vector, it must not be longer that the number ## of dimensions of @var{A} with each value of @var{n} corresponding to a ## dimension in @var{A}. The sign of the value(s) in @var{n} specify the ## direction in the elements of @var{A} are shifted. ## ## @code{@var{B} = circshift (@var{A}, @var{n}, @var{dim})} circularly ## shifts the elements of the categorical array @var{A} along the dimension ## specified by @var{dim}. In this case, @var{n} must be a scalar integer ## value. ## ## @end deftypefn function this = circshift (this, varargin) this.code = circshift (this.code, varargin{:}); this.isMissing = circshift (this.isMissing, varargin{:}); endfunction ## -*- texinfo -*- ## @deftypefn {categorical} {@var{B} =} permute (@var{A}, @var{dims}) ## ## Generalized transpose for a categorical N-D array. ## ## @code{@var{B} = permute (@var{A}, @var{dims})} returns the generalized ## transpose of the categorical array @var{A} by rearranging its dimensions ## according to the permutation vector specified in @var{dims}. ## ## @var{dims} must index all the dimensions @code{1:ndims (@var{A})} of the ## input array @var{A}, in any order, but only once. The @var{N}th ## dimension of @var{A} gets remapped to the dimension in @var{B} specified ## by @code{@var{dims}(@var{N})}. ## ## @end deftypefn function this = permute (this, dims) this.code = permute (this.code, dims); this.isMissing = permute (this.isMissing, dims); endfunction ## -*- texinfo -*- ## @deftypefn {categorical} {@var{A} =} ipermute (@var{B}, @var{dims}) ## ## Inverse of the generalized transpose for a categorical N-D array. ## ## @code{@var{A} = ipermute (@var{B}, @var{dims})} returns the inverse of ## the generalized transpose performed by the @code{permute} function. The ## expression @code{ipermute (permute (@var{A}, @var{dims}), @var{dims})} ## returns the original array @var{A}. ## ## @var{dims} must index all the dimensions @code{1:ndims (@var{B})} of the ## input array @var{B}, in any order, but only once. The dimension of ## @var{B} specified in @code{@var{dims}(@var{N})} gets remapped to the ## @var{N}th dimension of @var{A}. ## ## @end deftypefn function this = ipermute (this, dims) this.code = ipermute (this.code, dims); this.isMissing = ipermute (this.isMissing, dims); endfunction ## -*- texinfo -*- ## @deftypefn {categorical} {@var{B} =} transpose (@var{A}) ## ## Transpose a categorical matrix. ## ## @code{@var{B} = transpose (@var{A})} is the equivalent of the syntax ## @code{@var{B} = @var{A}.'} and returns the transpose of the categorical ## matrix @var{A}. ## ## @end deftypefn function this = transpose (this) if (ndims (this) != 2) error ("categorical.transpose: not defined for N-D arrays."); endif this.code = transpose (this.code); this.isMissing = transpose (this.isMissing); endfunction ## -*- texinfo -*- ## @deftypefn {categorical} {@var{B} =} ctranspose (@var{A}) ## ## Transpose a categorical matrix. ## ## @code{@var{B} = ctranspose (@var{A})} is the equivalent of the syntax ## @code{@var{B} = @var{A}'} and returns the transpose of the categorical ## matrix @var{A}. For categorical arrays, @code{ctranspose} is identical ## to @code{transpose}. ## ## @end deftypefn function this = ctranspose (this) if (ndims (this) != 2) error ("categorical.ctranspose: not defined for N-D arrays."); endif this.code = ctranspose (this.code); this.isMissing = ctranspose (this.isMissing); endfunction endmethods ################################################################################ ## ** Reference and Assignment Operations ** ## ################################################################################ ## Available Methods ## ## ## ## 'end' 'subsref' 'subsasgn' ## ## ## ################################################################################ methods (Hidden) ## Overload 'end' keyword function last_index = end (this, end_dim, ndim_obj) lastdim = ndims (this); if (end_dim == ndim_obj && ndim_obj == 1) last_index = prod (size (this)); elseif (end_dim == ndim_obj && end_dim < lastdim) last_index = prod (size (this)(end_dim:lastdim)); else last_index = size (this, end_dim); endif endfunction ## Class specific subscripted reference function varargout = subsref (this, s) chain_s = s(2:end); s = s(1); switch (s.type) case '()' out = this; out.code = this.code(s.subs{:}); out.isMissing = this.isMissing(s.subs{:}); case '{}' error (strcat ("categorical.subsref: '{}' invalid indexing", ... " for referencing values. Use '()' instead.")); case '.' error (strcat ("categorical.subsref: '.' invalid indexing", ... " for referencing field of non-structure array.")); endswitch ## Chained references if (! isempty (chain_s)) out = subsref (out, chain_s); endif varargout{1} = out; endfunction ## Class specific subscripted assignment function this = subsasgn (this, s, val) if (numel (s) > 1) error ("categorical.subsasgn: chained subscripts not allowed."); endif switch s.type case '()' if (isempty (val) && isa (val, 'double')) this.code(s.subs{:}) = []; this.isMissing(s.subs{:}) = []; return; elseif (isa (val, 'missing')) this.code(s.subs{:}) = 0; this.isMissing(s.subs{:}) = true; return; elseif (iscellstr (val) || ischar (val) || isstring (val)) ## Convert to unprotected unordered categorical array and ## handle it as usual below if (ischar (val)) val = cellstr (val); elseif (isempty (val)) error ("categorical.subsasgn: assignment value has no elements."); endif val = categorical (val); elseif (! isa (val, 'categorical')) error (strcat ("categorical.subsasgn: assignment value must", ... " be a categorical array or text representing", ... " a single or multiple categories.")); endif ## After this point VAL is a categorical array ## If any categorical array is ordinal, all must be if (isordinal (this) && isordinal (val)) ## Check that all categorical arrays have the same categories ## and they are in the same order if (! isequal (categories (this), categories (val))) error (strcat ("categorical.subsasgn: cannot assign value", ... " to ordinal categorical array unless they", ... " have the same ordered set of categories.")); endif this.code(s.subs{:}) = val.code; this.isMissing(s.subs{:}) = val.isMissing; return; elseif (isordinal (this)) error (strcat ("categorical.subsasgn: cannot assign unordered", ... " categorical array to ordinal categorical array.")); elseif (isordinal (val)) error (strcat ("categorical.subsasgn: cannot assign ordinal", ... " categorical array to unordered categorical", ... " array.")); endif ## If protected, no new categories can be assigned if (isprotected (this)) if (! all (ismember (val.cats, this.cats))) error (strcat ("categorical.subsasgn: cannot assign new", ... " categories to protected categorical array.")); endif ## Reorder codes accordingly n_cats = numel (val.cats); idx = cell (2, n_cats); for j = 1:n_cats new_code = find (strcmp (this.cats, val.cats(j))); idx{1,j} = val.code == j; idx{2,j} = new_code; endfor for j = 1:n_cats val.code(idx{1,j}) = idx{2,j}; endfor this.code(s.subs{:}) = val.code; this.isMissing(s.subs{:}) = val.isMissing; return; endif ## No constrains, add new categories as necessary and bump code ## indexing to reflect the changes in category list of assigned ## categorical array n_cats = numel (val.cats); maxcat = numel (this.cats); idx = cell (2, n_cats); newcat = {}; n_code = 0; for j = 1:n_cats new_code = find (strcmp (this.cats, val.cats(j))); if (! isempty (new_code)) idx{1,j} = val.code == j; idx{2,j} = new_code; else idx{1,j} = val.code == j; idx{2,j} = maxcat + n_code + 1; n_code += 1; newcat = [newcat; val.cats(j)]; endif endfor this.cats = [this.cats; newcat]; for j = 1:n_cats val.code(idx{1,j}) = idx{2,j}; endfor this.code(s.subs{:}) = val.code; this.isMissing(s.subs{:}) = val.isMissing; case '{}' error (strcat ("categorical.subsasgn: '{}' invalid indexing", ... " for assigning values. Use '()' instead.")); case '.' error (strcat ("categorical.subsasgn: '.' invalid indexing", ... " for assigning field of non-structure array.")); endswitch endfunction endmethods methods (Access = private) ## Return a subset of the array function this = subset (this, varargin) this = this; this.code = this.code(varargin{:}); this.isMissing = this.isMissing(varargin{:}); endfunction ## Common function for set operations function [C, ixA, ixB] = setop (A, B, fname, varargin) ixA = ixB = []; ## 'legacy' option is not supported if (any (strcmp ("legacy", varargin))) C = sprintf (strcat ("categorical.%s: 'legacy'", ... " option is not supported."), fname); return; endif if (ischar (A)) A = categorical (cellstr (A)); elseif (iscellstr (A) || isa (A, 'string')) A = categorical (A); elseif (ischar (B)) B = categorical (cellstr (B)); elseif (iscellstr (B) || isa (B, 'string')) B = categorical (B); endif if (! isa (A, 'categorical') || ! isa (B, 'categorical')) C = sprintf ("categorical.%s: invalid type input arrays.", fname); return; endif if (xor (A.isOrdinal, B.isOrdinal)) C = sprintf (strcat ("categorical.%s: if A is", ... " ordinal, B must be ordinal."), fname); return; endif C = A; if (A.isOrdinal && B.isOrdinal) if (! isequal (A.cats, B.cats)) C = sprintf (strcat ("categorical.%s: ordinal arrays must", ... " have the same set of categories in", ... " the same order."), fname); return; endif ## For ordinal arrays, operate directly on codes Acodes = double (A); Bcodes = double (B); else ## For unordered arrays, operate on categorical values allcats = unique ([A.cats(:); B.cats(:)]); A_cats_idx = cellfun (@(x) find (ismember (allcats, x)), A.cats); Acodes = nan (size (A.code)); for i = 1:numel (A_cats_idx) Acodes(A.code == i) = A_cats_idx(i); endfor B_cats_idx = cellfun (@(x) find (ismember (allcats, x)), B.cats); Bcodes = nan (size (B.code)); for i = 1:numel (B_cats_idx) Bcodes(B.code == i) = B_cats_idx(i); endfor C.cats = allcats; endif ## Apply set operation switch (fname) case 'intersect' [code, ixA, ixB] = intersect (Acodes, Bcodes, varargin{:}); case 'setdiff' [code, ixA] = setdiff (Acodes, Bcodes, varargin{:}); case 'setxor' [code, ixA, ixB] = setxor (Acodes, Bcodes, varargin{:}); case 'union' [code, ixA, ixB] = union (Acodes, Bcodes, varargin{:}); endswitch ## Add codes and missing arrays C.code = uint16 (code); C.isMissing = C.code == 0; endfunction ## Expand categorical arrays of compatible sizes function [A, B, err] = csize_expand (A, B) err = false; szA = size (A); szB = size (B); if (isscalar (A) && ! isscalar (B)) A = repmat (A, szB); elseif (! isscalar (A) && isscalar (B)) B = repmat (B, szA); elseif (! isequal (szA, szB)) ndA = numel (szA); ndB = numel (szB); if (ndA > ndB) padones = ones (1, ndA - ndB); szB = [szB, padones]; elseif (ndA < ndB) padones = ones (1, ndB - ndA); szA = [szA, padones]; endif ## Check that sizes are compatible must_equal = szA != 1 & szB != 1; if (! isequal (szA(must_equal), szB(must_equal))) err = true; return; endif ## Create expansion vectors dims = max (ndA, ndB); repA = ones (1, dims); repB = ones (1, dims); ## Get dimensions that should be expanded expA = szA == 1 & szB != 1; repA(expA) = szB(expA); expB = szB == 1 & szA != 1; repB(expB) = szA(expB); ## Expand accordingly A = repmat (A, repA); B = repmat (B, repB); endif endfunction endmethods endclassdef ## Promote text arrays to categorical objects function varargout = promote (varargin) for i = 1:numel (varargin) val = varargin{i}; if (isa (val, "categorical")) varargout{i} = val; elseif (iscellstr (val) || isa (val, "string") || ischar (val)) val = cellstr (val); varargout{i} = categorical (val); elseif (isnumeric (val) || islogical (val)) varargout{i} = categorical (val); elseif (isa (val, 'missing')) varargout{i} = categorical (nan (size (missing))); else error ("categorical: invalid input to constructor."); endif endfor endfunction ## Custom function for displaying categorical summary function dispcellmatrix (C) sz = terminal_size (); cols = sz(2) - 4; colgap = " "; dispstr = {}; optLens = []; for iCol = 1:size (C, 2) [outstr, optLen] = mixedcell2str (C(:, iCol)); dispstr = [dispstr, outstr]; optLens = [optLens, optLen]; endfor if (sum (optLens + 6) <= cols) # all columns fit in terminal size rowSpat = ""; for iCol = 1:size (C, 2) rowSpat = [rowSpat, sprintf("%%-%ds", optLens(iCol)), colgap]; endfor for iRow = 1:size (C, 1) strrow = sprintf (rowSpat, dispstr{iRow,:}); fprintf (" %s\n", strrow); endfor fprintf ("\n"); else # we need to split rows optLen_cs = cumsum (optLens + 6); startCol = 1; while (! isempty (find (optLen_cs > cols))) stopCol = find (optLen_cs > cols, 1) - 1; rowSpat = ""; for iCol = 1:stopCol rowSpat = [rowSpat, sprintf("%%-%ds", optLens(iCol)), colgap]; endfor optLens(1:iCol) = []; optLen_cs = cumsum (optLens + 6); stopCol = stopCol + startCol - 1; fprintf ("Columns %d through %d:\n\n", startCol, stopCol); for iRow = 1:size (C, 1) strrow = sprintf (rowSpat, dispstr{iRow,[startCol:stopCol]}); fprintf (" %s\n", strrow); endfor fprintf ("\n"); startCol = stopCol + 1; endwhile if (! isempty (optLens)) for iCol = 1:length (optLens) rowSpat = [rowSpat, sprintf("%%-%ds", optLens(iCol)), colgap]; endfor stopCol = startCol + iCol - 1; if (startCol == stopCol) fprintf ("Column %d:\n\n", startCol); else fprintf ("Columns %d through %d:\n\n", startCol, stopCol); endif for iRow = 1:size (C, 1) strrow = sprintf (rowSpat, dispstr{iRow,[startCol:stopCol]}); fprintf (" %s\n", strrow); endfor fprintf ("\n"); endif endif endfunction ## Custom function to convert a mixed cell array to cellstr array function [dispstr, optLen] = mixedcell2str (data) dispstr = cell (size (data)); ## Preallocate indexes to avoid truncation when last elements are 0 is_char = logical (zeros (size (data))); is_numb = is_char; ## Index numeric scalar and character row vector ve = cell2mat (cellfun (@(x) size (x,1), data, "UniformOutput", false)) == 1; ## Catch 'char' row vectors is_char(ve) = cellfun ('ischar', data(ve)); sf = @(x) sprintf ("%s", x); dispstr(is_char) = cellfun (sf, data(is_char), "UniformOutput", false); ## Catch 'numeric' scalars is_numb(ve) = cellfun ('isnumeric', data(ve)); sf = @(x) sprintf ("%d", x); dispstr(is_numb) = cellfun (sf, data(is_numb), "UniformOutput", false); ## Get optimal length optLen = max (cellfun (@length, dispstr)); ## Pad data according to optimal length Ra_wB = sprintf ("%%-%ds", optLen); fcn = @(x) sprintf (Ra_wB, x); dispstr(is_numb) = cellfun (fcn, dispstr(is_numb), "UniformOutput", false); La_wB = sprintf ("%%+%ds", optLen); fcn = @(x) sprintf (La_wB, x); dispstr(is_char) = cellfun (fcn, dispstr(is_char), "UniformOutput", false); endfunction pr0m1th3as-datatypes-9c9a8d3/inst/cell2table.m000066400000000000000000000162221522766574100212240ustar00rootroot00000000000000## Copyright (C) 2024-2026 Andreas Bertsatos ## ## This file is part of the datatypes package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {datatypes} {@var{tbl} =} cell2table (@var{C}) ## @deftypefnx {datatypes} {@var{tbl} =} cell2table (@var{C}, @var{Name}, @var{Value}) ## ## Convert a cell array to a table. ## ## @code{@var{tbl} = cell2table (@var{C})} converts the 2-D cell array @var{C} ## to the table @var{tbl}, where the contents of each column of @var{C} becomes ## a variable in @var{tbl}. The contents of each column are concatenated into ## their common data type (i.e. if a column of @var{C} contains explicitly ## @qcode{double} numbers, then the corresponding variable in @var{tbl} is of ## the same type), otherwise they are added as a column of cells. ## ## @code{@var{tbl} = cell2table (@var{C}, @var{Name}, @var{Value})} specifies ## optional parameters for creating the table @var{tbl} with the following ## Name-Value paired arguments. ## ## @multitable @columnfractions 0.23 0.75 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'VariableNames'} @tab A cell array of character vectors or ## a string array defining the variable names of @var{tbl}. The names must be ## valid variable names and unique. ## ## @item @qcode{'RowNames'} @tab A cell array of character vectors or ## a string array defining the row names of @var{tbl}. The names must be unique ## but not necessarily valid variable names. ## ## @item @qcode{'DimensionNames'} @tab A cell array of character vectors or ## a string array defining the dimension names of @var{tbl}. The names must be ## unique and not in conflict with variable names. By default, dimension names ## are @qcode{'Row', 'Variables'}. ## @end multitable ## ## @seealso{array2table, struct2table, table} ## @end deftypefn function tbl = cell2table (C, varargin) ## Check input is a matrix if (ndims (C) > 2) error ("cell2table: input array must be a 2-D cell array."); endif if (! iscell (C)) error ("cell2table: input array must be a cell array."); endif ## Parse optional Name-Value paired arguments optNames = {'VariableNames', 'RowNames', 'DimensionNames'}; dfValues = {{}, {}, {}}; [varNames, rowNames, dimNames, args] = parsePairedArguments ... (optNames, dfValues, varargin); ## Split columns into separate input data arguments for table varN = size (C, 2); varValues = cell (1, varN); for ix = 1:varN tmp = C(:,ix); if (iscellstr (tmp)) varValues{ix} = tmp; else ## Try to concatenate into array try warning ('off', 'Octave:num-to-str'); newArray = cat (1, tmp{:}); warning ('on', 'Octave:num-to-str'); if (size (newArray, 1) == size (tmp, 1)) varValues{ix} = newArray; continue; else error (); endif catch varValues{ix} = tmp; end_try_catch endif endfor ## Handle variable names if (! isempty (varNames)) if (numel (varNames) != varN) error (strcat ("cell2table: 'VariableNames' must match the", ... " columns in input cell.")); endif else varName = inputname (1); if (isempty (varName)) varName = 'Var'; endif varNames = cell (1, varN); for ix = 1:varN varNames{ix} = sprintf ('%s%d', varName, ix); endfor endif optArgs = {'VariableNames', varNames}; ## Handle remaining paired arguments if (! isempty (rowNames)) if (numel (rowNames) != size (C, 1)) error ("cell2table: 'RowNames' must match the rows in input cell."); endif optArgs = [optArgs {'RowNames', rowNames}]; endif if (! isempty (dimNames)) if (numel (dimNames) != 2) error ("cell2table: 'DimensionNames' must be a two-element vector."); endif optArgs = [optArgs {'DimensionNames', dimNames}]; endif ## Construct table tbl = table (varValues{:}, optArgs{:}); endfunction %!demo %! ## `cell2table` turns a 2-D cell array into a table, one column per variable. %! ## Unlike a matrix, the cell columns may hold different types. %! %! C = {'Sanchez', 38, true; 'Johnson', 43, false; 'Li', 38, true}; %! cell2table (C, 'VariableNames', {'LastName', 'Age', 'Smoker'}) %!test %! C = {1, 2; 3, 4}; %! tbl = cell2table (C); %! assert_equal (tbl.C1, [1; 3]); %! assert_equal (tbl.C2, [2; 4]); %! assert_equal (size (C), size (tbl)); %! assert_equal (isa (tbl.C1, 'double'), true); %!test %! tbl = cell2table ({1, 2; 3, 4}); %! assert_equal (tbl.Var1, [1; 3]); %! assert_equal (tbl.Var2, [2; 4]); %! assert_equal (size (tbl), [2, 2]); %! assert_equal (isa (tbl.Var1, 'double'), true); %!test %! tbl = cell2table ({1, ''; 3, 4}, 'VariableNames', {'A', 'B'}); %! assert_equal (tbl.A, [1; 3]); %! assert_equal (tbl.B, {''; 4}); %! assert_equal (isa (tbl.A, 'double'), true); %! assert_equal (isa (tbl.B, 'double'), false); %!test %! tbl = cell2table ({1, ''; 3, 4}, "RowNames", {'A', 'B'}); %! assert_equal (tbl.Var1, [1; 3]); %! assert_equal (tbl.Var2, {''; 4}); %! assert_equal (tbl.Properties.RowNames, {'A'; 'B'}); %! assert_equal (class (tbl('A', :)), 'table'); %!test %! tbl = cell2table ({1, ''; 3, 4}, string ('DimensionNames'), {'A', 'B'}); %! assert_equal (tbl.Var1, [1; 3]); %! assert_equal (tbl.Var2, {""; 4}); %! assert_equal (tbl.A, {}); %! assert_equal (tbl.B, {1, ''; 3, 4}); %!test %! tbl = cell2table ({1, ''; 3, 4}, "RowNames", {'A', 'B'}, ... %! "DimensionNames", {'A', 'B'}); %! assert_equal (tbl.Var1, [1; 3]); %! assert_equal (tbl.Var2, {''; 4}); %! assert_equal (tbl.A, {'A'; 'B'}); %! assert_equal (tbl.B, {1, ''; 3, 4}); %!test %! tbl = cell2table ({1, string(''); 3, 4}, "RowNames", {'R1', 'R2'}, ... %! "DimensionNames", {'A', 'B'}); %! assert_equal (class (tbl('R1', :)), 'table'); %! assert_equal (class (tbl{'R1', :}), 'string'); %! assert_equal (cellstr (tbl{'R1', :}), {'1', ''}); %! assert_equal (tbl.A, {'R1'; 'R2'}); %! assert_equal (class (tbl.B), 'string'); %! assert_equal (cellstr (tbl.B), {'1', ''; '3', '4'}); %!error ... %! cell2table (cell (3, 3, 3)); %!error cell2table ([3; 3; 3]); %!error ... %! cell2table ({1; 2; 3}, 'VariableNames', {'A', 'B'}); %!error ... %! cell2table ({1; 2; 3}, 'RowNames', {'A', 'B'}); %!error ... %! cell2table ({1; 2; 3}, 'DimensionNames', {'A', 'B', 'C'}); pr0m1th3as-datatypes-9c9a8d3/inst/convertCharsToStrings.m000066400000000000000000000054041522766574100235310ustar00rootroot00000000000000## Copyright (C) 2024-2026 Andreas Bertsatos ## ## This file is part of the datatypes package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {datatypes} {@var{B} =} convertCharsToStrings (@var{A}) ## @deftypefnx {datatypes} {[@var{B1}, @dots{}, @var{Bn}] =} convertCharsToStrings (@var{A1}, @dots{}, @var{An}) ## ## Convert character arrays to a string arrays, where applicable. ## ## @code{@var{B} = convertCharsToStrings (@var{A})} converts @var{A} to a string ## scalar, if @var{A} is a character vector or array, or to a string array, if ## @var{A} is a cell array of character vectors. Otherwise, @var{A} is returned ## unaltered. ## ## @code{[@var{B1}, @dots{}, @var{Bn}] = convertCharsToStrings (@var{A1}, ## @dots{}, @var{An})} converts any of the input arguments that are either ## character arrays or cell arrays of character vectors to string scalars or ## string arrays, respectively. Otherwise, @code{convertCharsToStrings} returns ## the input arguments unaltered. ## ## @seealso{convertStringsToChars, string} ## @end deftypefn function varargout = convertCharsToStrings (varargin) for i = 1:numel (varargin) if (ischar (varargin{i})) varargout{i} = string (varargin{i}(:)'); elseif (iscellstr (varargin{i})) varargout{i} = string (varargin{i}); else varargout{i} = varargin{i}; endif endfor endfunction %!test %! A = 'text'; %! B = [1, 2, 3]; %! C = string ("test"); %! [A1, B1, C1] = convertCharsToStrings (A, B, C); %! assert_equal (isstring (A1), true); %! assert_equal (B, B1); %! assert_equal (C == C1, true); %!test %! A = {'asd', 'ert'; 'xcv', 'dfg'}; %! A1 = convertCharsToStrings (A); %! assert_equal (isequal (size (A), size (A1)), true); %! assert_equal (isstring (A1), true); %!test %! A = categorical (NaN); %! B = duration; %! C = 'text'; %! [A1, B1, C1] = convertCharsToStrings (A, B, C); %! assert_equal (iscategorical (A1), true); %! assert_equal (isduration (B1), true); %! assert_equal (isstring (C1), true); %!test %! A = ['e','r';'v','b']; %! A1 = convertCharsToStrings (A); %! assert_equal (isscalar (A1), true); %! assert_equal (strcmp (char (A1), 'evrb'), true); pr0m1th3as-datatypes-9c9a8d3/inst/convertStringsToChars.m000066400000000000000000000050301522766574100235240ustar00rootroot00000000000000## Copyright (C) 2024-2026 Andreas Bertsatos ## ## This file is part of the datatypes package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {datatypes} {@var{B} =} convertStringsToChars (@var{A}) ## @deftypefnx {datatypes} {[@var{B1}, @dots{}, @var{Bn}] =} convertStringsToChars (@var{A1}, @dots{}, @var{An}) ## ## Convert string arrays to a character arrays, where applicable. ## ## @code{@var{B} = convertStringsToChars (@var{A})} converts @var{A} to a ## character vector, if @var{A} is a string scalar, or to a cell array of ## character vectors, if @var{A} is a string array. Otherwise, @var{A} is ## returned unaltered. ## ## @code{[@var{B1}, @dots{}, @var{Bn}] = convertStringsToChars (@var{A1}, ## @dots{}, @var{An})} converts any of the input arguments that are of string ## type to character vectors or to cell array of character vectors, or leaves ## them unaltered. ## ## @seealso{convertCharsToStrings, string} ## @end deftypefn function varargout = convertStringsToChars (varargin) for i = 1:numel (varargin) if (isa (varargin{i}, 'string')) if (isscalar (varargin{i})) varargout{i} = char (varargin{i}); else varargout{i} = cellstr (varargin{i}); endif else varargout{i} = varargin{i}; endif endfor endfunction %!test %! A = 'text'; %! B = [1, 2, 3]; %! C = string ("test"); %! [A1, B1, C1] = convertStringsToChars (A, B, C); %! assert_equal (A, A1); %! assert_equal (B, B1); %! assert_equal (ischar (C1), true); %!test %! A = string ({'asd', 'ert'; 'xcv', 'dfg'}); %! A1 = convertStringsToChars (A); %! assert_equal (isequal (size (A), size (A1)), true); %! assert_equal (iscellstr (A1), true); %!test %! A = categorical (NaN); %! B = duration; %! C = 'text'; %! [A1, B1, C1] = convertStringsToChars (A, B, C); %! assert_equal (iscategorical (A1), true); %! assert_equal (isduration (B1), true); %! assert_equal (ischar (C1), true); pr0m1th3as-datatypes-9c9a8d3/inst/csv2table.m000066400000000000000000001001701522766574100210740ustar00rootroot00000000000000## Copyright (C) 2025-2026 Andreas Bertsatos ## ## This file is part of the datatypes package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {datatypes} {@var{tbl} =} csv2table (@var{filename}) ## @deftypefnx {datatypes} {@var{tbl} =} csv2table (@var{filename}, @var{Name}, @var{Value}) ## ## Load a CSV file into a table. ## ## @code{@var{tbl} = csv2table (@var{filename})} creates a table @var{tbl} by ## reading the data in CSV file specified by @var{filename}, which can be either ## character vector or a string scalar. If the CSV file was saved by the ## @code{table2csv} method, then it reads the custom comment line in the first ## element of the CSV file and reconstructs the table as specified including ## variable types, variable names, and possibly any nested tables or structures ## it may contain. If no such special comment is found, then it treats the CSV ## file as simple columnar data and loads with the following default options. ## ## @enumerate ## @item The first column is considered as @qcode{RowNames} and it is converted ## to cell array of character vectors and it is subsequently removed from the ## remaining data contained in the CSV file. To change the default behavior, ## you need to specify the @qcode{ReadRowNames} and @qcode{RowNamesColumn} ## paired arguments accordingly. ## @item The first line is treated as a header containing the variable names of ## the table. Any numeric values in the header line are converted to character ## vectors and all variable names are automatically modified to valid Octave ## variable names. To change the default behavior, you need to specify the ## @qcode{ReadVariableNames}, @qcode{VariableNamingRule}, and ## @qcode{VariableNamesLine} paired arguments accordingly. ## @item The data type of each column in the remaining data is automatically ## detected according to its contents. Consequently, text is converted to ## character vectors, datetime and duration strings are converted to datetime ## and duration arrays, respectively, and hexadecimal strings are converted to ## the smallest integer type that can represent all variable values. To change ## the default behavior, you need to specify the @qcode{TextType}, ## @qcode{DatetimeType}, @qcode{DurationType}, and @qcode{HexType} accordingly. ## @end enumerate ## ## @code{@var{tbl} = csv2table (@var{filename}, @var{Name}, @var{Value})} ## specifies optional parameters for creating the table @var{tbl} with the ## following Name-Value paired arguments. ## ## @multitable @columnfractions 0.33 0.65 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'NumHeaderLines'} @tab A positive integer scalar value ## specifying the number of rows to omit reading from the CSV file. If the CSV ## contains the custom comment line in its first element, then the ## @qcode{'NumHeaderLines'} applies to the top number of rows to remove from the ## created table. If CSV is a generic case, then @qcode{'NumHeaderLines'} ## specifies the number of lines to omit parsing from the CSV file itself. ## ## @item @qcode{'VariableNames'} @tab A cell array of character vectors or ## a string array specifying the names of the variables of the created table. ## The names must be unique but not necessarily valid variable names. If not ## empty, it overrides any variable names extracted from the CSV file. This ## applies both to custom and generic CSV files. ## ## @item @qcode{'ReadVariableNames'} @tab A logical scalar specifying ## whether to parse or not the CSV files for variable names. If your CSV file ## only contains data, set @qcode{'ReadVariableNames'} to @qcode{false} so that ## @code{csv2table} parses all lines as data rows in the returned table. ## Variable names will be automatically generated to the default style as done ## by @code{table}, unless otherwise specified by the @qcode{'VariableNames'} ## paired argument. The default value is @qcode{true}. ## ## @item @qcode{'VariableNamingRule'} @tab A character vector or a string ## scalar specifying whether the variable names should be modified to valid ## Octave variable names or the original names should be preserved. Valid ## options are @qcode{"modify"} and @qcode{"preserve"}. By default, ## @code{csv2table} modifies the parsed variable names. ## ## @item @qcode{'VariableNamesLine'} @tab A nonnegative integer scalar ## value specifying the line number in the CSV file, which should be parsed for ## variable names. This only applies if the @qcode{'ReadVariableNames'} option ## is @qcode{true}. The specified line is subsequently removed from the ## remaining data contained in the CSV file. If @qcode{'VariableNamesLine'} is ## set to zero, then it is equivalent to setting @qcode{'ReadVariableNames'} to ## @qcode{false}. ## ## @item @qcode{'VariableTypes'} @tab A cell array of character vectors or ## a string array specifying the data type of the variables of the created ## table. The number of elements must much the number of variable in the table. ## This optional argument only has an effect on generic CSV files. When ## specified, it overrides any other data type specification or automatic ## detection by the @code{csv2table} function. ## ## @item @qcode{'VariableUnitsLine'} @tab A nonnegative integer scalar ## value specifying the line number in the CSV file, which should be parsed for ## variable units. The specified line is subsequently removed from the ## remaining data contained in the CSV file. ## ## @item @qcode{'VariableDescriptionsLine'} @tab A nonnegative integer ## scalar value specifying the line number in the CSV file, which should be ## parsed for variable descriptions. The specified line is subsequently removed ## from the remaining data contained in the CSV file. ## ## @item @qcode{'ReadRowNames'} @tab A logical scalar specifying whether to ## parse or not the CSV files for row names. If your CSV file only contains ## data, set @qcode{'ReadRowNames'} to @qcode{false} so that @code{csv2table} ## parses all columns as data columms in the returned table. The default value ## is @qcode{true}. ## ## @item @qcode{'RowNamesColumn'} @tab A nonnegative integer scalar value ## specifying the column number in the CSV file, which should be parsed for ## row names. This only applies if the @qcode{'ReadRowNames'} option is ## @qcode{true}. The specified column is subsequently removed from the ## remaining data contained in the CSV file. If @qcode{'RowNamesColumn'} is set ## to zero, then it is equivalent to setting @qcode{'ReadRowNames'} to ## @qcode{false}. Note that the values in the column specified by ## @qcode{'RowNamesColumn'} must be unique, otherwise @code{csv2table} will ## return an error. ## ## @item @qcode{'TextType'} @tab A character vector or a string scalar ## specifying whether the text data in the CSV file should be stored in the ## table as character vectors or string arrays. Valid options are ## @qcode{"char"} and @qcode{"string"}. By default, @code{csv2table} stores ## text data as character vectors. ## ## @item @qcode{'DatetimeType'} @tab A character vector or a string scalar ## specifying whether the datetime strings found in the CSV file should be ## stored in the table as datetime arrays or as text data. Valid options are ## @qcode{"datetime"} and @qcode{"text"}. By default, @code{csv2table} stores ## datetime strings as datetime arrays. If @qcode{"text"} is specified, then ## the data type depends on the @qcode{'TextType'} option. ## ## @item @qcode{'DurationType'} @tab A character vector or a string scalar ## specifying whether the duration strings found in the CSV file should be ## stored in the table as duration arrays or as text data. Valid options are ## @qcode{"duration"} and @qcode{"text"}. By default, @code{csv2table} stores ## duration strings as duration arrays. If @qcode{"text"} is specified, then ## the data type depends on the @qcode{'TextType'} option. ## ## @item @qcode{'HexType'} @tab A character vector or a string scalar ## specifying whether the hexadecimal text found in the CSV file should be ## stored as a suitable integer type, @qcode{"auto"} (default), as unaltered ## input text, @qcode{"text"}, (in which case the data type depends on the ## @qcode{'TextType'} option), or as any of the integer types supported by ## Octave. Valid options are @qcode{"auto"}, @qcode{"text"}, @qcode{"int8"}, ## @qcode{"int16"}, @qcode{"int32"}, @qcode{"int64"}, @qcode{"uint8"}, ## @qcode{"uint16"}, @qcode{"uint32"}, and @qcode{"uint64"}. ## @end multitable ## ## The following round-trip limitations apply when reading a file written by ## @code{table2csv}: @code{calendarDuration} and @code{categorical} variables ## are returned as cell arrays of character vectors (their values are not ## reconstructed), missing @code{string} values are read back as empty strings, ## and datetime and duration display formats are not preserved, although the ## values themselves are exact. ## ## @seealso{array2table, struct2table, table} ## @end deftypefn function tbl = csv2table (name, varargin) ## The field delimiter must be known before the file is read, so pre-scan the ## optional arguments for it (it is also parsed as a normal option below). delim = ','; for i = 1:2:numel (varargin) - 1 if (ischar (varargin{i}) && strcmpi (varargin{i}, 'Delimiter')) delim = varargin{i+1}; if (isa (delim, 'string')) delim = char (delim); endif endif endfor if (! (ischar (delim) && isscalar (delim))) error ("csv2table: 'Delimiter' must be a single character."); endif ## Check first input is a character vector or a string scalar if (ischar (name) && isvector (name)) C = __csv2table__ (name, delim); elseif (isa (name, 'string') && isscalar (name)) C = __csv2table__ (char (name), delim); else error ("csv2table: NAME must be a character vector or a string scalar."); endif if (ischar (C)) error ("csv2table: %s.", C); endif ## Get first comment line (as saved by 'table2csv' method). A generic CSV ## may carry a number in its first cell, so only attempt to split a string. if (ischar (C{1,1})) H_key = strsplit (C{1,1}); else H_key = {}; endif if (numel (H_key) == 13) H_txt = strjoin (H_key([1,2,4,5,7,8,10,11,13])); else H_txt = ''; endif txt = '# varTypes rows; varNames rows; varDescriptions rows; varUnits rows.'; if (strcmpi (H_txt, txt)) Trows = str2num (H_key{3}); Nrows = str2num (H_key{6}); Drows = str2num (H_key{9}); Urows = str2num (H_key{12}); C(1,:) = []; # remove comment line else Trows = 0; Nrows = 0; Drows = 0; Urows = 0; endif ## Handle CSV file with vartype header (as saved by table2csv method) if (Trows > 0) Hrows = Trows + Nrows + Drows + Urows; ## Check for RowNames if (strcmp (C{1,1}, 'RowNames') && isempty (C{Trows+1,1})) RowNames = C([Hrows+1:end],1); C(:,1) = []; # remove row names else RowNames = {}; endif ## Split cell into headers and data T = C(1:Trows,:); # variable types C(1:Trows,:) = []; N = C(1:Nrows,:); # variable names C(1:Nrows,:) = []; if (Drows) D = C(1:Drows,:); # variable descriptions C(1:Drows,:) = []; else D = {}; endif if (Urows) U = C(1:Urows,:); # variable units C(1:Urows,:) = []; else U = {}; endif ## After this point C contains only data ## Construct table tbl = cell2tbl (C, T, N, D, U, RowNames); endif ## Parse optional Name-Value paired arguments optNames = {'NumHeaderLines', 'VariableNames', 'ReadVariableNames', ... 'VariableNamingRule', 'VariableNamesLine', 'VariableTypes', ... 'VariableUnitsLine', 'VariableDescriptionsLine', ... 'ReadRowNames', 'RowNamesColumn', 'TextType', ... 'DatetimeType', 'DurationType', 'HexType', 'Delimiter'}; dfValues = {0, {}, true, 'modify', 1, {}, 0, 0, true, 1, 'char', ... 'datetime', 'duration', 'auto', ','}; [numHeaderLines, varNames, readVarNames, varNamingRule, varNamesLine, ... varTypes, varUnitsLine, varDescrLine, readRowNames, rowNamesColumn, ... textType, datetimeType, durationTypes, hexType, delim, args] = ... parsePairedArguments (optNames, dfValues, varargin(:)); ## Apply optional arguments to CSV files with vartype header if (Trows > 0) if (numHeaderLines) tbl(1:numHeaderLines,:) = []; endif if (! isempty (varNames)) tbl = tbl(:,varNames); endif if (! readRowNames) tbl.Properties.RowNames = []; endif return endif ## After this point handle generic CSV file ## Remove unnecessary header lines if (numHeaderLines) C(1:numHeaderLines,:) = []; endif ## Read row names has_RowNames = false; if (readRowNames && rowNamesColumn) has_RowNames = true; RowNames = C(:,rowNamesColumn); ## Force RowNames into cellstr if (! iscellstr (RowNames)) RowNames = cellstr (string (RowNames)); endif ## Remove RowNames column from data C(:,rowNamesColumn) = []; endif ## Read variable names, descriptions, and units from their respective lines. ## All line numbers refer to the same frame, so the values are read first and ## the consumed header rows are removed together below -- removing them one at ## a time would shift the indices of the lines still to be read. cols = size (C, 2); if (readVarNames && varNamesLine) N = C(varNamesLine,:); isnum = cellfun ('isnumeric', N); N(isnum) = cellfun ('num2str', N(isnum), "UniformOutput", false); ## Force to valid variable names if (strcmpi (varNamingRule, 'modify')) N = matlab.lang.makeValidName (N); endif else # Generate default variable names N = arrayfun (@(x) sprintf ("Var%d", x), [1:cols], "UniformOutput", false); endif ## Override variable names with user supplied if (! isempty (varNames)) N = varNames; endif ## Read variable descriptions and units if (varDescrLine) D = C(varDescrLine,:); ## Force variable descriptions into cellstr if (! iscellstr (D)) D = cellstr (string (D)); endif endif if (varUnitsLine) U = C(varUnitsLine,:); ## Force variable units into cellstr if (! iscellstr (U)) U = cellstr (string (U)); endif endif ## Remove all consumed header rows from data and RowNames at once droprows = []; if (readVarNames && varNamesLine) droprows = [droprows, varNamesLine]; endif if (varDescrLine) droprows = [droprows, varDescrLine]; endif if (varUnitsLine) droprows = [droprows, varUnitsLine]; endif droprows = unique (droprows); if (! isempty (droprows)) C(droprows,:) = []; if (has_RowNames) RowNames(droprows,:) = []; endif endif ## Check that RowNames are unique if (has_RowNames) if (numel (unique (RowNames)) != numel (RowNames)) error ("csv2table: 'RowNames' must be unique."); endif endif ## Parse columns into variables with user defined variable types varValues = cell (1, cols); if (! isempty (varTypes)) if (numel (varTypes) != cols) error ("csv2table: 'VariableTypes' do not match columns in CSV."); endif for ix = 1:cols varValues{ix} = cell2var (C(:,ix), varTypes{ix}); endfor ## Parse columns into variables by identifying the variable type else for ix = 1:cols varValues{ix} = cell2auto (C(:,ix), textType, datetimeType, ... durationTypes, hexType); endfor endif ## Create table if (has_RowNames) tbl = table (varValues{:}, 'VariableNames', N, 'RowNames', RowNames); else tbl = table (varValues{:}, 'VariableNames', N); endif ## Add variable descriptions and units if (varDescrLine) tbl.Properties.VariableDescriptions = D; endif if (varUnitsLine) tbl.Properties.VariableUnits = U; endif endfunction function varValue = cell2var (C, T) ## Numeric vartypes numvartype = {'double', 'single', 'int8', 'uint8', 'int16', ... 'uint16', 'int32', 'uint32', 'int64', 'uint64'}; if (strcmp (T, "cell")) varValue = C; elseif (strcmp (T, "logical")) varValue = logical (cell2mat (C)); elseif (ismember (T, numvartype)) ## Cast each element to the declared type directly. Large integers may ## have been read as int64/uint64 (to avoid double precision loss); casting ## the whole cell2mat result would let a mixed signed/unsigned column be ## promoted to a type that drops some values, so cast element-wise instead. if (all (cellfun (@(x) isa (x, 'double'), C(:)))) varValue = cast (cell2mat (C), T); else varValue = cellfun (@(x) cast (x, T), C); endif elseif (strcmp (T, "calendarDuration")) warning ("csv2table: 'calendarDuration' strings are not converted."); varValue = C; elseif (strcmp (T, "categorical")) warning ("csv2table: 'categorical' strings are not converted."); varValue = C; elseif (strncmp (T, "datetime", 8)) ## A zone-aware datetime carries its TimeZone after 'datetime '; the date ## strings are the wall-clock time in that zone. tzargs = {}; if (numel (T) > 9) tzargs = {'TimeZone', T(10:end)}; endif ## A 'NaT' token cannot be parsed alongside date strings (the format ## cannot be inferred), so reconstruct missing entries explicitly. This ## mirrors how a 'NaN' token round-trips for a duration variable. isNaT = strcmp (strtrim (C), 'NaT'); if (any (isNaT(:))) varValue = NaT (size (C, 1), size (C, 2), tzargs{:}); if (! all (isNaT(:))) varValue(! isNaT) = datetime (C(! isNaT), tzargs{:}); endif else varValue = datetime (C, tzargs{:}); endif elseif (strcmp (T, "duration")) varValue = duration (C); elseif (strcmp (T, "string")) varValue = string (C); endif endfunction function tbl = cell2tbl (C, T, N, D, U, RowNames); ## Get names, number, and positions of top level variables [varNames, ii, varIdx] = __unique__ (N(1,:), 'stable'); varlen = numel (ii); varValues = cell (1, varlen); ## No nested table or structure if (size (T, 1) == 1) for ix = 1:varlen colIdx = varIdx == ix; varC = C(:,colIdx); varValues{ix} = cell2var (varC, T{ii(ix)}); endfor ## Table contains nested tables or structures else ## For each top level variable search for nested tables or structures for ix = 1:varlen colIdx = varIdx == ix; varC = C(:,colIdx); varN = N(:,colIdx); varT = T(:,colIdx); ## No nested table or structure in this variable if (all (__ismissing__ (varT(2,:)))) varValues{ix} = cell2var (varC, varT{1}); ## Check for structure elseif (all (strcmp (varT(1,:), 'struct'))) varValues{ix} = cell2struct (varC, varN(2,:), 2); ## Check for table elseif (all (strcmp (varT(1,:), 'table'))) ## Pass the nested descriptions/units (rows below the top-level one) ## down so the recursive call restores them on the inner table. if (isempty (D)) varD = []; else varD = D(2:end,colIdx); endif if (isempty (U)) varU = []; else varU = U(2:end,colIdx); endif varValues{ix} = cell2tbl (varC, varT(2:end,:), varN(2:end,:), ... varD, varU, []); endif endfor endif ## Create table if (isempty (RowNames)) tbl = table (varValues{:}, 'VariableNames', varNames); else tbl = table (varValues{:}, 'VariableNames', varNames, 'RowNames', RowNames); endif ## Restore variable descriptions and units, when present. The first header ## row holds each top-level variable's metadata (deeper rows belong to nested ## tables and are restored by the recursive calls above). Multicolumn ## variables repeat the entry across split columns, so take the first column ## belonging to each variable. if (! isempty (D)) tbl.Properties.VariableDescriptions = D(1,ii); endif if (! isempty (U)) tbl.Properties.VariableUnits = U(1,ii); endif endfunction function varValue = cell2auto (C, textType, datetimeType, durationTypes, ... hexType) ## Index empty cells idx = cellfun ('isempty', C); ## Numeric columns are returned as doubles, unless they hold large integers ## that were read as int64/uint64 to avoid double precision loss. if (all (cellfun ('isnumeric', C))) if (all (cellfun (@(x) isa (x, 'double'), C))) varValue = cell2mat (C); elseif (any (cellfun (@(x) isa (x, 'int64'), C))) varValue = cellfun (@(x) int64 (x), C); # a negative large integer present else varValue = cellfun (@(x) uint64 (x), C); endif elseif (all (cellfun ('isnumeric', C(! idx)))) C(idx) = NaN; varValue = cell2mat (C); ## Mixed cells are forced to text elseif (! iscellstr (C)) if (strcmpi (textType, 'char')) varValue = cellstr (C); else varValue = string (C); endif else # cellstr ## Each detection is attempted in turn and stops at the first success, so a ## later attempt never clobbers an already-recognised datetime or duration. is_datetime = false; is_duration = false; is_hex = false; ## Check for datetime strings if (strcmpi (datetimeType, 'datetime')) try varValue = datetime (C); is_datetime = true; catch is_datetime = false; end_try_catch endif ## Check for duration strings if (! is_datetime && strcmpi (durationTypes, 'duration')) try varValue = duration (C); is_duration = true; catch is_duration = false; end_try_catch endif ## Check for hexadecimal strings (convert to integers) if (! is_datetime && ! is_duration && ! strcmpi (hexType, 'text')) hexval = hex2dec (C); if (any (isnan (hexval))) is_hex = false; else is_hex = true; varValue = hexval; ## Detect smallest integer type or cast user defined if (strcmpi (hexType, 'auto')) minval = min (varValue); maxval = max (varValue); if (minval < 0) # signed integers if (minval >= intmin ('int8') && maxval <= intmax ('int8')) varValue = cast (varValue, 'int8'); elseif (minval >= intmin ('int16') && maxval <= intmax ('int16')) varValue = cast (varValue, 'int16'); elseif (minval >= intmin ('int32') && maxval <= intmax ('int32')) varValue = cast (varValue, 'int32'); elseif (minval >= intmin ('int64') && maxval <= intmax ('int64')) varValue = cast (varValue, 'int64'); endif else # unsigned integers if (maxval <= intmax ('uint8')) varValue = cast (varValue, 'uint8'); elseif (maxval <= intmax ('uint16')) varValue = cast (varValue, 'uint16'); elseif (maxval <= intmax ('uint32')) varValue = cast (varValue, 'uint32'); elseif (maxval <= intmax ('uint64')) varValue = cast (varValue, 'uint64'); endif endif else varValue = cast (varValue, hexType); endif endif endif ## Leave it unaltered as cellstr or convert to strings if (! is_datetime && ! is_duration && ! is_hex) varValue = C; if (strcmpi (textType, 'string')) varValue = string (varValue); endif endif endif endfunction %!demo %! ## `csv2table` reads a CSV back into a table. When the file was written by %! ## `table2csv`, the typed header lets it restore each variable's type exactly. %! %! T = table ([38; 43], [71.5; 69.0], 'VariableNames', {'Age', 'Height'}); %! filename = fullfile (tempdir (), 'patients.csv'); %! table2csv (T, filename); %! csv2table (filename) %!demo %! ## For a plain CSV that has no type header, the first line is taken as the %! ## variable names and each column's type is detected automatically. Use %! ## `'ReadRowNames'` to turn the first column into row names instead of data. %! %! filename = fullfile (tempdir (), 'plain.csv'); %! fid = fopen (filename, 'w'); %! fputs (fid, "Patient,Age,Height\nLi,38,64\nDiaz,40,67\n"); %! fclose (fid); %! csv2table (filename, 'ReadRowNames', true) %! %! delete (filename); ## Test user-defined 'VariableTypes' are applied per column %!test %! fn = tempname (); %! fid = fopen (fn, "w"); %! fputs (fid, "A,B,C\n1,2.5,10\n3,4.5,20\n"); %! fclose (fid); %! unwind_protect %! t = csv2table (fn, 'ReadRowNames', false, ... %! 'VariableTypes', {'double', 'single', 'int32'}); %! assert_equal (class (t{:, 1}), 'double'); %! assert_equal (class (t{:, 2}), 'single'); %! assert_equal (class (t{:, 3}), 'int32'); %! assert_equal (t{:, 3}, int32 ([10; 20])); %! unwind_protect_cleanup %! delete (fn); %! end_unwind_protect %!error ... %! fn = tempname (); %! fid = fopen (fn, "w"); %! fputs (fid, "A,B,C\n1,2,3\n"); %! fclose (fid); %! unwind_protect %! csv2table (fn, 'ReadRowNames', false, 'VariableTypes', {'double', 'double'}); %! unwind_protect_cleanup %! delete (fn); %! end_unwind_protect ## Test a generic CSV (no vartype header) reads names and auto-detects types %!test %! fn = tempname (); %! fid = fopen (fn, "w"); %! fputs (fid, "A,B,C\n1,2.5,foo\n3,4.5,bar\n"); %! fclose (fid); %! unwind_protect %! t = csv2table (fn, 'ReadRowNames', false); %! assert_equal (t.Properties.VariableNames, {'A', 'B', 'C'}); %! assert_equal (t.A, [1; 3]); %! assert_equal (t.B, [2.5; 4.5]); %! assert_equal (t.C, {'foo'; 'bar'}); %! unwind_protect_cleanup %! delete (fn); %! end_unwind_protect ## Test 'ReadVariableNames' false generates default Var1, Var2, ... names %!test %! fn = tempname (); %! fid = fopen (fn, "w"); %! fputs (fid, "1,2\n3,4\n"); %! fclose (fid); %! unwind_protect %! t = csv2table (fn, 'ReadVariableNames', false, 'ReadRowNames', false); %! assert_equal (t.Properties.VariableNames, {'Var1', 'Var2'}); %! assert_equal (height (t), 2); %! assert_equal (t.Var1, [1; 3]); %! unwind_protect_cleanup %! delete (fn); %! end_unwind_protect ## Test 'VariableNames' overrides the names read from the header line %!test %! fn = tempname (); %! fid = fopen (fn, "w"); %! fputs (fid, "A,B\n1,2\n3,4\n"); %! fclose (fid); %! unwind_protect %! t = csv2table (fn, 'VariableNames', {'x', 'y'}, 'ReadRowNames', false); %! assert_equal (t.Properties.VariableNames, {'x', 'y'}); %! unwind_protect_cleanup %! delete (fn); %! end_unwind_protect ## Test 'NumHeaderLines' skips leading lines before the header row %!test %! fn = tempname (); %! fid = fopen (fn, "w"); %! fputs (fid, "junk,more\nA,B\n1,2\n3,4\n"); %! fclose (fid); %! unwind_protect %! t = csv2table (fn, 'NumHeaderLines', 1, 'ReadRowNames', false); %! assert_equal (t.Properties.VariableNames, {'A', 'B'}); %! assert_equal (t.A, [1; 3]); %! assert_equal (t.B, [2; 4]); %! unwind_protect_cleanup %! delete (fn); %! end_unwind_protect ## Test 'RowNamesColumn' reads a column as the table row names %!test %! fn = tempname (); %! fid = fopen (fn, "w"); %! fputs (fid, "id,v\nr1,10\nr2,20\n"); %! fclose (fid); %! unwind_protect %! t = csv2table (fn, 'ReadRowNames', true, 'RowNamesColumn', 1); %! assert_equal (t.Properties.RowNames, {'r1'; 'r2'}); %! assert_equal (t.v, [10; 20]); %! unwind_protect_cleanup %! delete (fn); %! end_unwind_protect ## Test 'TextType' string returns text columns as a string array %!test %! fn = tempname (); %! fid = fopen (fn, "w"); %! fputs (fid, "A,B\nfoo,bar\n"); %! fclose (fid); %! unwind_protect %! t = csv2table (fn, 'TextType', 'string', 'ReadRowNames', false); %! assert_equal (class (t.A), 'string'); %! unwind_protect_cleanup %! delete (fn); %! end_unwind_protect ## Test 'VariableUnitsLine' and 'VariableDescriptionsLine' read the right lines %!test %! fn = tempname (); %! fid = fopen (fn, "w"); %! fputs (fid, "A,B\nkg,m\nd1,d2\n1,2\n3,4\n"); %! fclose (fid); %! unwind_protect %! t = csv2table (fn, 'VariableUnitsLine', 2, 'VariableDescriptionsLine', 3, ... %! 'ReadRowNames', false); %! assert_equal (t.Properties.VariableUnits, {'kg', 'm'}); %! assert_equal (t.Properties.VariableDescriptions, {'d1', 'd2'}); %! assert_equal (t.A, [1; 3]); %! assert_equal (t.B, [2; 4]); %! unwind_protect_cleanup %! delete (fn); %! end_unwind_protect ## Test hexadecimal columns auto-detect to the smallest integer type %!test %! fn = tempname (); %! fid = fopen (fn, "w"); %! fputs (fid, "h\nFF\n0A\n"); %! fclose (fid); %! unwind_protect %! t = csv2table (fn, 'ReadRowNames', false); %! assert_equal (class (t.h), 'uint8'); %! assert_equal (t.h, uint8 ([255; 10])); %! unwind_protect_cleanup %! delete (fn); %! end_unwind_protect ## Test header names with spaces are made into valid variable names %!test %! fn = tempname (); %! fid = fopen (fn, "w"); %! fputs (fid, "my col,other one\n1,2\n"); %! fclose (fid); %! unwind_protect %! t = csv2table (fn, 'ReadRowNames', false); %! assert_equal (t.Properties.VariableNames, {'myCol', 'otherOne'}); %! unwind_protect_cleanup %! delete (fn); %! end_unwind_protect ## Test a generic CSV whose first cell is numeric does not error on read %!test %! fn = tempname (); %! fid = fopen (fn, "w"); %! fputs (fid, "10,20\n30,40\n"); %! fclose (fid); %! unwind_protect %! t = csv2table (fn, 'ReadVariableNames', false, 'ReadRowNames', false); %! assert_equal (height (t), 2); %! assert_equal (t.Var1, [10; 30]); %! assert_equal (t.Var2, [20; 40]); %! unwind_protect_cleanup %! delete (fn); %! end_unwind_protect ## Test duplicate row names are rejected %!error ... %! fn = tempname (); %! fid = fopen (fn, "w"); %! fputs (fid, "id,v\nr1,10\nr1,20\n"); %! fclose (fid); %! unwind_protect %! csv2table (fn, 'ReadRowNames', true, 'RowNamesColumn', 1); %! unwind_protect_cleanup %! delete (fn); %! end_unwind_protect ## Test a large integer column is read exactly as a 64-bit integer (no double loss) %!test %! fn = tempname (); %! fid = fopen (fn, "w"); %! fputs (fid, "v\n18446744073709551615\n18446744073709551614\n"); %! fclose (fid); %! unwind_protect %! t = csv2table (fn, 'ReadRowNames', false); %! assert_equal (class (t.v), 'uint64'); %! assert_equal (t.v, [intmax('uint64'); intmax('uint64') - uint64(1)]); %! unwind_protect_cleanup %! delete (fn); %! end_unwind_protect ## Test NAME input validation %!error ... %! csv2table (42); ## Test reading a non-existent file reports a clear error %!error ... %! csv2table (fullfile (tempname (), 'no_such_file.csv')); ## Test the 'Delimiter' option selects the field separator %!test %! fn = tempname (); %! fid = fopen (fn, "w"); %! fputs (fid, "A;B;C\n1;2.5;foo\n3;4.5;bar\n"); %! fclose (fid); %! unwind_protect %! t = csv2table (fn, 'Delimiter', ';', 'ReadRowNames', false); %! assert_equal (t.Properties.VariableNames, {'A', 'B', 'C'}); %! assert_equal (t.A, [1; 3]); %! assert_equal (t.C, {'foo'; 'bar'}); %! unwind_protect_cleanup %! delete (fn); %! end_unwind_protect ## A datetime column is not clobbered by the later duration/hex detection %!test %! fn = tempname (); %! fid = fopen (fn, "w"); %! fputs (fid, "id,d\n1,2024-01-15\n2,2024-02-20\n"); %! fclose (fid); %! unwind_protect %! t = csv2table (fn, 'ReadRowNames', false); %! assert_equal (class (t.d), 'datetime'); %! unwind_protect_cleanup %! delete (fn); %! end_unwind_protect ## Hexadecimal auto-detection still works after the detection-order fix %!test %! fn = tempname (); %! fid = fopen (fn, "w"); %! fputs (fid, "h\nFF\n0A\n"); %! fclose (fid); %! unwind_protect %! t = csv2table (fn, 'ReadRowNames', false); %! assert_equal (t.h, uint8 ([255; 10])); %! unwind_protect_cleanup %! delete (fn); %! end_unwind_protect pr0m1th3as-datatypes-9c9a8d3/inst/datetime.m000066400000000000000000005553231522766574100210210ustar00rootroot00000000000000## Copyright (C) 2024-2026 Andreas Bertsatos ## ## This file is part of the datatypes package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . classdef datetime ## -*- texinfo -*- ## @deftp {datatypes} datetime ## ## Array representing points in time using the Gregorian calendar. ## ## A @qcode{datetime} array stores internally the datetime points as double ## arrays representing whole years, months, days, hours, and minutes, as well ## as seconds including their fractional part. The underlying implementation ## relies on the @qcode{'date.h'} C++ library. The precision of this ## implementation is set at microseconds, which is substantial for typical ## times. ## ## A @code{datetime} array is a collection of date/time elements, with each ## element holding a complete date/time. The @code{datetime} array also has ## @qcode{TimeZone} and @qcode{Format} properties associated with it, which ## apply to all elements in the array. ## ## @seealso{calendarDuration, duration} ## @end deftp properties (Constant) ## -*- texinfo -*- ## @deftp {datetime} {property} SystemTimeZone ## ## System time zone setting ## ## A read-only property specifying the local time zone of the system, where ## Octave is running. ## ## @end deftp SystemTimeZone = localtime (time ()).zone; endproperties properties (Access = private, Hidden) ## Whole years Year = 0 ## Whole months Month = 0 ## Whole days Day = 0 ## Whole hours Hour = 0 ## Whole minutes Minute = 0 ## Seconds (including fractional seconds) Second = 0 endproperties properties ## -*- texinfo -*- ## @deftp {datetime} {property} Format ## ## Display format ## ## Display format, specified as a character vector or string scalar. If ## specified as a string scalar, it is converted and stored internally as ## a character vector. ## ## @end deftp Format = 'default' ## -*- texinfo -*- ## @deftp {datetime} {property} TimeZone ## ## Time zone ## ## Time zone, specified as a character vector or string scalar. If ## specified as a string scalar, it is converted and stored internally as ## a character vector. ## ## @end deftp TimeZone = '' endproperties methods (Hidden) ## Custom display function display (this) in_name = inputname (1); if (! isempty (in_name)) fprintf ('%s =\n', in_name); endif __disp__ (this, 'datetime', in_name); endfunction ## Custom display function disp (this) __disp__ (this, 'datetime'); endfunction endmethods ################################################################################ ## ** Create and convert 'datetime' type ** ## ################################################################################ ## Available Methods ## ## ## ## 'datetime' 'dispstrings' 'cellstr' 'char' ## ## 'ymd' 'hms' 'year' 'quarter' ## ## 'month' 'week' 'day' 'hour' ## ## 'minute' 'second' 'timeofday' 'tzoffset' ## ## ## ################################################################################ methods (Access = public) ## -*- texinfo -*- ## @deftypefn {datetime} {@var{T} =} datetime (@qcode{'now'}) ## @deftypefnx {datetime} {@var{T} =} datetime (@qcode{'today'}) ## @deftypefnx {datetime} {@var{T} =} datetime (@qcode{'tomorrow'}) ## @deftypefnx {datetime} {@var{T} =} datetime (@qcode{'yesterday'}) ## @deftypefnx {datetime} {@var{T} =} datetime (@var{DateStrings}) ## @deftypefnx {datetime} {@var{T} =} datetime (@var{DateStrings}, @qcode{'InputFormat'}, @var{INFMT}) ## @deftypefnx {datetime} {@var{T} =} datetime (@var{DateStrings},@ ## @qcode{'InputFormat'}, @var{INFMT}, @qcode{'PivotYear'}, @var{PIVOT}) ## @deftypefnx {datetime} {@var{T} =} datetime (@var{DateVectors}) ## @deftypefnx {datetime} {@var{T} =} datetime (@var{Y}, @var{MO}, @var{D}) ## @deftypefnx {datetime} {@var{T} =} datetime (@var{Y}, @var{MO}, @var{D}, @var{H}, @var{MI}, @var{S}) ## @deftypefnx {datetime} {@var{T} =} datetime (@var{Y}, @var{MO}, @var{D}, @var{H}, @var{MI}, @var{S}, @var{MS}) ## @deftypefnx {datetime} {@var{T} =} datetime (@var{X}, @qcode{'ConvertFrom'}, @var{TYPE}) ## @deftypefnx {datetime} {@var{T} =} datetime (@dots{}, @qcode{'Format'}, @var{FMT}) ## @deftypefnx {datetime} {@var{T} =} datetime (@dots{}, @qcode{'TimeZone'}, @var{TZ}) ## ## Create a new array of datetime values. ## ## @code{@var{T} = datetime (@qcode{'now'})} returns a scalar datetime array ## corresponding to the current date and time. @code{@var{T} = datetime ## (@qcode{'now'})} is the same as @code{@var{T} = datetime ()}. Except for ## @qcode{'now'}, the same syntax can be used with @qcode{'today'}, ## @qcode{'tomorrow'} and @qcode{'yesterday'}. These options return the ## respective date but with time set at midnight. ## ## @code{@var{T} = datetime (@var{DateStrings})} creates a datetime array ## from the text in @var{DateStrings} representing points in time. In ## current implementation, @var{DateStrings} are parsed by Octave's core ## @code{datevec} function, hence supported text formats are currently those ## supported by @code{datevec}. ## ## @code{@var{T} = datetime (@var{DateStrings}, @qcode{'InputFormat'}, ## @var{INFMT})} also allows to specify a particular input text format to ## parse @var{DateStrings}. It is always preferable to specify the format ## @var{INFMT} if it is known. Formats which do not specify a particular ## time component will have the value set to zero. Formats which do not ## a date will default to January 1st of the current year. ## ## @code{@var{T} = datetime (@var{DateStrings}, @qcode{'InputFormat'}, ## @var{INFMT}, @qcode{'PivotYear'}, @var{PIVOT})} also allows to specify a ## pivot year, which refers to the year at the start of the century to which ## two-digit years will be referenced. When not specified, it defaults to ## the current years minus 50. ## ## @code{@var{T} = datetime (@var{DateVectors})} creates a column vector of ## datetime values from the date vectors in @var{DateVectors}. ## ## @code{@var{T} = datetime (@var{Y}, @var{MO}, @var{D}} creates an array of ## datetime values for corresponding elements of the @var{Y}, @var{MO}, and ## @var{D} arrays, which must be of the same size or scalars, must contain ## integer values, and they correspond to years, months, and days, ## respectively. ## ## @code{@var{T} = datetime (@var{Y}, @var{MO}, @var{D}, @var{H}, @var{MI}, ## @var{S})} also adds time components to the constructed datetime arrays. ## @var{H}, @var{MI}, and @var{S} must be of the same size or scalars. ## @var{H} and @var{MI} must contain integer values, whereas @var{S} may ## also contain a fractional part. ## ## @code{@var{T} = datetime (@var{Y}, @var{MO}, @var{D}, @var{H}, @var{MI}, ## @var{S}, @var{MS})} also adds a milliseconds component, @var{MS}, which ## may also have a fractional part. ## ## @code{@var{T} = datetime (@var{X}, @qcode{'ConvertFrom'}, @var{TYPE})} ## converts the numeric values in @var{X} to a datetime array according to ## the data type specified by @var{TYPE}. The following types are ## supported: ## ## @itemize ## @item @qcode{'datenum'} ## @item @qcode{'excel'} ## @item @qcode{'posixtime'} ## @item @qcode{'epochtime'} ## @end itemize ## ## @code{@var{T} = datetime (@dots{}, @qcode{'Format'}, @var{FMT})} ## specifies the display format of the values in the output datetime array. ## Currently, only the default display format is implemented. ## ## @code{@var{T} = datetime (@dots{}, @qcode{'TimeZone'}, @var{TZ})} sets ## the time zone to the values in the output datetime array. If not ## specified, the computer's local timezone is used. Supported time zones ## are specified in the IANA's Time Zone Database. You may specify a new ## time zone by setting the @qcode{'TimeZone'} property of the datetime ## array, in which case the new datetime values may include Daylight Saving ## Time (DST) in their computation. ## ## @seealso{NaT, datetime, isdatetime, calendarDuration, duration} ## @end deftypefn function this = datetime (varargin) ## Return an scalar datetime object with current local time if (nargin == 0) [this.Year, this.Month, this.Day, this.Hour, this.Minute, ... this.Second] = __datetime__ ('now'); return; endif ## Parse optional Name-Value paired arguments optNames = {'ConvertFrom', 'Format', 'InputFormat', ... 'Locale', 'PivotYear', 'TimeZone'}; dfValues = {[], [], [], [], [], []}; [ConvertFrom, Format, inputFormat, Locale, PivotYear, TimeZone, args] =... parsePairedArguments (optNames, dfValues, varargin(:)); ## Check optional 'Format' and 'InputFormat' arguments if (! isempty (ConvertFrom)) ## Call __datetime__ to check for valid ConvertFrom string and ## data input [~,~,~,~,~,~,errmsg] = __datetime__ (args{:}, 'ConvertFrom', ... ConvertFrom); if (! isnumeric (errmsg)) error ("datetime: %s ", errmsg); elseif (! isempty (inputFormat)) error ("datetime: 'ConvertFrom' cannot be used with 'InputFormat'."); endif endif if (! isempty (Format)) if (! (ischar (Format) && isvector (Format))) error ("datetime: 'Format' must be a character vector."); endif this.Format = Format; endif if (! isempty (inputFormat)) if (! (ischar (inputFormat) && isvector (inputFormat))) error ("datetime: 'InputFormat' must be a character vector."); elseif (! isempty (ConvertFrom)) error ("datetime: 'InputFormat' cannot be used with 'ConvertFrom'."); elseif (strcmpi (this.Format, 'preserveinput')) this.Format = inputFormat; endif endif if (! isempty (Locale)) if (! (ischar (Locale) && isvector (Locale))) error ("datetime: 'Locale' must be a character vector."); else warning ("datetime: 'Locale' is currently unimplemented."); endif endif if (! isempty (PivotYear)) if (isempty (inputFormat)) error ("datetime: 'PivotYear' can only be used with 'InputFormat'."); elseif (! (isnumeric (PivotYear) && isscalar (PivotYear))) error ("datetime: 'PivotYear' must be a numeric scalar."); elseif (fix (PivotYear) != PivotYear) error ("datetime: 'PivotYear' must be an integer value."); endif endif if (! isempty (TimeZone)) ## Call __datetime__ to check for valid timezone string [~,~,~,~,~,~,errmsg] = __datetime__ (0, 0, 0, 'TimeZone', TimeZone); if (! isnumeric (errmsg)) error ("datetime: %s ", errmsg); endif this.TimeZone = TimeZone; endif ## Datestrings are currently handled by 'datevec' if (iscellstr (args{1}) || isstring (args{1}) || ischar (args{1})) DateStrings = ""; if (ischar (args{1}) && ! isvector (args{1})) error ("datetime: invalid type for 'DateStrings'."); elseif (ischar (args{1}) && ! any (strcmpi (args, {'now', 'today', 'yesterday', 'tomorrow'}))) DateStrings = cellstr (args{1}); elseif (! ischar (args{1})) DateStrings = cellstr (args{1}); endif if (! isempty (DateStrings)) if (! isempty (inputFormat) && ! isempty (PivotYear)) fcn = @(x) datevec (x, inputFormat, PivotYear); elseif (! isempty (inputFormat) && isempty (PivotYear)) fcn = @(x) datevec (x, inputFormat); elseif (isempty (inputFormat) && ! isempty (PivotYear)) fcn = @(x) datevec (x, PivotYear); else fcn = @(x) datevec (x); endif try DATEVEC = cellfun (fcn, DateStrings, "UniformOutput", false); DATEVEC = cell2mat (DATEVEC(:)); catch if (! isempty (inputFormat)) error ("datetime: invalid 'inputFormat'."); else error (strcat ("datetime: could not recognize date/time", ... " format from input.")); endif end_try_catch ## Split DATEVEC into individual date/time units and reshape this.Year = reshape (DATEVEC(:,1), size (DateStrings)); this.Month = reshape (DATEVEC(:,2), size (DateStrings)); this.Day = reshape (DATEVEC(:,3), size (DateStrings)); this.Hour = reshape (DATEVEC(:,4), size (DateStrings)); this.Minute = reshape (DATEVEC(:,5), size (DateStrings)); this.Second = reshape (DATEVEC(:,6), size (DateStrings)); return; endif endif ## Handle inputs (no errors here) if (! isempty (ConvertFrom) && ! isempty (TimeZone)) [this.Year, this.Month, this.Day, this.Hour, this.Minute, ... this.Second] = __datetime__ (args{1}, 'ConvertFrom', ConvertFrom, ... 'TimeZone', TimeZone); elseif (! isempty (ConvertFrom) && isempty (TimeZone)) [this.Year, this.Month, this.Day, this.Hour, this.Minute, ... this.Second] = __datetime__ (args{1}, 'ConvertFrom', ConvertFrom); else [this.Year, this.Month, this.Day, this.Hour, this.Minute, ... this.Second] = __datetime__ (args{:}); endif endfunction ## -*- texinfo -*- ## @deftypefn {datetime} {@var{cstr} =} dispstrings (@var{T}) ## ## Get display formatted strings for each element of a datetime object. ## ## @code{@var{cstr} = dispstrings (@var{T})} returns a cellstr array of ## character vectors, @var{cstr}, which has the same size as the input ## datetime @var{T}. ## ## @end deftypefn function cstr = dispstrings (this) ## Default display format mnames = {'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', ... 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'}; ## Process all elements sz = size (this); cstr = cell (sz); for i = 1:prod (sz) if (isnan (this.Year(i))) cstr{i} = 'NaT'; elseif (isinf (this.Year(i))) cstr{i} = num2str (this.Year(i)); else if (this.Hour(i) != 0 || this.Minute(i) != 0 || this.Second(i) != 0) if (fix (this.Second(i)) == this.Second(i)) str = sprintf ("%02d-%s-%04d %02d:%02d:%02d", this.Day(i), ... mnames{this.Month(i)}, this.Year(i), ... this.Hour(i), this.Minute(i), this.Second(i)); else fmt = "%02d-%s-%04d %02d:%02d:%02d.%03d"; sec = this.Second(i); str = sprintf (fmt, this.Day(i), mnames{this.Month(i)}, ... this.Year(i), this.Hour(i), this.Minute(i), ... fix (sec), rem (sec, 1) * 1000); endif else str = sprintf ("%02d-%s-%04d", this.Day(i), ... mnames{this.Month(i)}, this.Year(i)); endif cstr{i} = str; endif endfor endfunction ## -*- texinfo -*- ## @deftypefn {datetime} {@var{cstr} =} cellstr (@var{T}) ## @deftypefnx {datetime} {@var{cstr} =} cellstr (@var{T}, @var{Format}) ## ## Convert datetime array to a cell array of character vectors. ## ## @code{@var{cstr} = cellstr (@var{T})} returns a cellstr array of ## character vectors, @var{cstr}, which has the same size as the input ## datetime @var{T}. ## ## @end deftypefn function cstr = cellstr (this, Format = '') if (! isempty (Format)) if (! (ischar (Format) && isvector (Format))) error ("datetime.cellstr: FORMAT must be a character vector."); endif this.Format = Format; endif cstr = dispstrings (this); endfunction ## -*- texinfo -*- ## @deftypefn {datetime} {@var{cmat} =} char (@var{T}) ## @deftypefnx {datetime} {@var{cmat} =} char (@var{T}, @var{Format}) ## ## Convert datetime array to a character matrix. ## ## @code{@var{cmat} = char (@var{T})} returns a character matrix with one ## row per element in @var{T}. ## ## @end deftypefn function cmat = char (this, Format = '') cmat = char (cellstr (this, Format)); endfunction ## -*- texinfo -*- ## @deftypefn {datetime} {[@var{Y}, @var{M}, @var{D}] =} ymd (@var{T}) ## ## Year, Month, and Day components of datetime array. ## ## @code{[@var{Y}, @var{M}, @var{D}] = ymd (@var{T})} returns the year, ## month, and day components of the corresponding datetime values in @var{T} ## as separate numeric arrays. @var{Y}, @var{M}, @var{D} contain integer ## values and have the same size as @var{T}. Not-A-Time (@qcode{NaT}) ## values in @var{T} are returned as @qcode{NaN} in the output arrays. ## ## @end deftypefn function [Y, M, D] = ymd (this) Y = this.Year; M = this.Month; D = this.Day; endfunction ## -*- texinfo -*- ## @deftypefn {datetime} {[@var{h}, @var{m}, @var{s}] =} hms (@var{T}) ## ## Hour, Minute, and Second components of a datetime array. ## ## @code{[@var{h}, @var{m}, @var{s}] = hms (@var{T})} returns the hour, ## minute, and second components of the corresponding datetime values in ## @var{T} as separate numeric arrays. @var{h} and @var{m} contain integer ## values and @var{s} may also contain a fractional part. All outputs have ## the same size as @var{T}. Not-A-Time (@qcode{NaT}) values in @var{T} are ## returned as @qcode{NaN} in the output arrays. ## ## @end deftypefn function [h, m, s] = hms (this) h = this.Hour; m = this.Minute; s = this.Second; endfunction ## -*- texinfo -*- ## @deftypefn {datetime} {@var{Y} =} year (@var{T}) ## ## Year component of a datetime array. ## ## @code{@var{Y} = year (@var{T})} returns the year number for each element ## of the input datetime array @var{T}. The output @var{Y} is a ## @qcode{double} array and it has the same size as @var{T}. Not-A-Time ## (@qcode{NaT}) values in @var{T} are returned as @qcode{NaN} in the output ## array. ## ## @end deftypefn function out = year (this) out = this.Year; endfunction ## -*- texinfo -*- ## @deftypefn {datetime} {@var{Q} =} quarter (@var{T}) ## ## Quarter component of a datetime array. ## ## @code{@var{Q} = quarter (@var{T})} returns the quarter number for each ## element of the input datetime array @var{T}. The output @var{Q} is a ## @qcode{double} array containing integer values in the range @math{[1, 4]} ## and it has the same size as @var{T}. Not-A-Time (@qcode{NaT}) values in ## @var{T} are returned as @qcode{NaN} in the output array. ## ## @end deftypefn function out = quarter (this) out = ceil (this.Month / 3); endfunction ## -*- texinfo -*- ## @deftypefn {datetime} {@var{M} =} month (@var{T}) ## @deftypefnx {datetime} {@var{M} =} month (@var{T}, @var{monthType}) ## ## Month component of a datetime array. ## ## @code{@var{M} = month (@var{T})} returns the month number for each ## element of the input datetime array @var{T}. The output @var{M} is a ## @qcode{double} array containing integer values in the range ## @math{[1, 12]} and it has the same size as @var{T}. Not-A-Time ## (@qcode{NaT}) values in @var{T} are returned as @qcode{NaN} in the output ## array. ## ## @code{@var{M} = month (@var{T}, @var{monthType})} returns the month ## number or name for each element of the input datetime array @var{T} as ## specified by @var{monthType}, which may have any of the following ## options: ## ## @itemize ## @item @qcode{'monthofyear'} (default) returns the month number in a ## numeric array. ## @item @qcode{'name'} returns the corresponding full name of the month in ## a cell array of character vectors. ## @item @qcode{'shortname'} returns the corresponding 3-letter abbreviation ## of the month in a cell array of character vectors. ## @end itemize ## ## @end deftypefn function out = month (this, type = 'monthofyear') if (strcmpi (type, 'monthofyear')) out = this.Month; elseif (strcmpi (type, 'name')) mn = {'January', 'February', 'March', 'April', 'May', 'June', ... 'July', 'August', 'September', 'October', 'November', 'December'}; out = mn(this.Month); elseif (strcmpi (type, 'shortname')) mn = {'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', ... 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'}; out = mn(this.Month); else error ("datetime: unrecognized MONTHTYPE."); endif endfunction ## -*- texinfo -*- ## @deftypefn {datetime} {@var{D} =} day (@var{T}) ## @deftypefnx {datetime} {@var{D} =} day (@var{T}, @var{dayType}) ## ## Day component of a datetime array. ## ## @code{@var{D} = day (@var{T})} returns the day number for each element of ## the input datetime array @var{T}. The output @var{D} is a @qcode{double} ## array containing integer values in the range @math{[1, 31]}, depending on ## the month and year, and it has the same size as @var{T}. Not-A-Time ## (@qcode{NaT}) values in @var{T} are returned as @qcode{NaN} in the output ## array. ## ## @code{@var{D} = day (@var{T}, @var{dayType})} returns the day number or ## name for each element of the input datetime array @var{T} as specified by ## @var{dayType}, which may have any of the following options: ## ## @itemize ## @item @qcode{'dayofmonth'} (default) returns the day-of-month number in a ## numeric array. Depending on the month and year, it can range from 1 to ## 28, 29, 30, or 31. ## @item @qcode{'dayofweek'} returns the day-of-week number in a numeric ## array of @qcode{double} integer values ranging from 1 to 7, where 1 ## corresponds to Sunday. ## @item @qcode{'iso-dayofweek'} returns the day-of-week number in a numeric ## array of @qcode{double} integer values ranging from 1 to 7, where 1 ## corresponds to Monday according to the ISO 8601 standard. ## @item @qcode{'dayofyear'} returns the day-of-year number in a numeric ## array of @qcode{double} integer values ranging from 1 to 365 or 366, ## depending on the year. ## @item @qcode{'name'} returns the corresponding full name of the day in ## a cell array of character vectors. ## @item @qcode{'shortname'} returns the corresponding 3-letter abbreviation ## of the day in a cell array of character vectors. ## @end itemize ## ## @end deftypefn function out = day (this, type = 'dayofmonth') vtypes = {'dayofweek', 'iso-dayofweek', 'name', 'shortname'}; if (strcmpi (type, 'dayofmonth')) out = this.Day; elseif (any (strcmpi (type, vtypes))) m = this.Month - 2; y = this.Year; ## Compute the Jan/Feb borrow mask once, before mutating 'm'; adjusting ## 'm' first would clear the mask before 'y' is decremented. janfeb = m < 1; m(janfeb) += 12; y(janfeb) -= 1; K = mod (y, 100); J = floor (y ./ 100); code = floor ((26 .* m - 2) ./ 10); out = mod ((this.Day + code + K + floor (K ./ 4) ... + floor (J ./ 4) + 5 .* J), 7) + 1; if (strcmpi (type, 'iso-dayofweek')) out = mod (out - 1, 7); out(out == 0) = 7; elseif (strcmpi (type, 'name')) dn = {'Sunday', 'Monday', 'Tuesday', 'Wednesday', ... 'Thursday', 'Friday', 'Saturday'}; out = dn(out); elseif (strcmpi (type, 'shortname')) dn = {'Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'}; out = dn(out); endif elseif (strcmpi (type, 'dayofyear')) m = this.Month; y = this.Year; ## Column vector so that logical-indexed lookups keep column shape. cumdays = [0; 31; 59; 90; 120; 151; 181; 212; 243; 273; 304; 334]; isly = mod (y, 4) == 0 & (mod (y, 100) != 0 | mod (y, 400) == 0); out = nan (size (m)); valid = isfinite (m) & isfinite (this.Day); out(valid) = cumdays(m(valid)) + this.Day(valid)(:) ... + double (isly(valid) & m(valid) > 2)(:); else error ("datetime: unrecognized DAYTYPE."); endif endfunction ## -*- texinfo -*- ## @deftypefn {datetime} {@var{h} =} hour (@var{T}) ## ## Hour component of a datetime array. ## ## @code{@var{h} = hour (@var{T})} returns the hour number for each element ## of the input datetime array @var{T}. The output @var{h} is a ## @qcode{double} array containing integer values in the range ## @math{[0, 23]} and it has the same size as @var{T}. Not-A-Time ## (@qcode{NaT}) values in @var{T} are returned as @qcode{NaN} in the output ## array. ## ## @end deftypefn function out = hour (this) out = this.Hour; endfunction ## -*- texinfo -*- ## @deftypefn {datetime} {@var{m} =} minute (@var{T}) ## ## Minute component of a datetime array. ## ## @code{@var{m} = minute (@var{T})} returns the minute number for each ## element of the input datetime array @var{T}. The output @var{m} is a ## @qcode{double} array containing integer values in the range ## @math{[0, 59]} and it has the same size as @var{T}. Not-A-Time ## (@qcode{NaT}) values in @var{T} are returned as @qcode{NaN} in the output ## array. ## ## @end deftypefn function out = minute (this) out = this.Minute; endfunction ## -*- texinfo -*- ## @deftypefn {datetime} {@var{s} =} second (@var{T}) ## @deftypefnx {datetime} {@var{s} =} second (@var{T}, @var{secondType}) ## ## Seconds component of a datetime array. ## ## @code{@var{s} = second (@var{T})} returns the number of seconds for each ## element of the input datetime array @var{T}. The output @var{s} is a ## @qcode{double} array containing values in the range @math{[0, 60)}, ## including any fractional part of the second, and it has the same size as ## @var{T}. Not-A-Time (@qcode{NaT}) values in @var{T} are returned as ## @qcode{NaN} in the output array. ## ## @code{@var{s} = second (@var{T}, @var{secondType})} returns the seconds ## for each element of the input datetime array @var{T} as specified by ## @var{secondType}, which may have any of the following options: ## ## @itemize ## @item @qcode{'secondofminute'} (default) returns the second of the minute ## in a numeric array, in the range @math{[0, 60)}. ## @item @qcode{'secondofday'} returns the second of the day in a numeric ## array, in the range @math{[1, 86400)}. ## @end itemize ## ## @end deftypefn function out = second (this, secondType = 'secondofminute') if (strcmpi (secondType, 'secondofminute')) out = this.Second; elseif (strcmpi (secondType, 'secondofday')) out = this.Hour * 3600 + this.Minute * 60 + this.Second; else error ("datetime.second: unrecognized SECONDTYPE."); endif endfunction endmethods methods (Hidden) function out = week (this) error ("datetime.week: not implemented yet."); endfunction function out = timeofday (this) error ("datetime.timeofday: not implemented yet."); endfunction function out = tzoffset (this) error ("datetime.tzoffset: not implemented yet."); endfunction endmethods ################################################################################ ## ** Summary Information ** ## ################################################################################ ## Available Methods ## ## ## ## 'size' 'ndims' 'numel' 'length' ## ## 'keyHash' ## ## ## ################################################################################ methods (Access = public) ## -*- texinfo -*- ## @deftypefn {datetime} {@var{sz} =} size (@var{T}) ## @deftypefnx {datetime} {@var{dim_sz} =} size (@var{T}, @var{dim}) ## @deftypefnx {datetime} {@var{dim_sz} =} size (@var{T}, @var{d1}, @var{d2}, @dots{}) ## @deftypefnx {datetime} {[@var{rows}, @var{columns}, @dots{}, @var{dim_n_sz}] =} size (@dots{}) ## ## Size of a datetime array. ## ## @code{@var{sz} = size (@var{T})} returns a row vector with the size ## (number of elements) of each dimension for the datetime array @var{T}. ## ## @code{@var{dim_sz} = size (@var{T}, @var{dim})} returns the size of ## the corresponding dimension specified in @var{dim}. If @var{dim} is a ## vector, then @var{dim_sz} is a vector of the same length and with each ## element corresponding to a specified dimension. Multiple dimensions may ## also be specified as separate arguments. ## ## With a single output argument, @code{size} returns a row vector. When ## called with multiple output arguments, @code{size} returns the size of ## dimension N in the Nth argument. ## ## @end deftypefn function varargout = size (this, varargin) if (! isempty (varargin)) sz = size (this.Year, varargin{:}); else sz = size (this.Year); endif if (nargout == 0 || nargout == 1) varargout{1} = sz; elseif (numel (sz) != nargout) error (["datetime.size: nargout > 1 but does not", ... " match number of requested dimensions."]); else for i = 1:nargout varargout{i} = sz(i); endfor endif endfunction ## -*- texinfo -*- ## @deftypefn {datetime} {@var{out} =} ndims (@var{T}) ## ## Number of dimensions in a datetime array. ## ## @code{@var{out} = ndims (@var{T})} returns the number of dimensions of ## the datetime array @var{T}. ## ## @end deftypefn function out = ndims (this) out = ndims (this.Year); endfunction ## -*- texinfo -*- ## @deftypefn {datetime} {@var{out} =} numel (@var{T}) ## ## Total number of elements in a datetime array. ## ## @code{@var{out} = numel (@var{T})} returns the number of elements in the ## datetime array @var{T}, which is the product of the sizes of its ## dimensions. ## ## @end deftypefn function out = numel (this, varargin) out = prod (size (this)); endfunction ## -*- texinfo -*- ## @deftypefn {datetime} {@var{N} =} length (@var{T}) ## ## Length of a datetime vector. ## ## @code{@var{N} = length (@var{T})} returns the size of the longest ## dimension of the datetime array @var{T}, unless any of its dimensions ## has zero length, in which case @code{length (@var{T})} returns 0. ## ## @end deftypefn function N = length (this) if (isempty (this.Year)) N = 0; else N = max (size (this.Year)); endif endfunction ## -*- texinfo -*- ## @deftypefn {datetime} {@var{hey} =} keyHash (@var{T}) ## ## Generate a hash code for datetime array. ## ## @code{@var{h} = keyHash (@var{T})} generates a @qcode{uint64} scalar that ## represents the input array @var{T}. @code{keyHash} utilizes the 64-bit ## FNV-1a variant of the Fowler-Noll-Vo non-cryptographic hash function. ## ## @code{@var{h} = keyHash (@var{T}), @var{base}} also generates a 64-bit ## hash code using @var{base} as the offset basis for the FNV-1a hash ## algorithm. @var{base} must be a @qcode{uint64} integer type scalar. Use ## this syntax to cascade @code{keyHash} on multiple objects for which a ## single hash code is required. ## ## Note that unlike MATLAB, this implementation does not use any random ## seed. As a result, @code{keyHash} will always generate the exact same ## hash key for any particular input across different workers and Octave ## sessions. ## ## @end deftypefn function key = keyHash (this, base = []) ## Initialize string with size and class name size_str = sprintf ('%dx', size (this.Year))(1:end-1); flag_str = sprintf ('-TZ%s:', this.TimeZone); init_str = [size_str 'datetime' flag_str]; if (base) if (! (isscalar (base) && isa (base, 'uint64'))) error ("datetime.keyHash: BASE must be a UINT64 scalar."); endif key = __ckeyHash__(init_str, base); else key = __ckeyHash__(init_str); endif if (! isempty (this.Year)) key = __nkeyHash__(this.Year(:), key); key = __nkeyHash__(this.Month(:), key); key = __nkeyHash__(this.Day(:), key); key = __nkeyHash__(this.Hour(:), key); key = __nkeyHash__(this.Minute(:), key); key = __nkeyHash__(this.Second(:), key); endif endfunction endmethods ################################################################################ ## ** Convert to other Numeric Representations ** ## ################################################################################ ## Available Methods ## ## ## ## 'convertTo' 'exceltime' 'posixtime' 'juliandate' ## ## 'yyyymmdd' 'datevec' ## ## ## ################################################################################ methods (Hidden) function out = convertTo (this, varargin) error ("datetime.convertTo: not implemented yet."); endfunction function out = exceltime (this, varargin) error ("datetime.exceltime: not implemented yet."); endfunction function out = posixtime (this, varargin) error ("datetime.posixtime: not implemented yet."); endfunction function out = juliandate (this, varargin) error ("datetime.juliandate: not implemented yet."); endfunction function out = yyyymmdd (this, varargin) error ("datetime.yyyymmdd: not implemented yet."); endfunction endmethods methods (Access = public) ## -*- texinfo -*- ## @deftypefn {datetime} {@var{DV} =} datevec (@var{T}) ## ## Convert datetime array to date vectors. ## ## @code{@var{DV} = datevec (@var{T})} returns a numeric matrix with one row ## per element in @var{T}. ## ## @end deftypefn function varargout = datevec (this) DV = [this.Year(:), this.Month(:), this.Day(:), ... this.Hour(:), this.Minute(:), this.Second(:)]; if (nargout == 0 || nargout == 1) varargout{1} = DV; elseif (nargout <= 6) for i = 1:nargout varargout{i} = DV(:,i); endfor else error ("datetime.datevec: too many output arguments."); endif endfunction endmethods ################################################################################ ## ** Query Operations ** ## ################################################################################ ## Available Methods ## ## ## ## 'isbetween' 'iscolumn' 'isdst' 'isempty' ## ## 'isequal' 'isequaln' 'isfinite' 'isinf' ## ## 'ismatrix' 'ismember' 'isnat' 'isregular' ## ## 'isrow' 'isscalar' 'issorted' 'issortedrows' ## ## 'isvector' 'isweekend' ## ## ## ################################################################################ methods (Hidden) function TF = isdst (this) error ("datetime.isdst: not implemented yet."); endfunction function TF = isregular (this) error ("datetime.isregular: not implemented yet."); endfunction function TF = isweekend (this) error ("datetime.isweekend: not implemented yet."); endfunction endmethods methods (Access = public) ## -*- texinfo -*- ## @deftypefn {datetime} {@var{TF} =} isbetween (@var{X}, @var{lower}, @var{upper}) ## @deftypefnx {datetime} {@var{TF} =} isbetween (@var{X}, @var{lower}, @var{upper}, @var{intervalType}) ## ## Determine which datetime values lie within an interval. ## ## @code{@var{TF} = isbetween (@var{X}, @var{lower}, @var{upper})} returns a ## logical array @var{TF}, the same size as the broadcast of its inputs, ## containing @qcode{true} where the element of @var{X} lies between the ## corresponding @var{lower} and @var{upper} bounds. @var{lower} and ## @var{upper} may each be a datetime array or a date/time character vector, ## string array, or cell array of character vectors, and either may be scalar ## to broadcast against @var{X}. The comparison is made on the absolute ## instant, so zoned inputs may be in different time zones. A Not-A-Time ## element in any input makes the corresponding result @qcode{false}. ## ## @code{@var{TF} = isbetween (@var{X}, @var{lower}, @var{upper}, ## @var{intervalType})} selects which endpoints are included. ## @var{intervalType} is @qcode{'closed'} (the default, ## @w{@var{lower} @leq{} @var{X} @leq{} @var{upper}}), @qcode{'open'} ## (both endpoints excluded), @qcode{'openleft'} (exclude @var{lower}), or ## @qcode{'openright'} (exclude @var{upper}). ## ## @end deftypefn function TF = isbetween (X, varargin) if (numel (varargin) < 2) error ("datetime.isbetween: not enough input arguments."); endif if (numel (varargin) > 3) error ("datetime.isbetween: too many input arguments."); endif lo = varargin{1}; hi = varargin{2}; itype = 'closed'; if (numel (varargin) == 3) itype = varargin{3}; if (! (ischar (itype) && isrow (itype) && any (strcmpi (itype, ... {'closed', 'open', 'openleft', 'openright'})))) error (strcat ("datetime.isbetween: interval type must be", ... " 'closed', 'open', 'openleft', or 'openright'.")); endif endif if (isa (X, 'datetime')) ref = X; elseif (isa (lo, 'datetime')) ref = lo; else ref = hi; endif X = dtIsbetweenArg (X, ref); lo = dtIsbetweenArg (lo, ref); hi = dtIsbetweenArg (hi, ref); zoned = [! isempty(X.TimeZone), ! isempty(lo.TimeZone), ! isempty(hi.TimeZone)]; if (any (zoned) && ! all (zoned)) error (strcat ("datetime.isbetween: cannot combine a datetime array", ... " with a time zone with one without a time zone.")); endif sX = serial (X); sL = serial (lo); sU = serial (hi); switch (lower (itype)) case 'closed' TF = sL <= sX & sX <= sU; case 'open' TF = sL < sX & sX < sU; case 'openleft' TF = sL < sX & sX <= sU; case 'openright' TF = sL <= sX & sX < sU; endswitch endfunction ## -*- texinfo -*- ## @deftypefn {datetime} {@var{TF} =} issorted (@var{A}) ## @deftypefnx {datetime} {@var{TF} =} issorted (@var{A}, @var{dim}) ## @deftypefnx {datetime} {@var{TF} =} issorted (@var{A}, @var{direction}) ## @deftypefnx {datetime} {@var{TF} =} issorted (@var{A}, @var{dim}, @var{direction}) ## ## Determine whether a datetime array is sorted. ## ## @code{@var{TF} = issorted (@var{A})} returns @qcode{true} if the elements ## of the datetime array @var{A} are sorted in ascending (non-decreasing) ## order along its first non-singleton dimension, and @qcode{false} ## otherwise. For a matrix, every column (or row, depending on the operating ## dimension) must be sorted for @var{TF} to be @qcode{true}. Not-A-Time ## (@qcode{NaT}) elements are treated as greater than any other value, so an ## array is sorted in ascending order only when its @qcode{NaT} elements ## come last. ## ## @code{@var{TF} = issorted (@var{A}, @var{dim})} operates along dimension ## @var{dim}. ## ## @code{@var{TF} = issorted (@var{A}, @var{direction})} tests whether the ## elements are sorted according to @var{direction}, which may be one of: ## ## @itemize ## @item @qcode{'ascend'} (default) tests non-decreasing order. ## @item @qcode{'descend'} tests non-increasing order. ## @item @qcode{'monotonic'} tests non-decreasing or non-increasing order. ## @item @qcode{'strictascend'} tests strictly increasing order. ## @item @qcode{'strictdescend'} tests strictly decreasing order. ## @item @qcode{'strictmonotonic'} tests strictly monotonic order. ## @end itemize ## ## @end deftypefn function TF = issorted (A, varargin) dim = []; direction = 'ascend'; valid = {'ascend', 'descend', 'monotonic', 'strictascend', ... 'strictdescend', 'strictmonotonic'}; for i = 1:numel (varargin) arg = varargin{i}; if (isnumeric (arg)) if (! isscalar (arg) || arg < 1 || arg != fix (arg)) error ("datetime.issorted: DIM must be a positive integer."); endif dim = arg; elseif (ischar (arg) && isrow (arg)) didx = find (strcmpi (arg, valid)); if (isempty (didx)) error ("datetime.issorted: invalid DIRECTION '%s'.", arg); endif direction = valid{didx}; else error ("datetime.issorted: invalid input argument."); endif endfor if (isempty (dim)) dim = find (size (A) != 1, 1); if (isempty (dim)) dim = 1; endif endif ## NaT sorts as greater than any value; map it to +Inf on the serial. M = serial (A); M(isnan (M)) = Inf; if (dim > 2 || size (M, dim) < 2) TF = true; return; endif if (dim == 2) M = M.'; endif lo = M(1:end-1, :); hi = M(2:end, :); switch (direction) case 'ascend' ok = all (lo <= hi, 1); case 'descend' ok = all (lo >= hi, 1); case 'strictascend' ok = all (lo < hi, 1); case 'strictdescend' ok = all (lo > hi, 1); case 'monotonic' ok = all (lo <= hi, 1) | all (lo >= hi, 1); case 'strictmonotonic' ok = all (lo < hi, 1) | all (lo > hi, 1); endswitch TF = all (ok(:)); endfunction ## -*- texinfo -*- ## @deftypefn {datetime} {@var{TF} =} issortedrows (@var{A}) ## @deftypefnx {datetime} {@var{TF} =} issortedrows (@var{A}, @var{column}) ## @deftypefnx {datetime} {@var{TF} =} issortedrows (@var{A}, @var{direction}) ## @deftypefnx {datetime} {@var{TF} =} issortedrows (@var{A}, @var{column}, @var{direction}) ## ## Determine whether the rows of a datetime array are sorted. ## ## @code{@var{TF} = issortedrows (@var{A})} returns @qcode{true} if the rows ## of the 2-D datetime array @var{A} are sorted in ascending order, i.e.@: ## lexicographically by the first column, ties broken by the second column, ## and so on, and @qcode{false} otherwise. Not-A-Time (@qcode{NaT}) elements ## are treated as greater than any other value. ## ## @code{@var{TF} = issortedrows (@var{A}, @var{column})} checks the order ## using only the columns listed in @var{column}, in the given priority. A ## negative entry checks the corresponding column for descending order. ## ## @code{@var{TF} = issortedrows (@var{A}, @var{direction})} checks for the ## order given by @var{direction}, which may be one of @qcode{'ascend'} ## (default), @qcode{'descend'}, @qcode{'monotonic'}, @qcode{'strictascend'}, ## @qcode{'strictdescend'}, or @qcode{'strictmonotonic'}. It may also be a ## cell array of @qcode{'ascend'}/@qcode{'descend'} strings, one per sort ## column. For the strict options a matrix qualifies only when its first ## sort column is strictly monotonic and free of @qcode{NaT}. ## ## @code{@var{TF} = issortedrows (@var{A}, @var{column}, @var{direction})} ## combines an explicit column list with a @var{direction}. ## ## @end deftypefn function TF = issortedrows (A, varargin) if (ndims (A) != 2) error ("datetime.issortedrows: A must be a 2-D datetime array."); endif ncol = size (A, 2); keywords = {'ascend', 'descend', 'monotonic', 'strictascend', ... 'strictdescend', 'strictmonotonic'}; column = []; direction = []; if (numel (varargin) >= 1) if (isnumeric (varargin{1})) column = varargin{1}; if (numel (varargin) > 2) error ("datetime.issortedrows: too many input arguments."); elseif (numel (varargin) == 2) direction = varargin{2}; endif else direction = varargin{1}; if (numel (varargin) > 1) error ("datetime.issortedrows: COLUMN must precede DIRECTION."); endif endif endif if (isempty (column)) column = 1:ncol; endif column = column(:).'; if (any (column == 0) || any (column != fix (column)) ... || any (abs (column) > ncol)) error ("datetime.issortedrows: COLUMN out of range."); endif colmag = abs (column); desc = column < 0; check = 'ascend'; if (! isempty (direction)) if (ischar (direction) && isrow (direction)) check = lower (direction); if (! any (strcmp (check, keywords))) error ("datetime.issortedrows: invalid DIRECTION '%s'.", direction); endif if (any (strcmp (check, {'ascend', 'strictascend'}))) desc = false (size (colmag)); elseif (any (strcmp (check, {'descend', 'strictdescend'}))) desc = true (size (colmag)); endif elseif (iscellstr (direction)) if (numel (direction) != numel (colmag)) error (strcat ("datetime.issortedrows: DIRECTION must have one", ... " entry per sort column.")); endif desc = false (size (colmag)); for j = 1:numel (direction) if (strcmpi (direction{j}, 'descend')) desc(j) = true; elseif (! strcmpi (direction{j}, 'ascend')) error ("datetime.issortedrows: invalid DIRECTION '%s'.", ... direction{j}); endif endfor else error ("datetime.issortedrows: invalid DIRECTION argument."); endif endif if (isempty (colmag)) TF = true; return; endif S = serial (A); if (any (strcmp (check, {'strictascend', 'strictdescend', ... 'strictmonotonic'}))) ## Strict: only the first sort column matters; it must be strictly ## monotonic and contain no NaT. p = S(:, colmag(1)); if (any (isnan (p))) TF = false; else d = diff (p); if (strcmp (check, 'strictascend')) TF = all (d > 0); elseif (strcmp (check, 'strictdescend')) TF = all (d < 0); else TF = all (d > 0) || all (d < 0); endif endif return; endif ## Non-strict: lexicographic check on the selected columns. NaT maps to ## +Inf (largest); each column is negated to fold in a descending key. K = S(:, colmag); K(isnan (K)) = Inf; Kd = K; for j = 1:numel (colmag) if (desc(j)) Kd(:, j) = -Kd(:, j); endif endfor if (strcmp (check, 'monotonic')) TF = rowsNonDecreasing (A, Kd) || rowsNonDecreasing (A, -Kd); else TF = rowsNonDecreasing (A, Kd); endif endfunction ## -*- texinfo -*- ## @deftypefn {datetime} {@var{TF} =} iscolumn (@var{T}) ## ## Return true if datetime array is a column vector. ## ## @code{@var{TF} = iscolumn (@var{T})} returns a logical scalar @var{TF}, ## which is @qcode{true}, if the datetime array @var{T} is a column ## vector, and @qcode{false} otherwise. A column vector is a 2-D array for ## which @code{size (@var{X})} returns @code{[@var{N}, 1]} with non-negative ## @var{N}. ## ## @end deftypefn function TF = iscolumn (this) TF = iscolumn (this.Year); endfunction ## -*- texinfo -*- ## @deftypefn {datetime} {@var{TF} =} isempty (@var{T}) ## ## Return true if datetime array is empty. ## ## @code{@var{TF} = isempty (@var{T})} returns a logical scalar @var{TF}, ## which is @qcode{true}, if the datetime array @var{T} is empty, and ## @qcode{false} otherwise. ## ## @end deftypefn function TF = isempty (this) TF = isempty (this.Year); endfunction ## -*- texinfo -*- ## @deftypefn {datetime} {@var{TF} =} isequal (@var{A}, @var{B}) ## @deftypefnx {datetime} {@var{TF} =} isequal (@var{A}, @var{B}, @dots{}) ## ## Test datetime arrays for equality. ## ## @code{@var{TF} = isequal (@var{A}, @var{B})} returns a logical scalar ## @var{TF}, which is @qcode{true} if the datetime arrays @var{A} and ## @var{B} are the same size and each pair of corresponding elements is the ## same point in time, and @qcode{false} otherwise. As with @qcode{NaN}, ## Not-A-Time (@qcode{NaT}) elements are never equal, so any @qcode{NaT} in ## either array makes the result @qcode{false}; use @code{isequaln} to treat ## @qcode{NaT} elements as equal. ## ## Additional arrays may be supplied, as in @code{isequal (@var{A}, @var{B}, ## @var{C}, @dots{})}, in which case @var{TF} is @qcode{true} only when all ## of the arrays are equal to one another. Any input argument that is not a ## datetime array, or a datetime array whose time zone is not compatible ## with the others (one zoned and one unzoned), makes the result ## @qcode{false} rather than raising an error. Zoned arrays are compared by ## their absolute instants, so equal instants in different time zones are ## equal. ## ## @end deftypefn function TF = isequal (varargin) if (nargin < 2) print_usage (); endif TF = do_isequal (varargin, false); endfunction ## -*- texinfo -*- ## @deftypefn {datetime} {@var{TF} =} isequaln (@var{A}, @var{B}) ## @deftypefnx {datetime} {@var{TF} =} isequaln (@var{A}, @var{B}, @dots{}) ## ## Test datetime arrays for equality, treating Not-A-Time as equal. ## ## @code{@var{TF} = isequaln (@var{A}, @var{B})} is identical to ## @code{isequal (@var{A}, @var{B})} except that Not-A-Time (@qcode{NaT}) ## elements are treated as equal to one another, in the same way that ## @code{isequaln} treats @qcode{NaN}. It returns a logical scalar @var{TF}, ## which is @qcode{true} if the datetime arrays @var{A} and @var{B} are the ## same size and each pair of corresponding elements is either the same ## point in time or both @qcode{NaT}, and @qcode{false} otherwise. ## ## Additional arrays may be supplied, as in @code{isequaln (@var{A}, ## @var{B}, @var{C}, @dots{})}, in which case @var{TF} is @qcode{true} only ## when all of the arrays are equal to one another. Any input argument that ## is not a datetime array, or a datetime array whose time zone is not ## compatible with the others (one zoned and one unzoned), makes the result ## @qcode{false} rather than raising an error. Zoned arrays are compared by ## their absolute instants, so equal instants in different time zones are ## equal. ## ## @end deftypefn function TF = isequaln (varargin) if (nargin < 2) print_usage (); endif TF = do_isequal (varargin, true); endfunction ## -*- texinfo -*- ## @deftypefn {datetime} {@var{out} =} isfinite (@var{T}) ## ## Test for finite elements in datetime array. ## ## @code{@var{TF} = isfinite (@var{T})} returns a logical array @var{TF} of ## the same size as @var{T} containing @qcode{true} for each corresponding ## element of @var{T} that is finite and @qcode{false} otherwise. Finite ## elements in datetime arrays are those which are neither @qcode{Inf} nor ## @qcode{NaT}. ## ## @end deftypefn function TF = isfinite (this) TF = isfinite (this.Year); endfunction ## -*- texinfo -*- ## @deftypefn {datetime} {@var{out} =} isinf (@var{T}) ## ## Test for infinite elements in datetime array. ## ## @code{@var{TF} = isinf (@var{T})} returns a logical array @var{TF} of the ## same size as @var{T} containing @qcode{true} for each corresponding ## element of @var{T} that is infinite and @qcode{false} otherwise. ## ## @end deftypefn function TF = isinf (this) TF = isinf (this.Year); endfunction ## -*- texinfo -*- ## @deftypefn {datetime} {@var{TF} =} ismatrix (@var{T}) ## ## Return true if datetime array is a 2-D array. ## ## @code{@var{TF} = ismatrix (@var{T})} returns a logical scalar @var{TF}, ## which is @qcode{true}, if the datetime array @var{T} is a matrix, and ## @qcode{false} otherwise. A matrix is an array of any type where ## @code{ndims (@var{X}) == 2} and for which @code{size (@var{X})} returns ## @code{[@var{H}, @var{W}]} with non-negative @var{H} and @var{W}. ## ## @end deftypefn function TF = ismatrix (this) TF = ismatrix (this.Year); endfunction ## -*- texinfo -*- ## @deftypefn {datetime} {@var{TF} =} ismissing (@var{T}) ## @deftypefnx {datetime} {@var{TF} =} ismissing (@var{T}, @var{indicator}) ## ## Test for missing elements in datetime array. ## ## @var{TF} is a logical array of the same size as @var{T}. ## ## @end deftypefn function TF = ismissing (this, varargin) if (nargin > 2) error ("datetime.ismissing: too many input arguments."); endif if (! isempty (varargin)) indicator = varargin{1}; TF = false (size (this)); if (isvector (indicator)) if (isa (indicator, 'datetime')) for i = 1:numel (indicator.Year) is_eq = indicator.Year(i) == this.Year & ... indicator.Month(i) == this.Month & ... indicator.Day(i) == this.Day & ... indicator.Hour(i) == this.Hour & ... indicator.Minute(i) == this.Minute & ... indicator.Second(i) == this.Second; TF(is_eq) = true; endfor else error ("datetime.ismissing: INDICATOR must be a 'datetime' array."); endif else error ("datetime.ismissing: INDICATOR must be a vector."); endif else TF = isnan (this.Year); endif endfunction ## -*- texinfo -*- ## @deftypefn {datetime} {@var{TF} =} isnat (@var{T}) ## ## Test for Not-A-Time elements in datetime array. ## ## @code{@var{TF} = isnat (@var{T})} returns a logical array @var{TF} of the ## same size as @var{T} containing @qcode{true} for each corresponding ## element of @var{T} that is Not-A-Time (@qcode{NaT}) and @qcode{false} ## otherwise. @qcode{NaT} is the equivalent of @qcode{NaN} in numeric ## arrays. ## ## If @var{T} is not a datetime array, @code{isnat} returns an error. ## ## @end deftypefn function TF = isnat (this) TF = isnan (this.Year); endfunction ## -*- texinfo -*- ## @deftypefn {datetime} {@var{TF} =} isrow (@var{T}) ## ## Return true if datetime array is a row vector. ## ## @code{@var{TF} = isrow (@var{T})} returns a logical scalar @var{TF}, ## which is @qcode{true}, if the datetime array @var{T} is a row vector, ## and @qcode{false} otherwise. A row vector is a 2-D array for which ## @code{size (@var{X})} returns @code{[1, @var{N}]} with non-negative ## @var{N}. ## ## @end deftypefn function TF = isrow (this) TF = isrow (this.Year); endfunction ## -*- texinfo -*- ## @deftypefn {datetime} {@var{TF} =} isscalar (@var{T}) ## ## Return true if datetime array is a scalar. ## ## @code{@var{TF} = isscalar (@var{T})} returns a logical scalar @var{TF}, ## which is @qcode{true}, if the datetime array @var{T} is also a scalar, ## and @qcode{false} otherwise. A scalar is a single element object for ## which @code{size (@var{X})} returns @code{[1, 1]}. ## ## @end deftypefn function TF = isscalar (this) TF = isscalar (this.Year); endfunction ## -*- texinfo -*- ## @deftypefn {datetime} {@var{TF} =} isvector (@var{T}) ## ## Return true if datetime array is a vector. ## ## @code{@var{TF} = isvector (@var{T})} returns a logical scalar @var{TF}, ## which is @qcode{true} if the datetime array @var{T} is a vector and ## @qcode{false} otherwise. A vector is a 2-D array for which one of the ## dimensions is equal to 1 (either @math{1*N} or @math{N*1}). By ## definition, a scalar is also a vector. ## ## @end deftypefn function TF = isvector (this) TF = isvector (this.Year); endfunction endmethods ################################################################################ ## ** Sort, Filter, and Set Operations ** ## ################################################################################ ## Available Methods ## ## ## ## 'sort' 'sortrows' 'unique' 'interp1' ## ## 'intersect' 'setdiff' 'setxor' 'union' ## ## 'min' 'max' ## ## ## ################################################################################ methods (Access = public) ## -*- texinfo -*- ## @deftypefn {datetime} {@var{B} =} sort (@var{A}) ## @deftypefnx {datetime} {@var{B} =} sort (@var{A}, @var{dim}) ## @deftypefnx {datetime} {@var{B} =} sort (@var{A}, @var{direction}) ## @deftypefnx {datetime} {@var{B} =} sort (@var{A}, @var{dim}, @var{direction}) ## @deftypefnx {datetime} {@var{B} =} sort (@dots{}, @qcode{'MissingPlacement'}, @var{mp}) ## @deftypefnx {datetime} {[@var{B}, @var{I}] =} sort (@dots{}) ## ## Sort a datetime array. ## ## @code{@var{B} = sort (@var{A})} returns the elements of the datetime array ## @var{A} sorted in ascending order along its first non-singleton ## dimension. For a matrix, each column is sorted independently. Sorting is ## stable: elements that compare as equal keep their original relative order. ## ## @code{@var{B} = sort (@var{A}, @var{dim})} sorts along dimension ## @var{dim}. ## ## @code{@var{B} = sort (@var{A}, @var{direction})} sorts in the order given ## by @var{direction}, which is either @qcode{'ascend'} (default) or ## @qcode{'descend'}. ## ## @code{@var{B} = sort (@dots{}, @qcode{'MissingPlacement'}, @var{mp})} ## controls where Not-A-Time (@qcode{NaT}) elements are placed. @var{mp} may ## be @qcode{'auto'} (default; @qcode{NaT} last for ascending order and first ## for descending order), @qcode{'first'}, or @qcode{'last'}. ## ## @code{[@var{B}, @var{I}] = sort (@dots{})} also returns an index array ## @var{I} of the same size as @var{A} such that @var{B} is @var{A} indexed ## by @var{I} along the operating dimension. ## ## @end deftypefn function [B, I] = sort (A, varargin) dim = []; direction = 'ascend'; placement = 'auto'; i = 1; while (i <= numel (varargin)) arg = varargin{i}; if (ischar (arg) && isrow (arg) && strcmpi (arg, 'MissingPlacement')) if (i == numel (varargin)) error ("datetime.sort: 'MissingPlacement' requires a value."); endif placement = lower (varargin{i+1}); if (! any (strcmp (placement, {'auto', 'first', 'last'}))) error ("datetime.sort: invalid 'MissingPlacement' value."); endif i += 2; elseif (isnumeric (arg)) if (! isscalar (arg) || arg < 1 || arg != fix (arg)) error ("datetime.sort: DIM must be a positive integer."); endif dim = arg; i += 1; elseif (ischar (arg) && isrow (arg)) if (strcmpi (arg, 'ascend')) direction = 'ascend'; elseif (strcmpi (arg, 'descend')) direction = 'descend'; else error ("datetime.sort: invalid DIRECTION '%s'.", arg); endif i += 1; else error ("datetime.sort: invalid input argument."); endif endwhile if (isempty (dim)) dim = find (size (A) != 1, 1); if (isempty (dim)) dim = 1; endif endif ## Sorting along a singleton or higher dimension is a no-op. if (dim > 2 || size (A, dim) < 2) B = A; I = ones (size (A)); return; endif descend = strcmp (direction, 'descend'); ## Resolve 'auto': NaT goes last for ascending, first for descending. if (strcmp (placement, 'auto')) if (descend) placement = 'first'; else placement = 'last'; endif endif ## Work on the numeric serial (NaT -> NaN); sort along columns, so ## transpose for a row-wise sort and transpose the result back. S = serial (A); Y = A.Year; MO = A.Month; D = A.Day; H = A.Hour; MI = A.Minute; SE = A.Second; if (dim == 2) S = S.'; Y = Y.'; MO = MO.'; D = D.'; H = H.'; MI = MI.'; SE = SE.'; endif [nr, nc] = size (S); idx = zeros (nr, nc); for j = 1:nc col = S(:, j); nat = isnan (col); finidx = find (! nat); natidx = find (nat); ## Stable order: sort by value, breaking ties by original position. if (descend) [~, ord] = sortrows ([-col(finidx), finidx]); else [~, ord] = sortrows ([col(finidx), finidx]); endif finsorted = finidx(ord); if (strcmp (placement, 'first')) idx(:, j) = [natidx; finsorted]; else idx(:, j) = [finsorted; natidx]; endif endfor ## Reorder the component arrays column-wise using linear indexing. lin = idx + repmat ((0:nc-1) .* nr, nr, 1); Y = Y(lin); MO = MO(lin); D = D(lin); H = H(lin); MI = MI(lin); SE = SE(lin); I = idx; if (dim == 2) Y = Y.'; MO = MO.'; D = D.'; H = H.'; MI = MI.'; SE = SE.'; I = idx.'; endif B = A; B.Year = Y; B.Month = MO; B.Day = D; B.Hour = H; B.Minute = MI; B.Second = SE; endfunction ## -*- texinfo -*- ## @deftypefn {datetime} {@var{B} =} sortrows (@var{A}) ## @deftypefnx {datetime} {@var{B} =} sortrows (@var{A}, @var{column}) ## @deftypefnx {datetime} {@var{B} =} sortrows (@var{A}, @var{direction}) ## @deftypefnx {datetime} {@var{B} =} sortrows (@var{A}, @var{column}, @var{direction}) ## @deftypefnx {datetime} {@var{B} =} sortrows (@dots{}, @qcode{'MissingPlacement'}, @var{mp}) ## @deftypefnx {datetime} {[@var{B}, @var{index}] =} sortrows (@dots{}) ## ## Sort the rows of a datetime array. ## ## @code{@var{B} = sortrows (@var{A})} sorts the rows of the 2-D datetime ## array @var{A} in ascending order. Rows are ordered lexicographically: by ## the first column, ties broken by the second column, and so on. The sort ## is stable, so rows that compare as equal keep their original order. ## ## @code{@var{B} = sortrows (@var{A}, @var{column})} sorts using only the ## columns listed in @var{column}, in the given priority. A negative entry ## sorts the corresponding column in descending order. Columns not listed ## are not used as sort keys. ## ## @code{@var{B} = sortrows (@var{A}, @var{direction})} sorts every column in ## the given @var{direction}, either @qcode{'ascend'} (default) or ## @qcode{'descend'}. @var{direction} may also be a cell array of strings ## with one such value per sort column. ## ## @code{@var{B} = sortrows (@var{A}, @var{column}, @var{direction})} combines ## an explicit column list with a per-column @var{direction}. ## ## @code{@var{B} = sortrows (@dots{}, @qcode{'MissingPlacement'}, @var{mp})} ## controls where Not-A-Time (@qcode{NaT}) elements are placed. @var{mp} may ## be @qcode{'auto'} (default; @qcode{NaT} sorts as the largest value, i.e.@: ## last for ascending and first for descending columns), @qcode{'first'}, or ## @qcode{'last'}. ## ## @code{[@var{B}, @var{index}] = sortrows (@dots{})} also returns a column ## index vector @var{index} that maps the rows of @var{A} to @var{B}, such ## that @code{@var{B} = @var{A}(@var{index}, :)}. ## ## @end deftypefn function [B, index] = sortrows (A, varargin) if (ndims (A) != 2) error ("datetime.sortrows: A must be a 2-D datetime array."); endif ncol = size (A, 2); ## Split the 'MissingPlacement' (and ignored 'ComparisonMethod') name-value ## pairs off from the positional COLUMN/DIRECTION arguments. placement = 'auto'; pos = {}; k = 1; while (k <= numel (varargin)) a = varargin{k}; if (ischar (a) && isrow (a) && strcmpi (a, 'MissingPlacement')) if (k == numel (varargin)) error ("datetime.sortrows: 'MissingPlacement' requires a value."); endif placement = lower (varargin{k+1}); if (! any (strcmp (placement, {'auto', 'first', 'last'}))) error ("datetime.sortrows: invalid 'MissingPlacement' value."); endif k += 2; elseif (ischar (a) && isrow (a) && strcmpi (a, 'ComparisonMethod')) k += 2; else pos{end+1} = a; k += 1; endif endwhile if (numel (pos) > 2) error ("datetime.sortrows: too many input arguments."); endif ## Resolve the column selection and the per-column sort direction. column = []; direction = []; if (numel (pos) >= 1) if (isnumeric (pos{1})) column = pos{1}; if (numel (pos) == 2) direction = pos{2}; endif else direction = pos{1}; if (numel (pos) == 2) error ("datetime.sortrows: COLUMN must precede DIRECTION."); endif endif endif if (isempty (column)) column = 1:ncol; endif column = column(:).'; if (any (column == 0) || any (column != fix (column)) ... || any (abs (column) > ncol)) error ("datetime.sortrows: COLUMN out of range."); endif colmag = abs (column); desc = column < 0; if (! isempty (direction)) if (ischar (direction) && isrow (direction)) if (strcmpi (direction, 'descend')) desc = true (size (colmag)); elseif (strcmpi (direction, 'ascend')) desc = false (size (colmag)); else error ("datetime.sortrows: invalid DIRECTION '%s'.", direction); endif elseif (iscellstr (direction)) if (numel (direction) != numel (colmag)) error (strcat ("datetime.sortrows: DIRECTION must have one", ... " entry per sort column.")); endif desc = false (size (colmag)); for j = 1:numel (direction) if (strcmpi (direction{j}, 'descend')) desc(j) = true; elseif (! strcmpi (direction{j}, 'ascend')) error ("datetime.sortrows: invalid DIRECTION '%s'.", ... direction{j}); endif endfor else error ("datetime.sortrows: invalid DIRECTION argument."); endif endif ## Build the numeric key matrix from each selected column's instant. NaT ## maps to a signed-Inf sentinel that places it per MissingPlacement in the ## column's own direction ('auto' treats NaT as the largest value). S = serial (A); N = size (S, 1); K = S(:, colmag); for j = 1:numel (colmag) if (strcmp (placement, 'auto')) sentinel = Inf; elseif (strcmp (placement, 'last')) if (desc(j)) sentinel = -Inf; else sentinel = Inf; endif else if (desc(j)) sentinel = Inf; else sentinel = -Inf; endif endif col = K(:, j); col(isnan (col)) = sentinel; K(:, j) = col; endfor ## Append the original row index as a final ascending tie-break so equal ## rows keep their order, then defer to the built-in lexicographic sort. nkey = numel (colmag); spec = (1:nkey) .* (1 - 2 * desc); [~, index] = sortrows ([K, (1:N)'], [spec, nkey + 1]); index = index(:); B = subset (A, index, ':'); endfunction ## -*- texinfo -*- ## @deftypefn {datetime} {@var{M} =} min (@var{A}) ## @deftypefnx {datetime} {@var{M} =} min (@var{A}, @var{B}) ## @deftypefnx {datetime} {@var{M} =} min (@var{A}, [], @var{dim}) ## @deftypefnx {datetime} {@var{M} =} min (@dots{}, @var{nanflag}) ## @deftypefnx {datetime} {@var{M} =} min (@var{A}, [], @qcode{'all'}) ## @deftypefnx {datetime} {[@var{M}, @var{I}] =} min (@dots{}) ## ## Minimum of a datetime array. ## ## @code{@var{M} = min (@var{A})} returns the smallest element of the ## datetime array @var{A} along its first non-singleton dimension. For a ## matrix, @var{M} is a row vector with the minimum of each column. ## Not-A-Time (@qcode{NaT}) elements are omitted; a reduction over ## @qcode{NaT} elements only yields @qcode{NaT}. ## ## @code{@var{M} = min (@var{A}, @var{B})} returns an array the same size as ## @var{A} and @var{B} (after broadcasting) holding the element-wise minimum ## of the two datetime arrays. ## ## @code{@var{M} = min (@var{A}, [], @var{dim})} operates along dimension ## @var{dim}. The empty second argument distinguishes this from the ## element-wise form. ## ## @code{@var{M} = min (@dots{}, @var{nanflag})} sets the treatment of ## @qcode{NaT}: @qcode{'omitnan'} (default) ignores @qcode{NaT}, while ## @qcode{'includenan'} returns @qcode{NaT} whenever a @qcode{NaT} takes ## part in the comparison. ## ## @code{@var{M} = min (@var{A}, [], @qcode{'all'})} returns the smallest ## element of the whole array. ## ## @code{[@var{M}, @var{I}] = min (@dots{})} also returns the indices of the ## minima. A second output is not available for the element-wise form. ## ## @end deftypefn function [M, I] = min (A, varargin) [M, I] = minmaxImpl (A, varargin, false, nargout); endfunction ## -*- texinfo -*- ## @deftypefn {datetime} {@var{M} =} max (@var{A}) ## @deftypefnx {datetime} {@var{M} =} max (@var{A}, @var{B}) ## @deftypefnx {datetime} {@var{M} =} max (@var{A}, [], @var{dim}) ## @deftypefnx {datetime} {@var{M} =} max (@dots{}, @var{nanflag}) ## @deftypefnx {datetime} {@var{M} =} max (@var{A}, [], @qcode{'all'}) ## @deftypefnx {datetime} {[@var{M}, @var{I}] =} max (@dots{}) ## ## Maximum of a datetime array. ## ## @code{@var{M} = max (@var{A})} returns the largest element of the datetime ## array @var{A} along its first non-singleton dimension. For a matrix, ## @var{M} is a row vector with the maximum of each column. Not-A-Time ## (@qcode{NaT}) elements are omitted; a reduction over @qcode{NaT} elements ## only yields @qcode{NaT}. ## ## @code{@var{M} = max (@var{A}, @var{B})} returns an array the same size as ## @var{A} and @var{B} (after broadcasting) holding the element-wise maximum ## of the two datetime arrays. ## ## @code{@var{M} = max (@var{A}, [], @var{dim})} operates along dimension ## @var{dim}. The empty second argument distinguishes this from the ## element-wise form. ## ## @code{@var{M} = max (@dots{}, @var{nanflag})} sets the treatment of ## @qcode{NaT}: @qcode{'omitnan'} (default) ignores @qcode{NaT}, while ## @qcode{'includenan'} returns @qcode{NaT} whenever a @qcode{NaT} takes ## part in the comparison. ## ## @code{@var{M} = max (@var{A}, [], @qcode{'all'})} returns the largest ## element of the whole array. ## ## @code{[@var{M}, @var{I}] = max (@dots{})} also returns the indices of the ## maxima. A second output is not available for the element-wise form. ## ## @end deftypefn function [M, I] = max (A, varargin) [M, I] = minmaxImpl (A, varargin, true, nargout); endfunction ## -*- texinfo -*- ## @deftypefn {datetime} {@var{B} =} unique (@var{A}) ## @deftypefnx {datetime} {@var{B} =} unique (@var{A}, @var{setOrder}) ## @deftypefnx {datetime} {@var{B} =} unique (@var{A}, @var{occurrence}) ## @deftypefnx {datetime} {@var{B} =} unique (@var{A}, @var{setOrder}, @var{occurrence}) ## @deftypefnx {datetime} {@var{B} =} unique (@var{A}, @var{occurrence}, @var{setOrder}) ## @deftypefnx {datetime} {@var{B} =} unique (@var{A}, @dots{}, @qcode{'rows'}) ## @deftypefnx {datetime} {[@var{B}, @var{ixA}, @var{ixB}] =} unique (@dots{}) ## ## Unique values in a datetime array. ## ## @code{@var{B} = unique (@var{A})} returns the unique values of the ## datetime array @var{A} in sorted order. ## ## @code{@var{B} = unique (@var{A}, @var{setOrder})} returns the unique ## values of the datetime array @var{A} in an order as specified by ## @var{setOrder}, which can be either of the following values: ## ## @itemize ## @item @qcode{'sorted'} (default) returns the unique values sorted in ## ascending order. ## @item @qcode{'stable'} returns the unique values according to their order ## of occurrence. ## @end itemize ## ## @code{@var{B} = unique (@var{A}, @var{occurrence})} returns the unique ## values of the datetime array @var{tblA} according to their order of ## occurrence. @var{occurrence} can be either of the following values: ## ## @itemize ## @item @qcode{'first'} (default) returns the first occurrence of each ## unique value, i.e. the lowest possible indices are returned. ## @item @qcode{'last'} returns the last occurrence of each unique value, ## i.e. the highest possible indices are returned. ## @end itemize ## ## You can specify @var{setOrder} and @var{occurrence} arguments together. ## ## @code{@var{B} = unique (@var{A}, @dots{}, @qcode{'rows'})} returns the ## unique rows of @var{A} by treating each row as a single entity. The ## @qcode{'rows'} option can be used alone or in any combination with the ## @var{setOrder} and @var{occurrence} arguments. @qcode{'rows'} can be ## placed at any position in the function's argument list after the input ## array @var{A}. However, this syntax is only valid for 2-dimensional ## datetime arrays. ## ## @code{[@var{tblB}, @var{ixA}, @var{ixB}] = unique (@dots{})} also returns ## index vectors @var{ixA} and @var{ixB} using any of the previous syntaxes. ## @var{ixA} and @var{ixB} map the arrays @var{A} and @var{B} to one another ## such that @qcode{@var{B} = @var{A}(@var{ixA})} and ## @qcode{@var{A} = @var{B}(@var{ixB})}. When the @qcode{'rows'} optional ## argument is specified, then @qcode{@var{B} = @var{A}(@var{ixA},:)} and ## @qcode{@var{tblA} = @var{tblB}(@var{ixB},:)}. ## ## @end deftypefn function [B, ixA, ixB] = unique (A, varargin) ## 'legacy' option is not supported if (any (strcmp ("legacy", varargin))) error ("datetime.unique: 'legacy' option is not supported."); endif ## Handle each property array separately [~, ~, Yidx] = __unique__ (A.Year, varargin{:}); [~, ~, MOidx] = __unique__ (A.Month, varargin{:}); [~, ~, Didx] = __unique__ (A.Day, varargin{:}); [~, ~, Hidx] = __unique__ (A.Hour, varargin{:}); [~, ~, MIidx] = __unique__ (A.Minute, varargin{:}); [~, ~, Sidx] = __unique__ (A.Second, varargin{:}); DT = [Yidx, MOidx, Didx, Hidx, MIidx, Sidx]; ## Use indices to find unique datetime values if (any (strcmp ('rows', varargin))) [~, ixA, ixB] = __unique__ (DT, varargin{:}); if (any (strcmp ('last', varargin))) [~, ixA, ~] = __unique__ (ixB, 'last'); endif B = subset (A, ixA, ':'); else [~, ixA, ixB] = __unique__ (DT, 'rows', varargin{:}); if (any (strcmp ('last', varargin))) [~, ixA, ~] = __unique__ (ixB, 'last'); endif B = subset (A, ixA); ## Match MATLAB: a non-row input yields a column (so an empty 0-by-0 ## input returns a 0-by-1 result rather than 0-by-0). if (isempty (B) && ! isrow (A)) B = reshape (B, numel (B), 1); endif endif endfunction ## -*- texinfo -*- ## @deftypefn {datetime} {@var{C} =} intersect (@var{A}, @var{B}) ## @deftypefnx {datetime} {@var{C} =} intersect (@var{A}, @var{B}, @qcode{'rows'}) ## @deftypefnx {datetime} {@var{C} =} intersect (@dots{}, @var{order}) ## @deftypefnx {datetime} {[@var{C}, @var{ixA}, @var{ixB}] =} intersect (@dots{}) ## ## Set intersection of two datetime arrays. ## ## @code{@var{C} = intersect (@var{A}, @var{B})} returns the unique datetime ## values common to both @var{A} and @var{B}. Either input may instead be a ## date/time character vector, string array, or cell array of character ## vectors, which is promoted to a datetime array before the operation. ## Membership is decided on the absolute instant, so two zoned inputs may be ## in different time zones; the result carries the time zone and display ## format of @var{A}. Not-A-Time (@qcode{NaT}) elements are treated like ## @qcode{NaN} and never match. @var{C} is a row vector when both @var{A} ## and @var{B} are row vectors and a column vector otherwise. ## ## @code{@var{C} = intersect (@var{A}, @var{B}, @qcode{'rows'})} treats each ## row of the datetime matrices @var{A} and @var{B}, which must have the same ## number of columns, as a single element and returns their common rows. ## ## @code{@dots{} = intersect (@dots{}, @var{order})} returns the values in ## @qcode{'sorted'} order (the default) or in @qcode{'stable'} order, i.e.@: ## the order in which they appear in @var{A}. ## ## @code{[@var{C}, @var{ixA}, @var{ixB}] = intersect (@dots{})} also returns ## index vectors @var{ixA} and @var{ixB} such that @code{@var{C} = ## @var{A}(@var{ixA})} and @code{@var{C} = @var{B}(@var{ixB})}, or the ## corresponding row selections when @qcode{'rows'} is used. ## ## @end deftypefn function [C, ixA, ixB] = intersect (A, B, varargin) if (any (strcmpi ('legacy', varargin))) error ("datetime.intersect: 'legacy' option is not supported."); endif A = dtSetPromote (A, B, 'intersect'); B = dtSetPromote (B, A, 'intersect'); [A, B] = prepSetOp (A, B, 'intersect'); SA = serial (A); SB = serial (B); if (any (strcmpi ('rows', varargin))) [~, ixA, ixB] = intersect (SA, SB, varargin{:}); C = subset (A, ixA, ':'); else [~, ixA, ixB] = intersect (SA(:), SB(:), varargin{:}); C = subset (A, ixA); C = reshapeSetResult (C, isrow (A) && isrow (B)); endif endfunction ## -*- texinfo -*- ## @deftypefn {datetime} {@var{C} =} union (@var{A}, @var{B}) ## @deftypefnx {datetime} {@var{C} =} union (@var{A}, @var{B}, @qcode{'rows'}) ## @deftypefnx {datetime} {@var{C} =} union (@dots{}, @var{order}) ## @deftypefnx {datetime} {[@var{C}, @var{ixA}, @var{ixB}] =} union (@dots{}) ## ## Set union of two datetime arrays. ## ## @code{@var{C} = union (@var{A}, @var{B})} returns the unique datetime ## values present in either @var{A} or @var{B}. Either input may instead be ## a date/time character vector, string array, or cell array of character ## vectors, which is promoted to a datetime array before the operation. ## Membership is decided on the absolute instant, so two zoned inputs may be ## in different time zones; the result carries the time zone and display ## format of @var{A}. Distinct @qcode{NaT} elements are all retained. ## @var{C} is a row vector when both @var{A} and @var{B} are row vectors and ## a column vector otherwise. ## ## @code{@var{C} = union (@var{A}, @var{B}, @qcode{'rows'})} treats each row ## of the datetime matrices @var{A} and @var{B}, which must have the same ## number of columns, as a single element and returns their combined unique ## rows. ## ## @code{@dots{} = union (@dots{}, @var{order})} returns the values in ## @qcode{'sorted'} order (the default) or in @qcode{'stable'} order. ## ## @code{[@var{C}, @var{ixA}, @var{ixB}] = union (@dots{})} also returns ## index vectors @var{ixA} and @var{ixB} such that @var{C} is the combination ## of @code{@var{A}(@var{ixA})} and @code{@var{B}(@var{ixB})}, or the ## corresponding row selections when @qcode{'rows'} is used. ## ## @end deftypefn function [C, ixA, ixB] = union (A, B, varargin) if (any (strcmpi ('legacy', varargin))) error ("datetime.union: 'legacy' option is not supported."); endif A = dtSetPromote (A, B, 'union'); B = dtSetPromote (B, A, 'union'); [A, B] = prepSetOp (A, B, 'union'); SA = serial (A); SB = serial (B); stable = any (strcmpi ('stable', varargin)); if (any (strcmpi ('rows', varargin))) [~, ixA, ixB] = union (SA, SB, varargin{:}); C = combineSets (A, B, ixA, ixB, SA, SB, true, stable); else [~, ixA, ixB] = union (SA(:), SB(:), varargin{:}); C = combineSets (A, B, ixA, ixB, SA, SB, false, stable); endif endfunction ## -*- texinfo -*- ## @deftypefn {datetime} {@var{C} =} setdiff (@var{A}, @var{B}) ## @deftypefnx {datetime} {@var{C} =} setdiff (@var{A}, @var{B}, @qcode{'rows'}) ## @deftypefnx {datetime} {@var{C} =} setdiff (@dots{}, @var{order}) ## @deftypefnx {datetime} {[@var{C}, @var{ixA}] =} setdiff (@dots{}) ## ## Set difference of two datetime arrays. ## ## @code{@var{C} = setdiff (@var{A}, @var{B})} returns the unique datetime ## values in @var{A} that are not in @var{B}. Either input may instead be a ## date/time character vector, string array, or cell array of character ## vectors, which is promoted to a datetime array before the operation. ## Membership is decided on the absolute instant, so two zoned inputs may be ## in different time zones; the result carries the time zone and display ## format of @var{A}. @qcode{NaT} elements in @var{A} are all retained, as ## they never match an element of @var{B}. @var{C} is a row vector when both ## @var{A} and @var{B} are row vectors and a column vector otherwise. ## ## @code{@var{C} = setdiff (@var{A}, @var{B}, @qcode{'rows'})} treats each ## row of the datetime matrices @var{A} and @var{B}, which must have the same ## number of columns, as a single element and returns the rows of @var{A} ## that are not rows of @var{B}. ## ## @code{@dots{} = setdiff (@dots{}, @var{order})} returns the values in ## @qcode{'sorted'} order (the default) or in @qcode{'stable'} order. ## ## @code{[@var{C}, @var{ixA}] = setdiff (@dots{})} also returns an index ## vector @var{ixA} such that @code{@var{C} = @var{A}(@var{ixA})}, or ## @code{@var{C} = @var{A}(@var{ixA},:)} when @qcode{'rows'} is used. ## ## @end deftypefn function [C, ixA] = setdiff (A, B, varargin) if (any (strcmpi ('legacy', varargin))) error ("datetime.setdiff: 'legacy' option is not supported."); endif A = dtSetPromote (A, B, 'setdiff'); B = dtSetPromote (B, A, 'setdiff'); [A, B] = prepSetOp (A, B, 'setdiff'); SA = serial (A); SB = serial (B); if (any (strcmpi ('rows', varargin))) [~, ixA] = setdiff (SA, SB, varargin{:}); C = subset (A, ixA, ':'); else [~, ixA] = setdiff (SA(:), SB(:), varargin{:}); C = subset (A, ixA); C = reshapeSetResult (C, isrow (A) && isrow (B)); endif endfunction ## -*- texinfo -*- ## @deftypefn {datetime} {@var{C} =} setxor (@var{A}, @var{B}) ## @deftypefnx {datetime} {@var{C} =} setxor (@var{A}, @var{B}, @qcode{'rows'}) ## @deftypefnx {datetime} {@var{C} =} setxor (@dots{}, @var{order}) ## @deftypefnx {datetime} {[@var{C}, @var{ixA}, @var{ixB}] =} setxor (@dots{}) ## ## Set exclusive-or of two datetime arrays. ## ## @code{@var{C} = setxor (@var{A}, @var{B})} returns the unique datetime ## values that are in @var{A} or in @var{B} but not in both. Either input ## may instead be a date/time character vector, string array, or cell array ## of character vectors, which is promoted to a datetime array before the ## operation. Membership is decided on the absolute instant, so two zoned ## inputs may be in different time zones; the result carries the time zone ## and display format of @var{A}. Distinct @qcode{NaT} elements are all ## retained. @var{C} is a row vector when both @var{A} and @var{B} are row ## vectors and a column vector otherwise. ## ## @code{@var{C} = setxor (@var{A}, @var{B}, @qcode{'rows'})} treats each row ## of the datetime matrices @var{A} and @var{B}, which must have the same ## number of columns, as a single element and returns the rows that are in ## one input but not both. ## ## @code{@dots{} = setxor (@dots{}, @var{order})} returns the values in ## @qcode{'sorted'} order (the default) or in @qcode{'stable'} order. ## ## @code{[@var{C}, @var{ixA}, @var{ixB}] = setxor (@dots{})} also returns ## index vectors @var{ixA} and @var{ixB} such that @var{C} is the combination ## of @code{@var{A}(@var{ixA})} and @code{@var{B}(@var{ixB})}, or the ## corresponding row selections when @qcode{'rows'} is used. ## ## @end deftypefn function [C, ixA, ixB] = setxor (A, B, varargin) if (any (strcmpi ('legacy', varargin))) error ("datetime.setxor: 'legacy' option is not supported."); endif A = dtSetPromote (A, B, 'setxor'); B = dtSetPromote (B, A, 'setxor'); [A, B] = prepSetOp (A, B, 'setxor'); SA = serial (A); SB = serial (B); stable = any (strcmpi ('stable', varargin)); if (any (strcmpi ('rows', varargin))) [~, ixA, ixB] = setxor (SA, SB, varargin{:}); C = combineSets (A, B, ixA, ixB, SA, SB, true, stable); else [~, ixA, ixB] = setxor (SA(:), SB(:), varargin{:}); C = combineSets (A, B, ixA, ixB, SA, SB, false, stable); endif endfunction ## -*- texinfo -*- ## @deftypefn {datetime} {@var{TF} =} ismember (@var{A}, @var{B}) ## @deftypefnx {datetime} {@var{TF} =} ismember (@var{A}, @var{B}, @qcode{'rows'}) ## @deftypefnx {datetime} {[@var{TF}, @var{index}] =} ismember (@dots{}) ## ## Test for datetime elements in a set. ## ## @code{@var{TF} = ismember (@var{A}, @var{B})} returns a logical array ## @var{TF} of the same size as @var{A} containing @qcode{true} for each ## corresponding element of @var{A} that is in @var{B} and @qcode{false} ## otherwise. Either input may instead be a date/time character vector, ## string array, or cell array of character vectors, which is promoted to a ## datetime array before the test. Membership is decided on the absolute ## instant, so two zoned inputs may be in different time zones. Similarly to ## @qcode{NaN} values, Not-A-Time (@qcode{NaT}) elements are not equal with ## each other and always return @qcode{false}. ## ## @code{@var{TF} = ismember (@var{A}, @var{B}, @qcode{'rows'})} only ## applies to datetime matrices with the same number of columns, in which ## case the logical vector @var{TF} contains @qcode{true} for each row of ## @var{A} that is also a row in @var{B}. @var{TF} has the same number of ## rows as @var{A}. ## ## @code{[@var{TF}, @var{index}] = ismember (@var{A}, @var{B})} also returns ## an index array of the same size as @var{A} containing the lowest index in ## @var{B} for each element of @var{A} that is a member of @var{B} and 0 ## otherwise. If the @qcode{'rows'} optional argument is used, then the ## returning index is a column vector with the same rows as @var{A} and it ## contains the lowest index in @var{B} for each row of @var{A} that is a ## member of @var{B} and 0 otherwise. ## ## @end deftypefn function [TF, index] = ismember (A, B, varargin) do_rows = false; if (! isempty (varargin)) if (strcmpi (varargin{1}, 'rows')) do_rows = true; if (ndims (A) != 2 || ndims (A) != ndims (B)) error ("datetime.ismember: 'rows' applies only to 2-D matrices."); endif if (size (A, 2) != size (B, 2)) error (strcat ("datetime.ismember: 'rows' requires same", ... " number of columns.")); endif else error ("datetime.ismember: invalid optional argument."); endif endif A = dtSetPromote (A, B, 'ismember'); B = dtSetPromote (B, A, 'ismember'); [A, B] = prepSetOp (A, B, 'ismember'); SA = serial (A); SB = serial (B); if (do_rows) [TF, index] = __ismember__ (SA, SB, 'rows'); else [TF, index] = __ismember__ (SA, SB); endif endfunction endmethods methods (Hidden) function BI = interp1 (A, B, AI, varargin) error ("datetime.interp1: not implemented yet."); endfunction endmethods ################################################################################ ## ** Descriptive Statistics ** ## ################################################################################ ## Available Methods ## ## ## ## 'mean' 'median' 'mode' 'std' ## ## ## ################################################################################ methods (Access = public) ## -*- texinfo -*- ## @deftypefn {datetime} {@var{M} =} mean (@var{A}) ## @deftypefnx {datetime} {@var{M} =} mean (@var{A}, @var{dim}) ## @deftypefnx {datetime} {@var{M} =} mean (@var{A}, @qcode{'all'}) ## @deftypefnx {datetime} {@var{M} =} mean (@dots{}, @var{nanflag}) ## ## Mean of a datetime array. ## ## @code{@var{M} = mean (@var{A})} returns the mean of the datetime array ## @var{A} as a scalar datetime, computed as the average of the absolute ## instants along the first non-singleton dimension. A @var{dim} or ## @qcode{'all'} argument selects the dimension(s) to operate on. The result ## carries the @code{Format} and @code{TimeZone} of @var{A}. ## ## By default a Not-A-Time element makes the corresponding result ## @qcode{NaT}; pass @qcode{'omitnat'} (equivalently @qcode{'omitmissing'}) ## to ignore missing values, or @qcode{'includenat'} to keep the default. ## ## @end deftypefn function R = mean (A, varargin) args = dtStatFlags (varargin); R = fromReducedSerial (A, mean (serial (A), args{:})); endfunction ## -*- texinfo -*- ## @deftypefn {datetime} {@var{M} =} median (@var{A}) ## @deftypefnx {datetime} {@var{M} =} median (@var{A}, @var{dim}) ## @deftypefnx {datetime} {@var{M} =} median (@var{A}, @qcode{'all'}) ## @deftypefnx {datetime} {@var{M} =} median (@dots{}, @var{nanflag}) ## ## Median of a datetime array. ## ## @code{@var{M} = median (@var{A})} returns the median of the datetime array ## @var{A} as a datetime, computed on the absolute instants along the first ## non-singleton dimension (for an even number of elements the average of the ## two middle instants). A @var{dim} or @qcode{'all'} argument selects the ## dimension(s). The result carries the @code{Format} and @code{TimeZone} of ## @var{A}. Missing-value handling matches @code{mean}. ## ## @end deftypefn function R = median (A, varargin) args = dtStatFlags (varargin); R = fromReducedSerial (A, median (serial (A), args{:})); endfunction ## -*- texinfo -*- ## @deftypefn {datetime} {@var{M} =} mode (@var{A}) ## @deftypefnx {datetime} {@var{M} =} mode (@var{A}, @var{dim}) ## @deftypefnx {datetime} {@var{M} =} mode (@var{A}, @qcode{'all'}) ## @deftypefnx {datetime} {[@var{M}, @var{F}, @var{C}] =} mode (@dots{}) ## ## Most frequent value of a datetime array. ## ## @code{@var{M} = mode (@var{A})} returns the most frequently occurring ## datetime in @var{A} along the first non-singleton dimension; when several ## values are equally frequent the smallest is returned. A @var{dim} or ## @qcode{'all'} argument selects the dimension(s). Not-A-Time elements are ## ignored. The result carries the @code{Format} and @code{TimeZone} of ## @var{A}. ## ## @code{[@var{M}, @var{F}, @var{C}] = mode (@dots{})} also returns the ## frequency @var{F} of the modal value and a cell array @var{C} whose ## elements list all values that achieve that frequency. ## ## @end deftypefn function [R, F, C] = mode (A, varargin) if (nargout > 2) [ser, F, C] = mode (serial (A), varargin{:}); C = cellfun (@(x) fromReducedSerial (A, x), C, 'UniformOutput', false); elseif (nargout == 2) [ser, F] = mode (serial (A), varargin{:}); else ser = mode (serial (A), varargin{:}); endif R = fromReducedSerial (A, ser); endfunction ## -*- texinfo -*- ## @deftypefn {datetime} {@var{S} =} std (@var{A}) ## @deftypefnx {datetime} {@var{S} =} std (@var{A}, @var{w}) ## @deftypefnx {datetime} {@var{S} =} std (@var{A}, @var{w}, @var{dim}) ## @deftypefnx {datetime} {@var{S} =} std (@var{A}, @var{w}, @qcode{'all'}) ## @deftypefnx {datetime} {@var{S} =} std (@dots{}, @var{nanflag}) ## @deftypefnx {datetime} {[@var{S}, @var{M}] =} std (@dots{}) ## ## Standard deviation of a datetime array. ## ## @code{@var{S} = std (@var{A})} returns the standard deviation of the ## absolute instants of @var{A} as a @code{duration}. The weight @var{w} ## selects the normalisation (@code{0}, the default, divides by @math{N-1}; ## @code{1} divides by @math{N}), and a @var{dim} or @qcode{'all'} argument ## selects the dimension(s). Missing-value handling matches @code{mean}. ## ## @code{[@var{S}, @var{M}] = std (@dots{})} also returns the mean @var{M} as ## a datetime. ## ## @end deftypefn function [S, M] = std (A, varargin) args = dtStatFlags (varargin); if (nargout > 1) [sSec, mSec] = std (serial (A), args{:}); M = fromReducedSerial (A, mSec); else sSec = std (serial (A), args{:}); endif S = duration (0, 0, sSec); endfunction endmethods ################################################################################ ## ** Arithmetic Operations ** ## ################################################################################ ## Available Methods ## ## ## ## 'plus' 'minus' 'colon' ## ## ## ################################################################################ methods (Access = public) ## -*- texinfo -*- ## @deftypefn {datetime} {@var{R} =} colon (@var{A}, @var{B}) ## @deftypefnx {datetime} {@var{R} =} colon (@var{A}, @var{step}, @var{B}) ## ## Create a range of datetime values. ## ## @code{@var{R} = colon (@var{A}, @var{B})} is the equivalent of the syntax ## @code{@var{R} = @var{A}:@var{B}} and returns a row vector of datetime ## values starting at @var{A} and increasing in steps of one calendar day up ## to, and possibly including, @var{B}. ## ## @code{@var{R} = colon (@var{A}, @var{step}, @var{B})} is the equivalent of ## the syntax @code{@var{R} = @var{A}:@var{step}:@var{B}} and uses the ## specified @var{step} between consecutive elements. @var{step} may be: ## ## @itemize ## @item a @code{duration} or a numeric scalar (a number of fixed 24-hour ## days), in which case successive elements advance by a fixed amount of ## elapsed time; for a zoned range this is aware of daylight saving time. ## ## @item a @code{calendarDuration}, in which case successive elements advance ## in calendar units. Each element is computed as @code{@var{A} + k*@var{step}} ## for @code{k = 0, 1, 2, @dots{}}, so month and year steps clamp the day of ## month independently for every element (e.g.@: a one-month step from ## 31 January yields 31 January, 28 February, 31 March, @dots{}). ## @end itemize ## ## The default step of @code{@var{A}:@var{B}} is one calendar day ## (@code{caldays (1)}), which preserves the time of day across daylight ## saving time changes. A range whose @var{step} points away from @var{B} ## (for example an increasing step with @code{@var{A} > @var{B}}) is empty. ## @var{A} and @var{B} must be datetime scalars that are either both zoned or ## both unzoned, and must be finite. ## ## @end deftypefn function R = colon (varargin) if (nargin == 2) A = varargin{1}; step = caldays (1); B = varargin{2}; elseif (nargin == 3) A = varargin{1}; step = varargin{2}; B = varargin{3}; else print_usage (); endif if (! (isa (A, 'datetime') && isa (B, 'datetime'))) error ("datetime.colon: range endpoints must be datetime arrays."); endif if (! (isscalar (A) && isscalar (B))) error ("datetime.colon: range endpoints must be datetime scalars."); endif if (xor (isempty (A.TimeZone), isempty (B.TimeZone))) error (strcat ("datetime.colon: cannot create a range between a", ... " datetime with a time zone and one without a time", ... " zone.")); endif if (! (isfinite (A) && isfinite (B))) error (strcat ("datetime.colon: range endpoints must be finite", ... " (neither NaT nor Inf).")); endif if (! isscalar (step)) error ("datetime.colon: STEP must be a scalar."); endif if (isa (step, 'calendarDuration')) R = colonCalendar (A, step, B); elseif (isa (step, 'duration')) R = colonLinear (A, days (step) * 86400, B); elseif (isnumeric (step) && isreal (step)) R = colonLinear (A, double (step) * 86400, B); else error (strcat ("datetime.colon: STEP must be a duration,", ... " calendarDuration, or numeric scalar.")); endif endfunction ## -*- texinfo -*- ## @deftypefn {datetime} {@var{C} =} plus (@var{A}, @var{B}) ## ## Addition for datetime arrays. ## ## @code{@var{C} = plus (@var{A}, @var{B})} is the equivalent of the syntax ## @code{@var{C} = @var{A} + @var{B}} and supports the following operand ## combinations, in either order. ## ## @itemize ## @item @code{datetime + duration} returns a @code{datetime} array shifted ## later by a fixed number of 24-hour days. For a zoned array the shift is ## applied to the absolute instant, so it is aware of daylight saving time ## transitions. ## ## @item @code{datetime + calendarDuration} returns a @code{datetime} array ## shifted later in calendar units. Whole months (and years) are applied ## first, clamping the day of month to the last day of the target month when ## necessary (e.g.@: 31 January plus one month is 28 February), then whole ## calendar days, and finally the time-of-day component as an instant. ## ## @item @code{datetime + X}, where @var{X} is a numeric or logical array, ## treats the elements of @var{X} as a number of fixed 24-hour days. ## @end itemize ## ## Adding two datetime arrays is not defined and raises an error. @var{A} ## and @var{B} must be size compatible: they can be the same size, one can be ## scalar, or for every dimension their sizes must be equal or one of them ## must be 1. Not-A-Time and infinite elements propagate to the result. ## ## @end deftypefn function C = plus (A, B) ## Addition is symmetric for every supported operand type, so commute the ## operands to keep the datetime array on the left. if (! isa (A, 'datetime')) [A, B] = deal (B, A); endif if (isa (B, 'datetime')) ## datetime + datetime is not defined (MATLAB parity) error (strcat ("datetime.plus: addition is not defined between two", ... " datetime arrays.")); elseif (isa (B, 'duration')) ## datetime + duration -> datetime (fixed-length instant shift) C = addSeconds (A, days (B) * 86400); elseif (isa (B, 'calendarDuration')) ## datetime + calendarDuration -> datetime (calendar-aware shift) C = addCalendar (A, B, 1); elseif (islogical (B) || (isnumeric (B) && isfloat (B))) ## numeric/logical operand: a number of fixed 24-hour days C = addSeconds (A, double (B) * 86400); elseif (isinteger (B)) error (strcat ("datetime.plus: cannot add a '%s' array to a datetime", ... " array; convert it to double or a duration first."), ... class (B)); else error (strcat ("datetime.plus: cannot add a '%s' array to a datetime", ... " array."), class (B)); endif endfunction ## -*- texinfo -*- ## @deftypefn {datetime} {@var{C} =} minus (@var{A}, @var{B}) ## ## Subtraction for datetime arrays. ## ## @code{@var{C} = minus (@var{A}, @var{B})} is the equivalent of the syntax ## @code{@var{C} = @var{A} - @var{B}} and supports the following operand ## combinations. ## ## @itemize ## @item @code{datetime - datetime} returns a @code{duration} array holding ## the elapsed time between the corresponding elements. Both operands must ## either both have a time zone or both be unzoned; a zoned difference is ## computed from the absolute instants, so the two time zones may differ. ## ## @item @code{datetime - duration} returns a @code{datetime} array shifted ## earlier by a fixed number of 24-hour days. For a zoned array the shift ## is applied to the absolute instant, so it is aware of daylight saving ## time transitions. ## ## @item @code{datetime - calendarDuration} returns a @code{datetime} array ## shifted earlier in calendar units. Whole months (and years) are applied ## first, clamping the day of month to the last day of the target month when ## necessary (e.g.@: 31 March minus one month is 28 February), then whole ## calendar days, and finally the time-of-day component as an instant. ## ## @item @code{datetime - X}, where @var{X} is a numeric or logical array, ## treats the elements of @var{X} as a number of fixed 24-hour days. ## @end itemize ## ## @var{A} and @var{B} must be size compatible: they can be the same size, ## one can be scalar, or for every dimension their sizes must be equal or one ## of them must be 1. Not-A-Time and infinite elements propagate to the ## result. ## ## @end deftypefn function C = minus (A, B) ## Only 'datetime - ' is defined; a datetime array cannot be ## subtracted from a non-datetime left operand (MATLAB parity). if (! isa (A, 'datetime')) error (strcat ("datetime.minus: cannot subtract a datetime array", ... " from a '%s' array."), class (A)); endif if (isa (B, 'datetime')) ## datetime - datetime -> duration (elapsed time) if (xor (isempty (A.TimeZone), isempty (B.TimeZone))) error (strcat ("datetime.minus: cannot subtract a datetime array", ... " with a time zone from one without a time zone.")); endif C = duration (0, 0, serial (A) - serial (B)); elseif (isa (B, 'duration')) ## datetime - duration -> datetime (fixed-length instant shift) C = addSeconds (A, - days (B) * 86400); elseif (isa (B, 'calendarDuration')) ## datetime - calendarDuration -> datetime (calendar-aware shift) C = addCalendar (A, B, -1); elseif (islogical (B) || (isnumeric (B) && isfloat (B))) ## numeric/logical operand: a number of fixed 24-hour days C = addSeconds (A, - double (B) * 86400); elseif (isinteger (B)) error (strcat ("datetime.minus: cannot subtract a '%s' array from a", ... " datetime array; convert it to double or a duration", ... " first."), class (B)); else error (strcat ("datetime.minus: cannot subtract a '%s' array from a", ... " datetime array."), class (B)); endif endfunction ## -*- texinfo -*- ## @deftypefn {datetime} {@var{D} =} diff (@var{A}) ## @deftypefnx {datetime} {@var{D} =} diff (@var{A}, @var{N}) ## @deftypefnx {datetime} {@var{D} =} diff (@var{A}, @var{N}, @var{dim}) ## ## Differences between successive datetime elements. ## ## @code{@var{D} = diff (@var{A})} returns a @code{duration} array holding the ## elapsed time between successive elements of @var{A} along its first ## non-singleton dimension. The differences are computed from the absolute ## instants, so for a zoned array they are aware of daylight saving time ## transitions (a calendar day spanning a transition is 23 or 25 hours, not ## 24). Not-A-Time elements propagate as @qcode{NaN} durations. ## ## @code{@var{D} = diff (@var{A}, @var{N})} applies @code{diff} ## recursively @var{N} times, returning the @var{N}-th order difference. ## @var{N} must be a positive integer scalar. ## ## @code{@var{D} = diff (@var{A}, @var{N}, @var{dim})} operates along ## dimension @var{dim}. ## ## @end deftypefn function D = diff (A, varargin) n = 1; dim = []; if (numel (varargin) > 2) error ("datetime.diff: too many input arguments."); endif if (numel (varargin) >= 1) n = varargin{1}; if (! (isnumeric (n) && isscalar (n) && n > 0 && n == fix (n))) error (strcat ("datetime.diff: order N must be a positive integer", ... " scalar.")); endif endif if (numel (varargin) >= 2) dim = varargin{2}; if (! (isnumeric (dim) && isscalar (dim) && dim > 0 ... && dim == fix (dim))) error (strcat ("datetime.diff: DIM must be a positive integer", ... " scalar.")); endif endif S = serial (A); if (! isempty (dim) && dim > ndims (S)) ## Differencing along a trailing singleton dimension leaves at most one ## element there, so the result is empty along DIM (MATLAB parity). sz = size (S); sz(end+1:dim) = 1; sz(dim) = 0; DS = zeros (sz); elseif (isempty (dim)) DS = diff (S, n); else DS = diff (S, n, dim); endif D = duration (0, 0, DS); endfunction ## -*- texinfo -*- ## @deftypefn {datetime} {@var{D} =} caldiff (@var{A}) ## @deftypefnx {datetime} {@var{D} =} caldiff (@var{A}, @var{components}) ## @deftypefnx {datetime} {@var{D} =} caldiff (@var{A}, @var{components}, @var{dim}) ## ## Calendar differences between successive datetime elements. ## ## @code{@var{D} = caldiff (@var{A})} returns a @code{calendarDuration} array ## holding the calendar difference between successive elements of @var{A} ## along its first non-singleton dimension. Unlike @code{diff}, the result ## is expressed in whole calendar units (years, months, days) plus a leftover ## time, so it is aware of month lengths and, for a zoned array, of daylight ## saving time. Not-A-Time elements yield @qcode{NaN}. ## ## @code{@var{D} = caldiff (@var{A}, @var{components})} expresses each ## difference using only the requested calendar components. ## @var{components} is one of @qcode{'Years'}, @qcode{'Quarters'}, ## @qcode{'Months'}, @qcode{'Weeks'}, @qcode{'Days'}, or @qcode{'Time'}, or a ## cell array or string array containing several of them. The default is ## @qcode{@{'Years', 'Months', 'Days', 'Time'@}}. ## ## @code{@var{D} = caldiff (@var{A}, @var{components}, @var{dim})} operates ## along dimension @var{dim}. ## ## @end deftypefn function D = caldiff (A, varargin) comps = []; dim = []; for k = 1:numel (varargin) x = varargin{k}; if (ischar (x) || iscellstr (x) || isa (x, 'string')) comps = x; elseif (isnumeric (x) && isscalar (x)) dim = x; else error ("datetime.caldiff: invalid input argument."); endif endfor if (isempty (dim)) dim = find (size (A) != 1, 1); if (isempty (dim)) dim = 1; endif endif n = size (A, dim); idx = repmat ({':'}, 1, max (ndims (A), dim)); i1 = idx; i1{dim} = 1:n-1; i2 = idx; i2{dim} = 2:n; D = calDiff (subset (A, i1{:}), subset (A, i2{:}), comps, 'caldiff'); endfunction ## -*- texinfo -*- ## @deftypefn {datetime} {@var{D} =} between (@var{A}, @var{B}) ## @deftypefnx {datetime} {@var{D} =} between (@var{A}, @var{B}, @var{components}) ## ## Calendar difference between two datetime arrays. ## ## @code{@var{D} = between (@var{A}, @var{B})} returns a ## @code{calendarDuration} array holding the calendar difference from each ## element of @var{A} to the corresponding element of @var{B}. The result is ## signed (it is negative where @var{B} precedes @var{A}) and is expressed in ## whole calendar units plus a leftover time, taking month lengths and ## daylight saving time into account. @var{A} and @var{B} must be the same ## size or one of them must be scalar. Not-A-Time elements yield @qcode{NaN}. ## ## @code{@var{D} = between (@var{A}, @var{B}, @var{components})} expresses ## each difference using only the requested calendar components (see ## @code{caldiff}). The default is @qcode{@{'Years', 'Months', 'Days', ## 'Time'@}}. ## ## @end deftypefn function D = between (A, B, varargin) if (numel (varargin) > 1) error ("datetime.between: too many input arguments."); endif comps = []; if (numel (varargin) == 1) comps = varargin{1}; endif A = dtSetPromote (A, B, 'between'); B = dtSetPromote (B, A, 'between'); [A, B] = prepSetOp (A, B, 'between'); [A, B] = broadcastPair (A, B, 'between'); D = calDiff (A, B, comps, 'between'); endfunction ## -*- texinfo -*- ## @deftypefn {datetime} {@var{R} =} dateshift (@var{A}, @qcode{'start'}, @var{unit}) ## @deftypefnx {datetime} {@var{R} =} dateshift (@var{A}, @qcode{'end'}, @var{unit}) ## @deftypefnx {datetime} {@var{R} =} dateshift (@dots{}, @var{rule}) ## @deftypefnx {datetime} {@var{R} =} dateshift (@var{A}, @qcode{'dayofweek'}, @var{dow}) ## @deftypefnx {datetime} {@var{R} =} dateshift (@var{A}, @qcode{'dayofweek'}, @var{dow}, @var{rule}) ## ## Shift datetime values to calendar boundaries. ## ## @code{@var{R} = dateshift (@var{A}, @qcode{'start'}, @var{unit})} returns a ## datetime array in which each element of @var{A} is moved back to the start ## of the calendar @var{unit} that contains it, with the finer components set ## to zero. @var{unit} is @qcode{'second'}, @qcode{'minute'}, ## @qcode{'hour'}, @qcode{'day'}, @qcode{'week'}, @qcode{'month'}, ## @qcode{'quarter'}, or @qcode{'year'}. A week starts on Sunday. ## ## @code{@var{R} = dateshift (@var{A}, @qcode{'end'}, @var{unit})} moves each ## element to the end of its unit: the start of the next second, minute, ## hour, or day, and the last day (at midnight) of the week, month, quarter, ## or year. ## ## @code{@var{R} = dateshift (@dots{}, @var{rule})} first shifts each element ## by @var{rule} whole units. @var{rule} is @qcode{'current'} (the default), ## @qcode{'next'}, @qcode{'previous'}, @qcode{'nearest'}, or an integer ## number of units. ## ## @code{@var{R} = dateshift (@var{A}, @qcode{'dayofweek'}, @var{dow})} moves ## each element to the next date, on or after it, whose day of the week is ## @var{dow} (a number from 1 for Sunday to 7 for Saturday, or a day name), ## keeping the time of day. A trailing @var{rule} of @qcode{'previous'}, ## @qcode{'nearest'}, @qcode{'current'} (the day within the current week), or ## an integer occurrence selects a different date. ## ## Not-A-Time and infinite elements are returned unchanged. ## ## @end deftypefn function R = dateshift (this, op, varargin) ops = {'start', 'end', 'dayofweek'}; if (! (ischar (op) && isrow (op) && any (strcmpi (op, ops)))) error (strcat ("datetime.dateshift: second input must be 'start',", ... " 'end', or 'dayofweek'.")); endif op = lower (op); if (numel (varargin) < 1) error ("datetime.dateshift: not enough input arguments."); endif if (numel (varargin) > 2) error ("datetime.dateshift: too many input arguments."); endif Y = this.Year; M = this.Month; D = this.Day; h = this.Hour; mi = this.Minute; s = this.Second; keep = isnan (Y) | isinf (Y); if (any (keep(:))) Y(keep) = 2000; M(keep) = 1; D(keep) = 1; h(keep) = 0; mi(keep) = 0; s(keep) = 0; endif if (strcmp (op, 'dayofweek')) dow = varargin{1}; if (ischar (dow) && isrow (dow)) dow = dsDayName (dow); elseif (! (isnumeric (dow) && isscalar (dow))) dow = NaN; endif if (! (isscalar (dow) && dow >= 1 && dow <= 7 && dow == fix (dow))) error (strcat ("datetime.dateshift: day of week must be a number", ... " from 1 to 7 or a day name.")); endif kind = 'next'; n = 0; if (numel (varargin) == 2) [kind, n] = dsRule (varargin{2}); endif dowT = weekday (datenum (Y, M, D)); dNext = mod (dow - dowT, 7); dPrev = mod (dowT - dow, 7); switch (kind) case 'next' delta = dNext; case 'previous' delta = -dPrev; case 'current' delta = dow - dowT; case 'nearest' delta = dNext; useprev = dPrev < dNext; delta(useprev) = -dPrev(useprev); case 'int' if (n >= 1) delta = dNext + (n - 1) * 7; elseif (n <= -1) delta = -(dPrev + (- n - 1) * 7); else delta = dow - dowT; endif endswitch [Y, M, D] = dtAddDays (Y, M, D, delta); else unit = varargin{1}; units = {'second', 'minute', 'hour', 'day', 'week', 'month', ... 'quarter', 'year'}; if (! (ischar (unit) && isrow (unit) && any (strcmpi (unit, units)))) error (strcat ("datetime.dateshift: unit must be 'second',", ... " 'minute', 'hour', 'day', 'week', 'month',", ... " 'quarter', or 'year'.")); endif unit = lower (unit); n = 0; if (numel (varargin) == 2) [kind, nint] = dsRule (varargin{2}); switch (kind) case 'current' n = 0; case 'next' n = 1; case 'previous' n = -1; case 'int' n = nint; case 'nearest' [cY, cM, cD, ch, cm, cs] = dsStartComp (Y, M, D, h, mi, s, unit); sc = dsSerialOf (this, cY, cM, cD, ch, cm, cs); [nY, nM, nD, nh, nm, ns] = ... dsShiftUnits (cY, cM, cD, ch, cm, cs, unit, 1); sn = dsSerialOf (this, nY, nM, nD, nh, nm, ns); st = serial (this); st(keep) = sc(keep); n = double ((st - sc) >= (sn - st)); endswitch endif [Y, M, D, h, mi, s] = dsShiftUnits (Y, M, D, h, mi, s, unit, n); if (strcmp (op, 'start')) [Y, M, D, h, mi, s] = dsStartComp (Y, M, D, h, mi, s, unit); else [Y, M, D, h, mi, s] = dsEndComp (Y, M, D, h, mi, s, unit); endif endif R = this; R.Year = Y; R.Month = M; R.Day = D; R.Hour = h; R.Minute = mi; R.Second = s; R = normalize (R); if (any (keep(:))) R.Year(keep) = this.Year(keep); R.Month(keep) = this.Month(keep); R.Day(keep) = this.Day(keep); R.Hour(keep) = this.Hour(keep); R.Minute(keep) = this.Minute(keep); R.Second(keep) = this.Second(keep); endif endfunction endmethods ################################################################################ ## ** Relational Operations ** ## ################################################################################ ## Available Methods ## ## ## ## 'lt' 'le' 'gt' 'ge' ## ## 'eq' 'ne' ## ## ## ################################################################################ methods (Access = public) ## -*- texinfo -*- ## @deftypefn {datetime} {@var{TF} =} lt (@var{A}, @var{B}) ## ## Less-than comparison for datetime arrays. ## ## @code{@var{TF} = lt (@var{A}, @var{B})} is the equivalent of the syntax ## @code{@var{TF} = @var{A} < @var{B}} and returns a logical array set to ## @qcode{true} where the corresponding element of @var{A} is an earlier ## point in time than that of @var{B}, and @qcode{false} otherwise. ## ## Both operands must be datetime arrays and either both have a time zone or ## both be unzoned; zoned arrays are compared by their absolute instants, so ## the two time zones may differ. @var{A} and @var{B} must be size ## compatible. Not-A-Time compares as @qcode{false} against anything, just ## like @code{NaN}. ## ## @end deftypefn function TF = lt (A, B) TF = relcompare (A, B, 'lt'); endfunction ## -*- texinfo -*- ## @deftypefn {datetime} {@var{TF} =} le (@var{A}, @var{B}) ## ## Less-than-or-equal comparison for datetime arrays. ## ## @code{@var{TF} = le (@var{A}, @var{B})} is the equivalent of the syntax ## @code{@var{TF} = @var{A} <= @var{B}} and returns a logical array set to ## @qcode{true} where the corresponding element of @var{A} is an earlier or ## equal point in time to that of @var{B}, and @qcode{false} otherwise. ## ## Both operands must be datetime arrays and either both have a time zone or ## both be unzoned; zoned arrays are compared by their absolute instants, so ## the two time zones may differ. @var{A} and @var{B} must be size ## compatible. Not-A-Time compares as @qcode{false} against anything, just ## like @code{NaN}. ## ## @end deftypefn function TF = le (A, B) TF = relcompare (A, B, 'le'); endfunction ## -*- texinfo -*- ## @deftypefn {datetime} {@var{TF} =} gt (@var{A}, @var{B}) ## ## Greater-than comparison for datetime arrays. ## ## @code{@var{TF} = gt (@var{A}, @var{B})} is the equivalent of the syntax ## @code{@var{TF} = @var{A} > @var{B}} and returns a logical array set to ## @qcode{true} where the corresponding element of @var{A} is a later point ## in time than that of @var{B}, and @qcode{false} otherwise. ## ## Both operands must be datetime arrays and either both have a time zone or ## both be unzoned; zoned arrays are compared by their absolute instants, so ## the two time zones may differ. @var{A} and @var{B} must be size ## compatible. Not-A-Time compares as @qcode{false} against anything, just ## like @code{NaN}. ## ## @end deftypefn function TF = gt (A, B) TF = relcompare (A, B, 'gt'); endfunction ## -*- texinfo -*- ## @deftypefn {datetime} {@var{TF} =} ge (@var{A}, @var{B}) ## ## Greater-than-or-equal comparison for datetime arrays. ## ## @code{@var{TF} = ge (@var{A}, @var{B})} is the equivalent of the syntax ## @code{@var{TF} = @var{A} >= @var{B}} and returns a logical array set to ## @qcode{true} where the corresponding element of @var{A} is a later or ## equal point in time to that of @var{B}, and @qcode{false} otherwise. ## ## Both operands must be datetime arrays and either both have a time zone or ## both be unzoned; zoned arrays are compared by their absolute instants, so ## the two time zones may differ. @var{A} and @var{B} must be size ## compatible. Not-A-Time compares as @qcode{false} against anything, just ## like @code{NaN}. ## ## @end deftypefn function TF = ge (A, B) TF = relcompare (A, B, 'ge'); endfunction ## -*- texinfo -*- ## @deftypefn {datetime} {@var{TF} =} eq (@var{A}, @var{B}) ## ## Equality comparison for datetime arrays. ## ## @code{@var{TF} = eq (@var{A}, @var{B})} is the equivalent of the syntax ## @code{@var{TF} = @var{A} == @var{B}} and returns a logical array set to ## @qcode{true} where the corresponding elements of @var{A} and @var{B} are ## the same point in time, and @qcode{false} otherwise. ## ## Both operands must be datetime arrays and either both have a time zone or ## both be unzoned; zoned arrays are compared by their absolute instants, so ## the two time zones may differ. @var{A} and @var{B} must be size ## compatible. Not-A-Time is never equal to anything, including another ## Not-A-Time, just like @code{NaN}. ## ## @end deftypefn function TF = eq (A, B) TF = relcompare (A, B, 'eq'); endfunction ## -*- texinfo -*- ## @deftypefn {datetime} {@var{TF} =} ne (@var{A}, @var{B}) ## ## Inequality comparison for datetime arrays. ## ## @code{@var{TF} = ne (@var{A}, @var{B})} is the equivalent of the syntax ## @code{@var{TF} = @var{A} != @var{B}} and returns a logical array set to ## @qcode{true} where the corresponding elements of @var{A} and @var{B} are ## not the same point in time, and @qcode{false} otherwise. ## ## Both operands must be datetime arrays and either both have a time zone or ## both be unzoned; zoned arrays are compared by their absolute instants, so ## the two time zones may differ. @var{A} and @var{B} must be size ## compatible. Not-A-Time is never equal to anything, so it compares as ## @qcode{true} against everything, including another Not-A-Time, just like ## @code{NaN}. ## ## @end deftypefn function TF = ne (A, B) TF = relcompare (A, B, 'ne'); endfunction endmethods ################################################################################ ## ** Array Operations ** ## ################################################################################ ## Available Methods ## ## ## ## 'cat' 'horzcat' 'vertcat' 'repmat' ## ## 'repelem' 'repelems' 'reshape' 'circshift' ## ## 'permute' 'ipermute' 'transpose' 'ctranspose' ## ## ## ################################################################################ methods (Access = public) ## -*- texinfo -*- ## @deftypefn {datetime} {@var{C} =} cat (@var{dim}, @var{A}, @var{B}, @dots{}) ## ## Concatenate datetime arrays. ## ## @code{@var{C} = cat (@var{dim}, @var{A}, @var{B}, @dots{})} concatenates ## datetime arrays @var{A}, @var{B}, @dots{} along dimension @var{dim}. All ## input arrays must be datetime arrays and have the same size except along ## the operating dimension @var{dim}. ## ## @end deftypefn function out = cat (dim, varargin) args = varargin; f = @(x) isa (x, 'datetime'); if (! all (cellfun (f, args))) error ("datetime: invalid input to constructor."); endif out = args{1}; fieldArgs = cellfun (@(obj) obj.Year, args, 'UniformOutput', false); out.Year = cat (dim, fieldArgs{:}); fieldArgs = cellfun (@(obj) obj.Month, args, 'UniformOutput', false); out.Month = cat (dim, fieldArgs{:}); fieldArgs = cellfun (@(obj) obj.Day, args, 'UniformOutput', false); out.Day = cat (dim, fieldArgs{:}); fieldArgs = cellfun (@(obj) obj.Hour, args, 'UniformOutput', false); out.Hour = cat (dim, fieldArgs{:}); fieldArgs = cellfun (@(obj) obj.Minute, args, 'UniformOutput', false); out.Minute = cat (dim, fieldArgs{:}); fieldArgs = cellfun (@(obj) obj.Second, args, 'UniformOutput', false); out.Second = cat (dim, fieldArgs{:}); endfunction ## -*- texinfo -*- ## @deftypefn {datetime} {@var{C} =} horzcat (@var{A}, @var{B}, @dots{}) ## ## Horizontal concatenation of datetime arrays. ## ## @code{@var{C} = horzcat (@var{A}, @var{B}, @dots{}} is the equivalent of ## the syntax @code{@var{B} = [@var{A}, @var{B}, @dots{}]} and horizontally ## concatenates the datetime arrays @var{A}, @var{B}, @dots{}. All input ## arrays must be datetime arrays and have the same size except along the ## second dimension. ## ## @end deftypefn function out = horzcat (varargin) out = cat (2, varargin{:}); endfunction ## -*- texinfo -*- ## @deftypefn {datetime} {@var{C} =} vertcat (@var{A}, @var{B}, @dots{}) ## ## Vertical concatenation of datetime arrays. ## ## @code{@var{C} = vertcat (@var{A}, @var{B}, @dots{}} is the equivalent of ## the syntax @code{@var{B} = [@var{A}; @var{B}; @dots{}]} and vertically ## concatenates the datetime arrays @var{A}, @var{B}, @dots{}. All input ## arrays must be datetime arrays and have the same size except along the ## second dimension. ## ## @end deftypefn function out = vertcat (varargin) out = cat (1, varargin{:}); endfunction ## -*- texinfo -*- ## @deftypefn {datetime} {@var{B} =} repmat (@var{A}, @var{n}) ## @deftypefnx {datetime} {@var{B} =} repmat (@var{A}, @var{d1}, @dots{}, @var{dN}) ## @deftypefnx {datetime} {@var{B} =} repmat (@var{A}, @var{dimvec}) ## ## Repeat copies of a datetime array. ## ## @code{@var{B} = repmat (@var{A}, @var{n})} returns a datetime array ## @var{B} containing @var{n} copies of the input datetime array @var{A} ## along every dimension of @var{A}. ## ## @code{@var{B} = repmat (@var{A}, @var{d1}, @dots{}, @var{dN})} returns an ## array @var{B} containing copies of @var{A} along the dimensions specified ## by the list of scalar integer values @var{d1}, @dots{}, @var{dN}, which ## specify how many copies of @var{A} are made in each dimension. ## ## @code{@var{B} = repmat (@var{A}, @var{dimvec})} is equivalent to the ## previous syntax with @code{@var{dimvec} = [@var{d1}, @dots{}, @var{dN}]}. ## ## @end deftypefn function this = repmat (this, varargin) this.Year = repmat (this.Year, varargin{:}); this.Month = repmat (this.Month, varargin{:}); this.Day = repmat (this.Day, varargin{:}); this.Hour = repmat (this.Hour, varargin{:}); this.Minute = repmat (this.Minute, varargin{:}); this.Second = repmat (this.Second, varargin{:}); endfunction ## -*- texinfo -*- ## @deftypefn {datetime} {@var{B} =} repelem (@var{A}, @var{n}) ## @deftypefnx {datetime} {@var{B} =} repelem (@var{A}, @var{d1}, @dots{}, @var{dN}) ## ## Repeat copies of datetime array elements. ## ## @code{@var{B} = repelem (@var{A}, @var{n})} returns a datetime vector ## @var{B} containing repeated elements of the input @var{A}, which must be ## a datetime vector. If @var{n} is a scalar, each element of @var{A} is ## repeated @var{n} times along the non-singleton dimension of @var{A}. If ## @var{n} is a vector, it must have the same elements as @var{A}, in which ## case it specifies the number of times to repeat each corresponding ## element of @var{A}. ## ## @code{@var{B} = repelem (@var{A}, @var{d1}, @dots{}, @var{dN})} returns ## an array @var{B} with each element of @var{A} repeated according to the ## the list of input arguments @code{@var{d1}, @dots{}, @var{dN}} each ## corresponding to a different dimension @code{1:ndims (@var{A})} of the ## input array @var{A}. @var{d1}, @dots{}, @var{dN} must be either scalars ## or vectors with the same length as the corresponding dimension of ## @var{A} containing non-negative integer values specifying the number of ## repetitions of each element along the corresponding dimension. ## ## @end deftypefn function this = repelem (this, varargin) this.Year = repelem (this.Year, varargin{:}); this.Month = repelem (this.Month, varargin{:}); this.Day = repelem (this.Day, varargin{:}); this.Hour = repelem (this.Hour, varargin{:}); this.Minute = repelem (this.Minute, varargin{:}); this.Second = repelem (this.Second, varargin{:}); endfunction ## -*- texinfo -*- ## @deftypefn {datetime} {@var{B} =} repelems (@var{A}, @var{R}) ## ## Construct a vector of repeated elements from a datetime array. ## ## @code{@var{B} = repelems (@var{A}, @var{R})} returns a datetime vector ## @var{B} containing repeated elements of the input @var{A}, which must be ## a datetime vector. @var{R} must be a @math{2*N} matrix of integers. ## Entries in the first row of @var{R} correspond to the linear indexing of ## the elements in @var{A} to be repeated. The corresponding entries in the ## second row of @var{R} specify the repeat count of each element. ## ## @end deftypefn function this = repelems (this, R) this.Year = repelems (this.Year, R); this.Month = repelems (this.Month, R); this.Day = repelems (this.Day, R); this.Hour = repelems (this.Hour, R); this.Minute = repelems (this.Minute, R); this.Second = repelems (this.Second, R); endfunction ## -*- texinfo -*- ## @deftypefn {datetime} {@var{B} =} reshape (@var{A}, @var{d1}, @dots{}, @var{dN}) ## @deftypefnx {datetime} {@var{B} =} reshape (@var{A}, @dots{}, @qcode{[]}, @dots{}) ## @deftypefnx {datetime} {@var{B} =} reshape (@var{A}, @var{dimvec}) ## ## Reshape datetime array. ## ## @code{@var{B} = reshape (@var{A}, @var{d1}, @dots{}, @var{dN})} returns ## a datetime array @var{B} with specified dimensions @var{d1}, @dots{}, ## @var{dN}, whose elements are taken columnwise from the datetime array ## @var{A}. The product of @var{d1}, @dots{}, @var{dN} must equal the total ## number of elements in @var{A}. ## ## @code{@var{B} = reshape (@var{A}, @dots{}, @qcode{[]}, @dots{})} returns ## a datetime array @var{B} with one dimension unspecified which is ## calculated automatically so that the product of dimensions in @var{B} ## matches the total elements in @var{A}, which must be divisible the ## product of specified dimensions. An empty matrix @qcode{([])} is used to ## flag the unspecified dimension. ## ## @end deftypefn function this = reshape (this, varargin) this.Year = reshape (this.Year, varargin{:}); this.Month = reshape (this.Month, varargin{:}); this.Day = reshape (this.Day, varargin{:}); this.Hour = reshape (this.Hour, varargin{:}); this.Minute = reshape (this.Minute, varargin{:}); this.Second = reshape (this.Second, varargin{:}); endfunction ## -*- texinfo -*- ## @deftypefn {datetime} {@var{B} =} circshift (@var{A}, @var{n}) ## @deftypefnx {datetime} {@var{B} =} circshift (@var{A}, @var{n}, @var{dim}) ## ## Circularly shift the elements in a datetime array. ## ## @code{@var{B} = circshift (@var{A}, @var{n})} circularly shifts the ## elements of the datetime array @var{A} according to @var{n}. If @var{n} ## is a nonzero integer scalar, then the elements of @var{A} are shifted by ## @var{n} elements along the first non-singleton dimension of @var{A}. If ## @var{n} is a vector, it must not be longer that the number of dimensions ## of @var{A} with each value of @var{n} corresponding to a dimension in ## @var{A}. The sign of the value(s) in @var{n} specify the direction in ## the elements of @var{A} are shifted. ## ## @code{@var{B} = circshift (@var{A}, @var{n}, @var{dim})} circularly ## shifts the elements of the datetime array @var{A} along the dimension ## specified by @var{dim}. In this case, @var{n} must be a scalar integer ## value. ## ## @end deftypefn function this = circshift (this, varargin) this.Year = circshift (this.Year, varargin{:}); this.Month = circshift (this.Month, varargin{:}); this.Day = circshift (this.Day, varargin{:}); this.Hour = circshift (this.Hour, varargin{:}); this.Minute = circshift (this.Minute, varargin{:}); this.Second = circshift (this.Second, varargin{:}); endfunction ## -*- texinfo -*- ## @deftypefn {datetime} {@var{B} =} permute (@var{A}, @var{dims}) ## ## Generalized transpose for a datetime N-D array. ## ## @code{@var{B} = permute (@var{A}, @var{dims})} returns the generalized ## transpose of the datetime array @var{A} by rearranging its dimensions ## according to the permutation vector specified in @var{dims}. ## ## @var{dims} must index all the dimensions @code{1:ndims (@var{A})} of the ## input array @var{A}, in any order, but only once. The @var{N}th ## dimension of @var{A} gets remapped to the dimension in @var{B} specified ## by @code{@var{dims}(@var{N})}. ## ## @end deftypefn function this = permute (this, order) this.Year = permute (this.Year, order); this.Month = permute (this.Month, order); this.Day = permute (this.Day, order); this.Hour = permute (this.Hour, order); this.Minute = permute (this.Minute, order); this.Second = permute (this.Second, order); endfunction ## -*- texinfo -*- ## @deftypefn {datetime} {@var{A} =} ipermute (@var{B}, @var{dims}) ## ## Inverse of the generalized transpose for a datetime N-D array. ## ## @code{@var{A} = ipermute (@var{B}, @var{dims})} returns the inverse of ## the generalized transpose performed by the @code{permute} function. The ## expression @code{ipermute (permute (@var{A}, @var{dims}), @var{dims})} ## returns the original array @var{A}. ## ## @var{dims} must index all the dimensions @code{1:ndims (@var{B})} of the ## input array @var{B}, in any order, but only once. The dimension of ## @var{B} specified in @code{@var{dims}(@var{N})} gets remapped to the ## @var{N}th dimension of @var{A}. ## ## @end deftypefn function this = ipermute (this, order) this.Year = ipermute (this.Year, order); this.Month = ipermute (this.Month, order); this.Day = ipermute (this.Day, order); this.Hour = ipermute (this.Hour, order); this.Minute = ipermute (this.Minute, order); this.Second = ipermute (this.Second, order); endfunction ## -*- texinfo -*- ## @deftypefn {datetime} {@var{B} =} transpose (@var{A}) ## ## Transpose a datetime matrix. ## ## @code{@var{B} = transpose (@var{A})} is the equivalent of the syntax ## @code{@var{B} = @var{A}.'} and returns the transpose of the datetime ## matrix @var{A}. ## ## @end deftypefn function this = transpose (this) this.Year = transpose (this.Year); this.Month = transpose (this.Month); this.Day = transpose (this.Day); this.Hour = transpose (this.Hour); this.Minute = transpose (this.Minute); this.Second = transpose (this.Second); endfunction ## -*- texinfo -*- ## @deftypefn {datetime} {@var{B} =} ctranspose (@var{A}) ## ## Transpose a datetime matrix. ## ## @code{@var{B} = ctranspose (@var{A})} is the equivalent of the syntax ## @code{@var{B} = @var{A}'} and returns the transpose of the datetime ## matrix @var{A}. For datetime arrays, @code{ctranspose} is identical to ## @code{transpose}. ## ## @end deftypefn function this = ctranspose (this) this.Year = ctranspose (this.Year); this.Month = ctranspose (this.Month); this.Day = ctranspose (this.Day); this.Hour = ctranspose (this.Hour); this.Minute = ctranspose (this.Minute); this.Second = ctranspose (this.Second); endfunction endmethods ################################################################################ ## ** Reference and Assignment Operations ** ## ################################################################################ ## Available Methods ## ## ## ## 'end' 'subsref' 'subsasgn' ## ## ## ################################################################################ methods (Hidden) ## Overloaded end keyword function last_index = end (this, end_dim, ndim_obj) lastdim = ndims (this); if (end_dim == ndim_obj && ndim_obj == 1) last_index = prod (size (this)); elseif (end_dim == ndim_obj && end_dim < lastdim) last_index = prod (size (this)(end_dim:lastdim)); else last_index = size (this, end_dim); endif endfunction ## Class specific subscripted reference function varargout = subsref (this, s) chain_s = s(2:end); s = s(1); switch (s.type) case '()' out = this; out.Year = this.Year(s.subs{:}); out.Month = this.Month(s.subs{:}); out.Day = this.Day(s.subs{:}); out.Hour = this.Hour(s.subs{:}); out.Minute = this.Minute(s.subs{:}); out.Second = this.Second(s.subs{:}); case '{}' error (["datetime.subsref: '{}' invalid indexing", ... " for referencing values. Use '()' instead."]); case '.' if (! ischar (s.subs)) error (["datetime.subsref: '.' index argument", ... " must be a character vector."]); endif switch (s.subs) case 'proxyArray' # used by 'table' class out = proxyArray (this); case 'Format' out = this.Format; case 'SystemTimeZone' out = this.SystemTimeZone; case 'TimeZone' out = this.TimeZone; case {'Year'} out = this.Year; case {'Month'} out = this.Month; case {'Day'} out = this.Day; case {'Hour'} out = this.Hour; case {'Minute'} out = this.Minute; case {'Second'} out = this.Second; otherwise error ("datetime.subsref: unrecognized property: %s", s.subs); endswitch endswitch ## Chained references if (! isempty (chain_s)) out = subsref (out, chain_s); endif varargout{1} = out; endfunction ## Class specific subscripted assignment function this = subsasgn (this, s, val) if (numel (s) > 1) # error ("duration.subsasgn: chained subscripts not allowed."); p = s(2); s = s(1); if (! strcmp (p.type, '()')) error (["datetime.subsasgn: '%s' invalid indexing", ... " for assigning values. Use '()' instead."], p.type); endif else p.subs = {':'}; endif switch s.type case '()' if (isempty (val)) this.Year(s.subs{:}) = []; this.Month(s.subs{:}) = []; this.Day(s.subs{:}) = []; this.Hour(s.subs{:}) = []; this.Minute(s.subs{:}) = []; this.Second(s.subs{:}) = []; return; elseif (! isa (val, "datetime")) error (["datetime.subsasgn: cannot assign %s values", ... "to a datetime array."], class (val)); endif this.Year(s.subs{:}) = val.Year; this.Month(s.subs{:}) = val.Month; this.Day(s.subs{:}) = val.Day; this.Hour(s.subs{:}) = val.Hour; this.Minute(s.subs{:}) = val.Minute; this.Second(s.subs{:}) = val.Second; case '{}' error (["datetime.subsasgn: '{}' invalid indexing", ... " for assigning values. Use '()' instead."]); case '.' if (! ischar (s.subs)) error (["datetime.subsasgn: '.' index argument", ... " must be a character vector."]); endif switch (s.subs) case 'Format' this.Format = val; case 'TimeZone' toTimeZone = val; if (! (ischar (toTimeZone) && (isrow (toTimeZone) ... || isempty (toTimeZone)))) error (["datetime.subsasgn: 'TimeZone' must be a", ... " character vector."]); endif ## Validate the target zone (empty means an unzoned array). if (! isempty (toTimeZone)) [~,~,~,~,~,~,errmsg] = __datetime__ (0, 0, 0, ... 'TimeZone', toTimeZone); if (! isnumeric (errmsg)) error ("datetime.subsasgn: %s", errmsg); endif endif if (isempty (this.TimeZone) || isempty (toTimeZone)) ## Attaching a zone to an unzoned array, or dropping the zone, ## reinterprets/keeps the wall-clock values without converting. this.TimeZone = toTimeZone; else ## Switching between two zones preserves the absolute instant, ## so the wall-clock values shift by the offset difference. [this.Year, this.Month, this.Day, this.Hour, this.Minute, ... this.Second, errmsg] = __datetime__ (this.Year, this.Month, ... this.Day, this.Hour, this.Minute, this.Second, ... 'TimeZone', this.TimeZone, 'toTimeZone', toTimeZone, ... 'Precision', 'microseconds'); if (! isnumeric (errmsg)) error ("datetime.subsasgn: %s", errmsg); endif this.TimeZone = toTimeZone; endif case {'Year'} this.Year(p.subs{:}) = val; this = normalize (this); case {'Month'} this.Month(p.subs{:}) = val; this = normalize (this); case {'Day'} this.Day(p.subs{:}) = val; this = normalize (this); case {'Hour'} this.Hour(p.subs{:}) = val; this = normalize (this); case {'Minute'} this.Minute(p.subs{:}) = val; this = normalize (this); case {'Second'} this.Second(p.subs{:}) = val; this = normalize (this); otherwise error ("datetime.subsasgn: unrecognized property: %s", s.subs); endswitch endswitch endfunction endmethods methods (Access = private) ## Return true if the rows of the numeric matrix KD are in lexicographic ## non-decreasing order (used by 'issortedrows'). Equal entries (including ## the +Inf sentinel used for NaT, and genuine infinities) count as ties and ## are resolved by the next column. function tf = rowsNonDecreasing (this, KD) m = rows (KD); if (m < 2) tf = true; return; endif a = KD(1:end-1, :); b = KD(2:end, :); undecided = true (m - 1, 1); bad = false (m - 1, 1); for j = 1:columns (KD) lt = a(:, j) < b(:, j); gt = a(:, j) > b(:, j); bad = bad | (undecided & gt); undecided = undecided & ! (lt | gt); endfor tf = ! any (bad); endfunction ## Shared engine for 'min' and 'max'. ARGS is the method's varargin, ISMAX ## selects max over min, and NOUT is the caller's nargout. Handles both the ## reduction form (min (A), min (A, [], DIM), '-all', nan flags) and the ## two-array elementwise form (min (A, B)). function [M, I] = minmaxImpl (A, args, ismax, nout) if (ismax) fname = 'max'; else fname = 'min'; endif I = []; ## Two-array elementwise form is signalled by a non-'[]' second argument. elementwise = ! isempty (args) ... && ! (isnumeric (args{1}) && isempty (args{1})); if (elementwise) if (nout > 1) error (strcat ("datetime.", fname, ": a second output is not", ... " supported when comparing two arrays.")); endif B = args{1}; if (! isa (B, 'datetime')) error (strcat ("datetime.", fname, ": comparison of two arrays", ... " requires both to be datetime.")); endif if (xor (isempty (A.TimeZone), isempty (B.TimeZone))) error (strcat ("datetime.", fname, ": cannot compare a datetime", ... " with a time zone to one without a time zone.")); endif nanflag = 'omitnan'; for k = 2:numel (args) x = args{k}; if (ischar (x) && isrow (x) && strcmpi (x, 'omitnan')) nanflag = 'omitnan'; elseif (ischar (x) && isrow (x) && strcmpi (x, 'includenan')) nanflag = 'includenan'; else error (strcat ("datetime.", fname, ": invalid option in a", ... " two-array comparison.")); endif endfor ## Compare by absolute instant (zone-independent), but pick exact ## component values from whichever operand wins to avoid a lossy ## instant round-trip. SA = serial (A); SB = serial (B); common = size (SA + SB); SA = SA + zeros (common); SB = SB + zeros (common); if (ismax) takeA = (SA >= SB) | isnan (SB); else takeA = (SA <= SB) | isnan (SB); endif ## Express B's components in A's time zone so selected values are exact. if (isempty (A.TimeZone) || strcmp (A.TimeZone, B.TimeZone)) YB = B.Year; MB = B.Month; DB = B.Day; hB = B.Hour; mB = B.Minute; sB = B.Second; else [YB, MB, DB, hB, mB, sB] = __datetime__ (B.Year, B.Month, B.Day, ... B.Hour, B.Minute, B.Second, 'TimeZone', B.TimeZone, ... 'toTimeZone', A.TimeZone, 'Precision', 'microseconds'); endif z = zeros (common); Y = A.Year + z; Mo = A.Month + z; D = A.Day + z; h = A.Hour + z; mi = A.Minute + z; s = A.Second + z; YB = YB + z; MB = MB + z; DB = DB + z; hB = hB + z; mB = mB + z; sB = sB + z; takeB = ! takeA; Y(takeB) = YB(takeB); Mo(takeB) = MB(takeB); D(takeB) = DB(takeB); h(takeB) = hB(takeB); mi(takeB) = mB(takeB); s(takeB) = sB(takeB); if (strcmp (nanflag, 'includenan')) nanpos = isnan (SA) | isnan (SB); Y(nanpos) = NaN; Mo(nanpos) = NaN; D(nanpos) = NaN; h(nanpos) = NaN; mi(nanpos) = NaN; s(nanpos) = NaN; endif M = A; M.Year = Y; M.Month = Mo; M.Day = D; M.Hour = h; M.Minute = mi; M.Second = s; return; endif ## Reduction form. Skip the '[]' placeholder, then read DIM / 'all' / ## the NaN flag from the remaining arguments. rest = args; if (! isempty (rest)) rest = rest(2:end); endif dim = []; allflag = false; nanflag = 'omitnan'; for k = 1:numel (rest) x = rest{k}; if (isnumeric (x)) if (! isscalar (x) || x < 1 || x != fix (x)) error (strcat ("datetime.", fname, ... ": DIM must be a positive integer.")); endif dim = x; elseif (ischar (x) && isrow (x)) if (strcmpi (x, 'all')) allflag = true; elseif (strcmpi (x, 'omitnan')) nanflag = 'omitnan'; elseif (strcmpi (x, 'includenan')) nanflag = 'includenan'; else error (strcat ("datetime.", fname, ": invalid option '", x, "'.")); endif else error (strcat ("datetime.", fname, ": invalid input argument.")); endif endfor if (isempty (A)) M = A; return; endif S = serial (A); if (allflag) Sv = S(:); if (ismax) [~, iv] = max (Sv, [], 1); else [~, iv] = min (Sv, [], 1); endif if (strcmp (nanflag, 'includenan') && any (isnan (Sv))) iv = find (isnan (Sv), 1); endif M = subset (A, iv); I = iv; return; endif if (isempty (dim)) dim = find (size (A) != 1, 1); if (isempty (dim)) dim = 1; endif endif if (dim > 2 || size (A, dim) < 2) M = A; I = ones (size (A)); return; endif if (ismax) [~, I] = max (S, [], dim); else [~, I] = min (S, [], dim); endif if (strcmp (nanflag, 'includenan')) nanmask = any (isnan (S), dim); [~, firstnan] = max (isnan (S), [], dim); I(nanmask) = firstnan(nanmask); endif [nr, nc] = size (S); if (dim == 1) lin = I + (0:nc-1) * nr; else lin = (1:nr)' + (I - 1) * nr; endif M = subset (A, lin); endfunction ## Return a subset of the array function this = subset (this, varargin) this = this; this.Year = this.Year(varargin{:}); this.Month = this.Month(varargin{:}); this.Day = this.Day(varargin{:}); this.Hour = this.Hour(varargin{:}); this.Minute = this.Minute(varargin{:}); this.Second = this.Second(varargin{:}); endfunction ## Numeric proxy used by 'table' and set operations for sorting, grouping, ## and set membership. Each datetime element maps to its six canonical ## components [Year, Month, Day, Hour, Minute, Second]; for a datetime ## matrix, each column contributes a six-column block. Not-A-Time (NaT) ## elements map to NaN across their components, just like the stored arrays. function out = proxyArray (this) [~, cols] = size (this.Year); if (cols > 1) out = []; for i = 1:cols SC = [this.Year(:,i), this.Month(:,i), this.Day(:,i), ... this.Hour(:,i), this.Minute(:,i), this.Second(:,i)]; out = [out, SC]; endfor else out = [this.Year(:), this.Month(:), this.Day(:), ... this.Hour(:), this.Minute(:), this.Second(:)]; endif endfunction ## Re-canonicalise the component arrays after a direct component assignment ## (e.g. 'd.Month = 13' rolls the extra month into the year). This routes ## the raw values back through the same C++ normaliser used by the ## constructor, at microsecond precision so no sub-second detail is lost. ## Not-A-Time and infinite elements are passed through unchanged. function this = normalize (this) if (isempty (this.Year)) return; endif if (isempty (this.TimeZone)) [this.Year, this.Month, this.Day, this.Hour, this.Minute, ... this.Second] = __datetime__ (this.Year, this.Month, this.Day, ... this.Hour, this.Minute, this.Second, 'Precision', 'microseconds'); else [this.Year, this.Month, this.Day, this.Hour, this.Minute, ... this.Second] = __datetime__ (this.Year, this.Month, this.Day, ... this.Hour, this.Minute, this.Second, 'TimeZone', this.TimeZone, ... 'toTimeZone', this.TimeZone, 'Precision', 'microseconds'); endif endfunction ## Absolute instant of each element as POSIX seconds (double, microsecond ## precision). Unzoned arrays are treated as UTC so the serial carries no ## system-zone daylight-saving offset; zoned arrays honour their zone (and ## DST). Not-A-Time maps to NaN and infinite elements keep their sign. ## Used by the arithmetic and relational instant-based comparisons. function s = serial (this) if (isempty (this.TimeZone)) tz = 'UTC'; else tz = this.TimeZone; endif s = __datetime__ (this.Year, this.Month, this.Day, this.Hour, ... this.Minute, this.Second, 'ConvertTo', 'posixtime', ... 'TimeZone', tz, 'Precision', 'microseconds'); endfunction ## Enforce the zone-compatibility rule for a set operation and express B in ## A's time zone so membership is decided on a shared wall clock (the ## absolute instants are preserved by the conversion). Both operands are ## already datetime arrays here; text/numeric promotion and rejection is ## done by the 'dtSetPromote' local function before this is called. function [A, B] = prepSetOp (A, B, op) if (xor (isempty (A.TimeZone), isempty (B.TimeZone))) error (strcat ("datetime.%s: cannot combine a datetime array with a", ... " time zone with one without a time zone."), op); endif if (! isempty (A.TimeZone) && ! strcmp (A.TimeZone, B.TimeZone)) [B.Year, B.Month, B.Day, B.Hour, B.Minute, B.Second] = __datetime__ ... (B.Year, B.Month, B.Day, B.Hour, B.Minute, B.Second, 'TimeZone', ... B.TimeZone, 'toTimeZone', A.TimeZone, 'Precision', 'microseconds'); B.TimeZone = A.TimeZone; endif endfunction ## Broadcast two datetime arrays to a common size for an element-wise ## operation, erroring if their sizes are incompatible. function [A, B] = broadcastPair (A, B, op) try z = zeros (size (A.Year + B.Year)); catch error (strcat ("datetime.%s: A and B must be of common size or", ... " scalars."), op); end_try_catch A.Year = A.Year + z; A.Month = A.Month + z; A.Day = A.Day + z; A.Hour = A.Hour + z; A.Minute = A.Minute + z; A.Second = A.Second + z; B.Year = B.Year + z; B.Month = B.Month + z; B.Day = B.Day + z; B.Hour = B.Hour + z; B.Minute = B.Minute + z; B.Second = B.Second + z; endfunction ## Assemble the result of a two-source set operation ('union'/'setxor'), ## whose values are drawn from both A (at IXA) and B (at IXB). The built-in ## returns those indices grouped A-then-B, which is exactly the 'stable' ## order; for the default 'sorted' order the gathered elements are reordered ## by their absolute instant (SA/SB), NaT sorting last. DOROWS selects the ## row-wise variant. function C = combineSets (A, B, ixA, ixB, SA, SB, doRows, stable) if (doRows) C = vertcat (subset (A, ixA, ':'), subset (B, ixB, ':')); if (! stable) [~, perm] = sortrows ([SA(ixA,:); SB(ixB,:)]); C = subset (C, perm, ':'); endif else Ca = subset (A, ixA); Cb = subset (B, ixB); C = vertcat (reshape (Ca, numel (Ca), 1), reshape (Cb, numel (Cb), 1)); if (! stable) sa = SA(ixA); sb = SB(ixB); [~, perm] = sort ([sa(:); sb(:)]); C = subset (C, perm); endif C = reshapeSetResult (C, isrow (A) && isrow (B)); endif endfunction ## Calendar-aware difference from A to B, element-wise on equal-sized inputs, ## expressed in the requested COMPS. Whole units are taken greedily from the ## largest requested down to the smallest (years, quarters, months, then ## weeks, days), each taking the most that does not step past B; the day of ## month is clamped on month steps and a whole unit is only borrowed if the ## time of day allows it (31 Jan 10:00 to 28 Feb 08:00 is 27 days 22 hours, ## not one month). When 'Time' is requested the leftover is its absolute ## instant difference, so it is daylight-saving aware. Returns a ## calendarDuration; NaT operands yield NaN. function C = calDiff (A, B, comps, op) [f, fmt] = parseCalComponents (comps, op); Y1 = A.Year; M1 = A.Month; D1 = A.Day; h1 = A.Hour; mi1 = A.Minute; s1 = A.Second; Y2 = B.Year; M2 = B.Month; D2 = B.Day; h2 = B.Hour; mi2 = B.Minute; s2 = B.Second; sz = size (Y1); ## A Not-A-Time operand yields a NaN result. Replace NaN components with a ## harmless placeholder for the calendar arithmetic, then restore NaN in ## the outputs, so month/day indexing never hits a NaN subscript. bad = isnan (Y1) | isnan (Y2); if (any (bad(:))) Y1(bad) = 2000; M1(bad) = 1; D1(bad) = 1; h1(bad) = 0; mi1(bad) = 0; s1(bad) = 0; Y2(bad) = 2000; M2(bad) = 1; D2(bad) = 1; h2(bad) = 0; mi2(bad) = 0; s2(bad) = 0; endif ## Direction: +1 where A < B (forward), -1 where A > B, 0 where equal. fwd = lexlt (Y1, M1, D1, h1, mi1, s1, Y2, M2, D2, h2, mi2, s2); bwd = lexlt (Y2, M2, D2, h2, mi2, s2, Y1, M1, D1, h1, mi1, s1); sgn = double (fwd) - double (bwd); Yc = Y1; Mc = M1; Dc = D1; # current date; time stays A's throughout monthsOut = zeros (sz); daysOut = zeros (sz); munits = []; if (f.y) munits(end+1) = 12; endif if (f.q) munits(end+1) = 3; endif if (f.m) munits(end+1) = 1; endif for u = munits totalM = (Y2 - Yc) .* 12 + (M2 - Mc); kApprox = fix (totalM ./ u); [Yk, Mk, Dk] = dtAddMonths (Yc, Mc, Dc, kApprox .* u); candGT = lexlt (Y2, M2, D2, h2, mi2, s2, Yk, Mk, Dk, h1, mi1, s1); candLT = lexlt (Yk, Mk, Dk, h1, mi1, s1, Y2, M2, D2, h2, mi2, s2); over = (sgn > 0 & candGT) | (sgn < 0 & candLT); k = kApprox - sgn .* double (over); monthsOut = monthsOut + k .* u; [Yc, Mc, Dc] = dtAddMonths (Yc, Mc, Dc, k .* u); endfor dunits = []; if (f.w) dunits(end+1) = 7; endif if (f.d) dunits(end+1) = 1; endif if (! isempty (dunits)) tLT = lexlt (0, 0, 0, h2, mi2, s2, 0, 0, 0, h1, mi1, s1); tGT = lexlt (0, 0, 0, h1, mi1, s1, 0, 0, 0, h2, mi2, s2); for u = dunits totalD = datenum (Y2, M2, D2) - datenum (Yc, Mc, Dc); wholeD = totalD - (sgn > 0 & tLT) + (sgn < 0 & tGT); k = fix (wholeD ./ u); daysOut = daysOut + k .* u; [Yc, Mc, Dc] = dtAddDays (Yc, Mc, Dc, k .* u); endfor endif if (f.t) cur = A; cur.Year = Yc; cur.Month = Mc; cur.Day = Dc; remSec = serial (B) - serial (cur); else remSec = zeros (sz); endif monthsOut(bad) = NaN; daysOut(bad) = NaN; remSec(bad) = NaN; Tdur = duration (0, 0, remSec); C = calendarDuration (zeros (sz), monthsOut, daysOut, Tdur, 'Format', fmt); endfunction ## Absolute instant of the given wall-clock components, interpreted in this ## array's time zone. Used by dateshift's 'nearest' rule to measure how far ## an element sits into its current unit. function ser = dsSerialOf (this, Y, M, D, h, m, s) tmp = this; tmp.Year = Y; tmp.Month = M; tmp.Day = D; tmp.Hour = h; tmp.Minute = m; tmp.Second = s; ser = serial (normalize (tmp)); endfunction ## Build a datetime from reduced POSIX seconds (the result of mean/median/ ## mode/std on this array's serial), preserving the Format and TimeZone. function R = fromReducedSerial (this, ser) R = this; [Y, M, D, h, m, s] = serial2components (this, ser); R.Year = Y; R.Month = M; R.Day = D; R.Hour = h; R.Minute = m; R.Second = s; endfunction ## Inverse of 'serial': map POSIX seconds back to the wall-clock components ## of this array's time zone. For a zoned array the serial is first read as ## a UTC wall clock and then converted into the target zone (honouring DST). function [Y, M, D, h, m, s] = serial2components (this, ser) if (isempty (this.TimeZone)) [Y, M, D, h, m, s] = __datetime__ (ser, 'ConvertFrom', 'posixtime', ... 'Precision', 'microseconds'); else [Y, M, D, h, m, s] = __datetime__ (ser, 'ConvertFrom', 'posixtime', ... 'Precision', 'microseconds'); [Y, M, D, h, m, s] = __datetime__ (Y, M, D, h, m, s, ... 'TimeZone', 'UTC', 'toTimeZone', this.TimeZone, ... 'Precision', 'microseconds'); endif endfunction ## Shift each element by a fixed number of seconds applied to its absolute ## instant (daylight-saving aware for zoned arrays), then rebuild the ## wall-clock components. DSEC may broadcast against the array size. The ## Format and TimeZone properties are preserved. function this = addSeconds (this, dsec) ser = serial (this) + dsec; [Y, M, D, h, m, s] = serial2components (this, ser); this.Year = Y; this.Month = M; this.Day = D; this.Hour = h; this.Minute = m; this.Second = s; endfunction ## Shift each element by a calendarDuration (SGN is +1 for addition, -1 for ## subtraction). Whole months are applied first with end-of-month day ## clamping, then whole calendar days (wall-clock preserving), then the ## time-of-day component as an instant. Not-A-Time and infinite elements are ## carried through unchanged. function this = addCalendar (this, calD, sgn) dMonths = sgn * calmonths (calD); dDays = sgn * caldays (calD); dTime = sgn * days (time (calD)) * 86400; # seconds ## Broadcast the instant components and the calendar deltas to a common ## size so the month math and the Not-A-Time / infinite masks all align. base = zeros (size (this.Year)) + zeros (size (this.Month)) ... + zeros (size (this.Day)) + zeros (size (this.Hour)) ... + zeros (size (this.Minute)) + zeros (size (this.Second)) ... + zeros (size (dMonths)) + zeros (size (dDays)); Y = this.Year + base; M = this.Month + base; D = this.Day + base; h = this.Hour + base; m = this.Minute + base; s = this.Second + base; dM = dMonths + base; dD = dDays + base; ## An element is "live" only when both the instant and the calendar delta ## are finite; Not-A-Time and infinite inputs (from either operand) ## propagate straight through as the corresponding non-finite marker. ok = isfinite (Y) & isfinite (dM) & isfinite (dD); mk = Y + dM + dD; ## Add whole months, clamping the day to the last day of the target month ## (e.g. 31 Jan + 1 month -> 28 Feb), then add whole calendar days. total = Y * 12 + (M - 1) + dM; Y(ok) = floor (total(ok) / 12); M(ok) = mod (total(ok), 12) + 1; D(ok) = min (D(ok), eomday (Y(ok), M(ok))) + dD(ok); Y(! ok) = mk(! ok); M(! ok) = mk(! ok); D(! ok) = mk(! ok); h(! ok) = mk(! ok); m(! ok) = mk(! ok); s(! ok) = mk(! ok); this.Year = Y; this.Month = M; this.Day = D; this.Hour = h; this.Minute = m; this.Second = s; this = normalize (this); ## Add the time-of-day component as an instant (daylight-saving aware). if (any (dTime(:) != 0)) this = addSeconds (this, dTime + zeros (size (this.Year))); endif endfunction ## Range with a fixed-length (duration/numeric) step of STEPSEC seconds. ## The endpoints' absolute instants are stepped by the ordinary numeric ## colon (so the inclusive-endpoint tolerance matches numeric ranges), then ## rebuilt into this array's time zone. Empty and reversed ranges fall out ## naturally from the numeric colon. function R = colonLinear (A, stepSec, B) ser = serial (A) : stepSec : serial (B); [Y, M, D, h, m, s] = serial2components (A, ser); R = A; R.Year = Y; R.Month = M; R.Day = D; R.Hour = h; R.Minute = m; R.Second = s; endfunction ## Range with a calendarDuration STEP. Each element is A + k*STEP for ## k = 0, 1, 2, ... (non-iterative, so month/year steps clamp the day of ## month per element). The number of steps is found by bracketing then ## binary-searching the largest k whose element has not passed B, which ## keeps calendar arithmetic exact without assuming a fixed element spacing. function R = colonCalendar (A, step, B) first = A + step; if (first == A) error ("datetime.colon: STEP must be nonzero."); endif incr = first > A; if ((incr && A > B) || (! incr && A < B)) R = A + (0:-1) .* step; # empty range, keeps A's Format and TimeZone return; endif hi = 1; while (colon_within (A + hi .* step, B, incr) && hi < 2^40) hi *= 2; endwhile lo = 0; while (hi - lo > 1) mid = floor ((lo + hi) / 2); if (colon_within (A + mid .* step, B, incr)) lo = mid; else hi = mid; endif endwhile R = A + (0:lo) .* step; endfunction ## Shared implementation of the six relational operators. Both operands ## must be datetime and either both zoned or both unzoned; a zoned pair with ## differing zones is aligned onto A's zone (preserving the instant) so the ## wall-clock components can be compared directly. Comparison is ## lexicographic on [Year Month Day Hour Minute Second] and therefore exact ## at every magnitude. Not-A-Time (NaN components) never compares ## less/greater/equal, so only 'ne' returns true when a NaT is involved. function TF = relcompare (A, B, op) if (! (isa (A, 'datetime') && isa (B, 'datetime'))) error ("datetime.%s: both operands must be datetime arrays.", op); endif if (xor (isempty (A.TimeZone), isempty (B.TimeZone))) error (strcat ("datetime.%s: cannot compare a datetime array with a", ... " time zone to one without a time zone."), op); endif aY = A.Year; aM = A.Month; aD = A.Day; ah = A.Hour; am = A.Minute; asec = A.Second; if (! isempty (A.TimeZone) && ! strcmp (A.TimeZone, B.TimeZone)) [bY, bM, bD, bh, bm, bsec] = __datetime__ (B.Year, B.Month, B.Day, ... B.Hour, B.Minute, B.Second, 'TimeZone', B.TimeZone, ... 'toTimeZone', A.TimeZone, 'Precision', 'microseconds'); else bY = B.Year; bM = B.Month; bD = B.Day; bh = B.Hour; bm = B.Minute; bsec = B.Second; endif switch (op) case 'eq' TF = (aY == bY) & (aM == bM) & (aD == bD) ... & (ah == bh) & (am == bm) & (asec == bsec); case 'ne' TF = ! ((aY == bY) & (aM == bM) & (aD == bD) ... & (ah == bh) & (am == bm) & (asec == bsec)); case 'lt' TF = lexlt (aY, aM, aD, ah, am, asec, bY, bM, bD, bh, bm, bsec); case 'gt' TF = lexlt (bY, bM, bD, bh, bm, bsec, aY, aM, aD, ah, am, asec); case 'le' TF = lexlt (aY, aM, aD, ah, am, asec, bY, bM, bD, bh, bm, bsec) ... | ((aY == bY) & (aM == bM) & (aD == bD) ... & (ah == bh) & (am == bm) & (asec == bsec)); case 'ge' TF = lexlt (bY, bM, bD, bh, bm, bsec, aY, aM, aD, ah, am, asec) ... | ((aY == bY) & (aM == bM) & (aD == bD) ... & (ah == bh) & (am == bm) & (asec == bsec)); endswitch endfunction endmethods endclassdef ## Shared back-end for 'isequal' (NANEQUAL false) and 'isequaln' (NANEQUAL ## true). ARGS is the cell array of operands. Returns true only when every ## operand is a datetime of the same size as the first and each pair of ## corresponding elements is the same point in time. A non-datetime operand or ## a time-zone mismatch (one zoned, one unzoned) yields false rather than an ## error. Zoned arrays are compared by absolute instant. When NANEQUAL is ## true, Not-A-Time elements compare equal to one another (component-wise NaN ## matches NaN); otherwise any NaT makes the result false, as with NaN. function TF = do_isequal (args, nanEqual) A = args{1}; TF = true; for i = 2:numel (args) B = args{i}; if (! (isa (A, 'datetime') && isa (B, 'datetime'))) TF = false; return; endif if (! isequal (size (A), size (B))) TF = false; return; endif if (xor (isempty (A.TimeZone), isempty (B.TimeZone))) TF = false; return; endif if (isempty (A)) continue; # two empties of equal size compare equal endif ## Align B onto A's zone so the wall-clock components compare by instant. aY = A.Year; aM = A.Month; aD = A.Day; ah = A.Hour; am = A.Minute; asec = A.Second; if (! isempty (A.TimeZone) && ! strcmp (A.TimeZone, B.TimeZone)) [bY, bM, bD, bh, bm, bsec] = __datetime__ (B.Year, B.Month, B.Day, ... B.Hour, B.Minute, B.Second, 'TimeZone', B.TimeZone, ... 'toTimeZone', A.TimeZone, 'Precision', 'microseconds'); else bY = B.Year; bM = B.Month; bD = B.Day; bh = B.Hour; bm = B.Minute; bsec = B.Second; endif if (nanEqual) E = ceq (aY, bY) & ceq (aM, bM) & ceq (aD, bD) ... & ceq (ah, bh) & ceq (am, bm) & ceq (asec, bsec); else E = (aY == bY) & (aM == bM) & (aD == bD) ... & (ah == bh) & (am == bm) & (asec == bsec); endif if (! all (E(:))) TF = false; return; endif endfor endfunction ## Component equality that also treats NaN as equal to NaN (used by isequaln). function TF = ceq (x, y) TF = (x == y) | (isnan (x) & isnan (y)); endfunction ## True while a candidate datetime X has not yet passed the range endpoint B, ## for an increasing (INCR true) or decreasing calendar range. Used to bracket ## and binary-search the element count in 'colonCalendar'. function TF = colon_within (X, B, incr) if (incr) TF = X <= B; else TF = X >= B; endif endfunction ## Lexicographic strictly-less-than on datetime component arrays. Returns true ## where the [Year Month Day Hour Minute Second] tuple of the first operand is ## strictly earlier than that of the second. Any NaN component (Not-A-Time) ## makes the element false, matching NaN comparison semantics. All arguments ## broadcast against each other element-wise. function TF = lexlt (aY, aM, aD, ah, am, asec, bY, bM, bD, bh, bm, bsec) eqY = aY == bY; eqM = aM == bM; eqD = aD == bD; eqh = ah == bh; eqm = am == bm; TF = (aY < bY) ... | (eqY & aM < bM) ... | (eqY & eqM & aD < bD) ... | (eqY & eqM & eqD & ah < bh) ... | (eqY & eqM & eqD & eqh & am < bm) ... | (eqY & eqM & eqD & eqh & eqm & asec < bsec); endfunction ## Promote a set-operation operand to a datetime array. A datetime is returned ## unchanged; text (character vector, string, or cellstr) is parsed by the ## constructor, inheriting REF's time zone so the two operands share a frame; ## numeric, logical, and duration operands are rejected the way MATLAB rejects ## them. Defined at file scope (not as a method) so it dispatches correctly ## when the first set-operation argument is text rather than a datetime. function d = dtSetPromote (x, ref, op) if (isa (x, 'datetime')) d = x; elseif (ischar (x) || iscellstr (x) || isa (x, 'string')) if (isempty (ref.TimeZone)) d = datetime (x); else d = datetime (x, 'TimeZone', ref.TimeZone); endif elseif (isa (x, 'duration') || isa (x, 'calendarDuration')) error (strcat ("datetime.%s: comparison is not defined between datetime", ... " and duration arrays."), op); else error (strcat ("datetime.%s: comparison is not defined between datetime", ... " and numeric arrays."), op); endif endfunction ## Orient a (non-'rows') set-operation result: a row vector when both operands ## were row vectors, and a column vector otherwise (matching MATLAB, so an empty ## result is 0-by-1 rather than 0-by-0). C is a datetime array. function C = reshapeSetResult (C, bothRows) if (bothRows) C = reshape (C, 1, numel (C)); else C = reshape (C, numel (C), 1); endif endfunction ## Promote a bound of 'isbetween' to a datetime array. A datetime is returned ## unchanged; text is parsed by the constructor, inheriting REF's time zone; ## anything else (numeric, duration, ...) is rejected. Defined at file scope so ## it dispatches correctly when the argument is text rather than a datetime. function d = dtIsbetweenArg (x, ref) if (isa (x, 'datetime')) d = x; elseif (ischar (x) || iscellstr (x) || isa (x, 'string')) if (isempty (ref.TimeZone)) d = datetime (x); else d = datetime (x, 'TimeZone', ref.TimeZone); endif else error (strcat ("datetime.isbetween: LOWER and UPPER must be datetime", ... " arrays or date/time text.")); endif endfunction ## Translate datetime missing-value flags into the 'omitnan'/'includenan' flags ## understood by the core reduction functions, leaving dims, 'all', and the core ## flags untouched. function args = dtStatFlags (args) for i = 1:numel (args) if (ischar (args{i}) && isrow (args{i})) switch (lower (args{i})) case {'omitnat', 'omitmissing'} args{i} = 'omitnan'; case {'includenat', 'includemissing'} args{i} = 'includenan'; endswitch endif endfor endfunction ## Number of days in month M of year Y (element-wise, proleptic Gregorian). function d = dtDaysInMonth (Y, M) dpm = [31 28 31 30 31 30 31 31 30 31 30 31]; d = dpm(M); d = reshape (d, size (M)); leap = (mod (Y, 4) == 0 & mod (Y, 100) != 0) | (mod (Y, 400) == 0); d(M == 2 & leap) = 29; endfunction ## Add K whole calendar months to the date (Y, M, D), clamping the day of month ## to the last valid day of the target month (e.g. 31 Jan + 1 month -> 28 Feb). ## Time of day is not represented here; it is carried unchanged by the caller. function [Yo, Mo, Do] = dtAddMonths (Y, M, D, K) idx = Y .* 12 + (M - 1) + K; Yo = floor (idx ./ 12); Mo = idx - Yo .* 12 + 1; Do = min (D, dtDaysInMonth (Yo, Mo)); endfunction ## Add K whole calendar days to the date (Y, M, D), element-wise. function [Yo, Mo, Do] = dtAddDays (Y, M, D, K) dn = datenum (Y(:), M(:), D(:)) + K(:); dv = datevec (dn); Yo = reshape (dv(:,1), size (Y)); Mo = reshape (dv(:,2), size (Y)); Do = reshape (dv(:,3), size (Y)); endfunction ## Map a day-of-week name to its number (Sunday = 1 .. Saturday = 7); NaN for an ## unrecognised name. function n = dsDayName (name) names = {'sunday', 'monday', 'tuesday', 'wednesday', 'thursday', ... 'friday', 'saturday'}; n = find (strcmpi (name, names), 1); if (isempty (n)) n = NaN; endif endfunction ## Parse a dateshift RULE into a kind ('current'/'next'/'previous'/'nearest'/ ## 'int') and, for the integer kind, its value. function [kind, n] = dsRule (r) n = 0; msg = strcat ("datetime.dateshift: rule must be an integer, 'next',", ... " 'previous', 'current', or 'nearest'."); if (ischar (r) && isrow (r)) if (any (strcmpi (r, {'current', 'next', 'previous', 'nearest'}))) kind = lower (r); else error (msg); endif elseif (isnumeric (r) && isscalar (r) && isreal (r) && r == fix (r)) kind = 'int'; n = r; else error (msg); endif endfunction ## Shift a date/time by N whole units (used by dateshift's rule). Calendar units ## produce a canonical date; sub-day units may overflow and are canonicalised by ## the caller's final normalisation. function [Y, M, D, h, mi, s] = dsShiftUnits (Y, M, D, h, mi, s, unit, n) switch (unit) case 'year' [Y, M, D] = dtAddMonths (Y, M, D, 12 .* n); case 'quarter' [Y, M, D] = dtAddMonths (Y, M, D, 3 .* n); case 'month' [Y, M, D] = dtAddMonths (Y, M, D, n); case 'week' [Y, M, D] = dtAddDays (Y, M, D, 7 .* n); case 'day' [Y, M, D] = dtAddDays (Y, M, D, n); case 'hour' h = h + n; case 'minute' mi = mi + n; case 'second' s = s + n; endswitch endfunction ## Truncate a date/time down to the start of the given calendar unit. function [Y, M, D, h, mi, s] = dsStartComp (Y, M, D, h, mi, s, unit) z = zeros (size (Y)); switch (unit) case 'year' M = ones (size (M)); D = ones (size (D)); h = z; mi = z; s = z; case 'quarter' M = 3 .* floor ((M - 1) / 3) + 1; D = ones (size (D)); h = z; mi = z; s = z; case 'month' D = ones (size (D)); h = z; mi = z; s = z; case 'week' dow = weekday (datenum (Y, M, D)); [Y, M, D] = dtAddDays (Y, M, D, -(dow - 1)); h = z; mi = z; s = z; case 'day' h = z; mi = z; s = z; case 'hour' mi = z; s = z; case 'minute' s = z; case 'second' s = floor (s); endswitch endfunction ## Compute the end of the given calendar unit. For the sub-day units and 'day' ## this is the start of the next unit; for a week it is the last day (Saturday); ## for month/quarter/year it is the last day of the unit at midnight. The ## results may overflow and are canonicalised by the caller's normalisation. function [Y, M, D, h, mi, s] = dsEndComp (Y, M, D, h, mi, s, unit) z = zeros (size (Y)); switch (unit) case 'year' M = 12 .* ones (size (M)); D = 31 .* ones (size (D)); h = z; mi = z; s = z; case 'quarter' qm = 3 .* ceil (M / 3); D = dtDaysInMonth (Y, qm); M = qm; h = z; mi = z; s = z; case 'month' D = dtDaysInMonth (Y, M); h = z; mi = z; s = z; case 'week' dow = weekday (datenum (Y, M, D)); [Y, M, D] = dtAddDays (Y, M, D, 7 - dow); h = z; mi = z; s = z; case 'day' [Y, M, D] = dtAddDays (Y, M, D, 1); h = z; mi = z; s = z; case 'hour' h = h + 1; mi = z; s = z; case 'minute' mi = mi + 1; s = z; case 'second' s = floor (s) + 1; endswitch endfunction ## Error message shared by caldiff and between for an invalid COMPONENTS input. function msg = calCompError (op) msg = strcat ("datetime.", op, ": COMPONENTS must be 'Years', 'Quarters',", ... " 'Months', 'Weeks', 'Days', or 'Time', or a string array or", ... " cell array containing those components."); endfunction ## Parse the COMPONENTS argument of caldiff/between into presence flags for each ## calendar unit and the display Format string of the resulting calendarDuration ## (which always contains 'm', 'd', and 't', with 'y'/'q'/'w' added only when ## those units are requested). An empty COMPONENTS selects the default set ## {Years, Months, Days, Time}. function [f, fmt] = parseCalComponents (comps, op) if (isempty (comps)) f = struct ('y', true, 'q', false, 'm', true, ... 'w', false, 'd', true, 't', true); else if (ischar (comps) && isrow (comps)) toks = {comps}; elseif (iscellstr (comps)) toks = comps(:)'; elseif (isa (comps, 'string')) toks = cellstr (comps)(:)'; else error (calCompError (op)); endif f = struct ('y', false, 'q', false, 'm', false, ... 'w', false, 'd', false, 't', false); for i = 1:numel (toks) switch (lower (toks{i})) case {'years', 'year', 'y'} f.y = true; case {'quarters', 'quarter', 'q'} f.q = true; case {'months', 'month', 'mo', 'm'} f.m = true; case {'weeks', 'week', 'w'} f.w = true; case {'days', 'day', 'd'} f.d = true; case {'time', 't'} f.t = true; otherwise error (calCompError (op)); endswitch endfor if (! (f.y || f.q || f.m || f.w || f.d || f.t)) error (calCompError (op)); endif endif ## The calendarDuration Format must contain 'm', 'd', and 't', so a ## single-component result such as caldiff (..., 'Years') keeps them even ## though only years are populated. This is invisible for a non-zero result ## (the other fields are zero and are not shown) but means an all-zero result ## displays as '0d' rather than MATLAB's '0y'/'0q'/'0w'; the stored value is ## the same. fmt = ''; if (f.y) fmt = [fmt, 'y']; endif if (f.q) fmt = [fmt, 'q']; endif fmt = [fmt, 'm']; if (f.w) fmt = [fmt, 'w']; endif fmt = [fmt, 'dt']; endfunction pr0m1th3as-datatypes-9c9a8d3/inst/days.m000066400000000000000000000041121522766574100201460ustar00rootroot00000000000000## Copyright (C) 2024-2026 Andreas Bertsatos ## ## This file is part of the datatypes package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {datatypes} {@var{D} =} days (@var{X}) ## ## Fixed-time duration in days. ## ## @code{@var{D} = days (@var{X})} returns a @qcode{duration} array representing ## fixed-time duration days equivalent to the values in @var{X}, which must be a ## numeric array. ## ## @code{days} is also available as a method for @qcode{duration} arrays, in ## which case it performs the opposite conversion. ## ## @seealso{duration, years, hours, minutes, seconds, milliseconds, ## duration.days} ## @end deftypefn function out = days (x) if (nargin == 0) x = 1; elseif (! isnumeric (x)) error ("days: input array must be numeric."); elseif (! isreal (x)) error ("days: input array must be real."); endif out = duration (double (x) * 24, 0, 0, 'Format', 'd'); endfunction %!demo %! ## `days` builds a *fixed-length* day of 24 hours (contrast a calendar day, %! ## whose length varies across DST changes). %! %! days (2.5) %!test %! X = magic (3); %! D = days (X); %! assert_equal (size (D), size (X)); %!test %! D = days ([1, 2, 3]); %! assert_equal (days (D), [1, 2, 3]); %!test %! D = days (int16 (1)); %! assert_equal (days (D), 1); %!test %! D = days (); %! assert_equal (days (D), 1); %!error days ("asd"); %!error days (1+i); pr0m1th3as-datatypes-9c9a8d3/inst/demos/000077500000000000000000000000001522766574100201415ustar00rootroot00000000000000pr0m1th3as-datatypes-9c9a8d3/inst/demos/calendarDuration.caldays000066400000000000000000000002561522766574100247650ustar00rootroot00000000000000%!demo %! ## As a `calendarDuration` method, `caldays` returns the day part of the span %! ## as a whole number of days. %! %! d = calendarDuration (2, 3, 40) %! caldays (d) pr0m1th3as-datatypes-9c9a8d3/inst/demos/calendarDuration.calendarDuration000066400000000000000000000024641522766574100266270ustar00rootroot00000000000000%!demo %! ## `calendarDuration` represents a span in *calendar* units — years, months, %! ## days, and a time-of-day. A calendar month or year is a real calendar step, %! ## not a fixed number of days, which is what sets it apart from `duration`. %! ## Give years, months and days: %! %! calendarDuration (1, 3, 10) %!demo %! ## Overflowing whole units normalise: 14 months rolls up into 1 year 2 months. %! ## Days are kept separate (a month has no fixed number of days), so 40 stays 40. %! %! calendarDuration (1, 14, 40) %!demo %! ## The six-argument form adds a time-of-day: years, months, days, hours, %! ## minutes, seconds. %! %! calendarDuration (0, 2, 5, 6, 30, 0) %!demo %! ## The `'Format'` option controls how the SAME span is broken down for display. %! ## A format must contain `m`, `d` and `t`; it may also use `y` (years), %! ## `q` (quarters) and `w` (weeks), listed in the order y q m w d t. Here one %! ## span shown four ways: %! %! calendarDuration (1, 14, 40, 'Format', 'ymdt') %! calendarDuration (1, 14, 40, 'Format', 'mdt') %! calendarDuration (1, 14, 40, 'Format', 'qmwdt') %! calendarDuration (1, 14, 40, 'Format', 'ymwdt') %!demo %! ## A numeric matrix with three (or six) columns builds a whole array at once — %! ## one calendar duration per row. %! %! calendarDuration ([1, 2, 15; 0, 6, 3]) pr0m1th3as-datatypes-9c9a8d3/inst/demos/calendarDuration.calmonths000066400000000000000000000004611522766574100253330ustar00rootroot00000000000000%!demo %! ## As a `calendarDuration` *method*, `calmonths` inverts the builder: it returns %! ## the whole span expressed as a number of months (years and months rolled %! ## together). Compare `split`, which instead decomposes the span into parts. %! %! d = calendarDuration (2, 3, 40) %! calmonths (d) pr0m1th3as-datatypes-9c9a8d3/inst/demos/calendarDuration.calquarters000066400000000000000000000003051522766574100256660ustar00rootroot00000000000000%!demo %! ## As a `calendarDuration` method, `calquarters` returns the year-and-month part %! ## of the span as a whole number of quarters. %! %! d = calendarDuration (2, 3, 40) %! calquarters (d) pr0m1th3as-datatypes-9c9a8d3/inst/demos/calendarDuration.calweeks000066400000000000000000000002611522766574100251370ustar00rootroot00000000000000%!demo %! ## As a `calendarDuration` method, `calweeks` returns the day part of the span %! ## as a whole number of weeks. %! %! d = calendarDuration (0, 0, 40) %! calweeks (d) pr0m1th3as-datatypes-9c9a8d3/inst/demos/calendarDuration.calyears000066400000000000000000000003131522766574100251420ustar00rootroot00000000000000%!demo %! ## As a `calendarDuration` method, `calyears` returns the number of whole years %! ## in the span (the inverse of the `calyears` builder). %! %! d = calendarDuration (2, 3, 40) %! calyears (d) pr0m1th3as-datatypes-9c9a8d3/inst/demos/calendarDuration.cellstr000066400000000000000000000002051522766574100250070ustar00rootroot00000000000000%!demo %! ## `cellstr` converts the array into a cell array of its display strings. %! %! d = calmonths ([1, 13, 25]) %! cellstr (d) pr0m1th3as-datatypes-9c9a8d3/inst/demos/calendarDuration.char000066400000000000000000000002761522766574100242640ustar00rootroot00000000000000%!demo %! ## `char` converts the array into a character matrix of its display strings, %! ## one row per element (shorter rows padded with spaces). %! %! d = calmonths ([1, 13]) %! char (d) pr0m1th3as-datatypes-9c9a8d3/inst/demos/calendarDuration.datevec000066400000000000000000000003311522766574100247520ustar00rootroot00000000000000%!demo %! ## `datevec` returns the span as a numeric row `[Y MO D H MI S]` per element, %! ## handy for feeding the raw components into other calculations. %! %! d = calendarDuration (1, 2, 3, 4, 5, 6) %! datevec (d) pr0m1th3as-datatypes-9c9a8d3/inst/demos/calendarDuration.eq000066400000000000000000000003261522766574100237500ustar00rootroot00000000000000%!demo %! ## `eq` (`==`) tests calendar durations for equality. Equivalent spans compare %! ## equal even when written with different builders. %! %! calmonths (12) == calyears (1) %! calweeks (2) == caldays (14) pr0m1th3as-datatypes-9c9a8d3/inst/demos/calendarDuration.ismissing000066400000000000000000000003561522766574100253530ustar00rootroot00000000000000%!demo %! ## For a calendar duration `ismissing` is the same test as `isnan`: a NaN span %! ## is the missing value, so calendar durations plug into the generic %! ## missing-data tools. %! %! d = calmonths ([1, NaN, 3]) %! ismissing (d) pr0m1th3as-datatypes-9c9a8d3/inst/demos/calendarDuration.isnan000066400000000000000000000002131522766574100244460ustar00rootroot00000000000000%!demo %! ## `isnan` flags the elements built from a NaN component — a Not-a-Number span. %! %! d = calmonths ([1, NaN, 3]) %! isnan (d) pr0m1th3as-datatypes-9c9a8d3/inst/demos/calendarDuration.minus000066400000000000000000000002051522766574100244720ustar00rootroot00000000000000%!demo %! ## `minus` (`-`) subtracts one calendar duration from another. %! %! a = calyears (2) + calmonths (2) %! a - calmonths (5) pr0m1th3as-datatypes-9c9a8d3/inst/demos/calendarDuration.plus000066400000000000000000000003411522766574100243230ustar00rootroot00000000000000%!demo %! ## `plus` (`+`) adds calendar durations. Because the units are calendar units, %! ## the sum normalises on display (here 6 + 9 months rolls into a year). %! %! a = calyears (1) + calmonths (6) %! a + calmonths (9) pr0m1th3as-datatypes-9c9a8d3/inst/demos/calendarDuration.split000066400000000000000000000005701522766574100244770ustar00rootroot00000000000000%!demo %! ## `split` decomposes a calendar duration into the whole-unit components you %! ## request, distributing the span across them. (Contrast the single-unit %! ## extractors such as `calmonths`, which each return the span as a *total* in %! ## one unit.) %! %! d = calendarDuration (2, 3, 40) %! [y, mo, days] = split (d, {'years', 'months', 'days'}); %! [y, mo, days] pr0m1th3as-datatypes-9c9a8d3/inst/demos/calendarDuration.time000066400000000000000000000002631522766574100243010ustar00rootroot00000000000000%!demo %! ## `time` extracts the time-of-day part as a `duration`, discarding the %! ## calendar (year/month/day) part. %! %! d = calendarDuration (0, 1, 2, 6, 30, 0) %! time (d) pr0m1th3as-datatypes-9c9a8d3/inst/demos/calendarDuration.times000066400000000000000000000003051522766574100244610ustar00rootroot00000000000000%!demo %! ## `times` (`.*`) scales a calendar duration by an integer factor. %! %! q = calmonths (3) %! q * 4 %! %! ## Scaling by an array yields an array of durations. %! caldays (7) .* [1, 2, 3] pr0m1th3as-datatypes-9c9a8d3/inst/demos/calendarDuration.unique000066400000000000000000000002501522766574100246450ustar00rootroot00000000000000%!demo %! ## `unique` returns the distinct calendar durations in an array (equivalent %! ## spans count as one). %! %! d = calmonths ([1, 13, 1, 25, 13]) %! unique (d) pr0m1th3as-datatypes-9c9a8d3/inst/demos/calendarDuration.vertcat000066400000000000000000000002631522766574100250130ustar00rootroot00000000000000%!demo %! ## Calendar durations concatenate like ordinary arrays; `[a; b]` stacks them %! ## into a single column. %! %! a = calyears ([1; 2]) %! b = calmonths ([3; 4]) %! [a; b] pr0m1th3as-datatypes-9c9a8d3/inst/demos/categorical.addcats000066400000000000000000000006551522766574100237510ustar00rootroot00000000000000%!demo %! ## `addcats` adds new, initially-empty categories without changing any values — %! ## handy to reserve labels that may appear later. For an ordinal array place %! ## the new category with `'Before'`/`'After'` so the order stays sensible. %! %! C = categorical ({'S'; 'M'; 'S'}, {'S', 'M', 'L'}, 'Ordinal', true) %! categories (C) %! %! ## Reserve `XL` just after `L`. %! categories (addcats (C, 'XL', 'After', 'L')) pr0m1th3as-datatypes-9c9a8d3/inst/demos/categorical.categorical000066400000000000000000000015421522766574100246170ustar00rootroot00000000000000%!demo %! ## `categorical` stores discrete labels compactly: each value becomes an index %! ## into a shared list of *categories*. Starting from repeated text labels ... %! %! sizes = {'M'; 'S'; 'L'; 'M'; 'S'; 'L'; 'M'} %! %! ## ... the categories are detected automatically — the sorted unique values. %! C = categorical (sizes) %! categories (C) %!demo %! ## Supply a *valueset* with `'Ordinal', true` to impose a meaningful order, so %! ## the values compare with `<` and `>`, not only `==`. %! %! raw = {'M'; 'S'; 'L'; 'M'} %! C = categorical (raw, {'S', 'M', 'L'}, 'Ordinal', true) %! %! ## Ordering questions now make sense. %! C >= 'M' %!demo %! ## A third argument renames the categories as you build; empty labels become %! ## the special `` element. %! %! codes = {'1'; '2'; ''; '1'} %! C = categorical (codes, {'1', '2'}, {'low', 'high'}) pr0m1th3as-datatypes-9c9a8d3/inst/demos/categorical.categories000066400000000000000000000005721522766574100244710ustar00rootroot00000000000000%!demo %! ## `categories` lists an array's categories — the full declared label set, not %! ## merely the values that happen to be present. %! %! C = categorical ({'M'; 'S'; 'M'}, {'S', 'M', 'L'}, 'Ordinal', true) %! categories (C) %! %! ## `L` is a declared category (and, being ordinal, keeps its place in the %! ## order) even though no element currently takes that value. pr0m1th3as-datatypes-9c9a8d3/inst/demos/categorical.cellstr000066400000000000000000000003401522766574100240050ustar00rootroot00000000000000%!demo %! ## `cellstr` converts the array back into a cell array of character vectors — %! ## the labels themselves. `` elements become empty strings. %! %! C = categorical ({'M'; 'S'; ''; 'L'}) %! cellstr (C) pr0m1th3as-datatypes-9c9a8d3/inst/demos/categorical.char000066400000000000000000000003511522766574100232540ustar00rootroot00000000000000%!demo %! ## `char` converts the array to a character matrix of its labels, one row per %! ## element (shorter labels are padded with spaces, as for any `char` matrix). %! %! C = categorical ({'small'; 'large'; 'small'}) %! char (C) pr0m1th3as-datatypes-9c9a8d3/inst/demos/categorical.countcats000066400000000000000000000005011522766574100243370ustar00rootroot00000000000000%!demo %! ## `countcats` tallies how many elements fall in each category, in category %! ## order. Categories with no elements are counted as zero, so the result lines %! ## up one-to-one with `categories`. %! %! C = categorical ({'M'; 'S'; 'L'; 'M'; 'S'; 'M'}, {'S', 'M', 'L', 'XL'}) %! categories (C) %! countcats (C) pr0m1th3as-datatypes-9c9a8d3/inst/demos/categorical.double000066400000000000000000000004211522766574100236070ustar00rootroot00000000000000%!demo %! ## `double` returns the integer *category codes* — each value's 1-based index %! ## into the category list. `` elements map to `NaN`. %! %! C = categorical ({'S'; 'M'; 'L'; ''; 'S'}, {'S', 'M', 'L'}, 'Ordinal', true) %! categories (C) %! double (C) pr0m1th3as-datatypes-9c9a8d3/inst/demos/categorical.eq000066400000000000000000000006451522766574100227520ustar00rootroot00000000000000%!demo %! ## `eq` (the `==` operator) compares a categorical array element by element, %! ## either to a single category name or to another categorical array. Equality %! ## works for every categorical — nominal or ordinal. %! %! C = categorical ({'M'; 'S'; 'L'; 'M'}) %! %! ## Which elements equal a given category? %! C == 'M' %! %! ## Element-wise against a second array. %! C == categorical ({'M'; 'M'; 'L'; 'S'}) pr0m1th3as-datatypes-9c9a8d3/inst/demos/categorical.histcounts000066400000000000000000000004571522766574100245510ustar00rootroot00000000000000%!demo %! ## `histcounts` bins a categorical array by category and returns the count in %! ## each — the histogram companion of `countcats`, with a second output naming %! ## the bins. %! %! C = categorical ({'M'; 'S'; 'L'; 'M'; 'S'; 'M'}, {'S', 'M', 'L'}) %! [N, cats] = histcounts (C); %! N %! cats pr0m1th3as-datatypes-9c9a8d3/inst/demos/categorical.intersect000066400000000000000000000003451522766574100243420ustar00rootroot00000000000000%!demo %! ## `intersect` returns the categories common to the values of two arrays — set %! ## arithmetic over categorical data. %! %! A = categorical ({'S'; 'M'; 'L'}) %! B = categorical ({'M'; 'L'; 'XL'}) %! intersect (A, B) pr0m1th3as-datatypes-9c9a8d3/inst/demos/categorical.iscategory000066400000000000000000000004521522766574100245120ustar00rootroot00000000000000%!demo %! ## `iscategory` tests whether names are categories of the array — independently %! ## of whether any element currently takes those values. %! %! C = categorical ({'M'; 'S'}, {'S', 'M', 'L'}) %! iscategory (C, {'M', 'L', 'XL'}) %! %! ## `M` and `L` are declared categories; `XL` is not. pr0m1th3as-datatypes-9c9a8d3/inst/demos/categorical.ismember000066400000000000000000000003471522766574100241470ustar00rootroot00000000000000%!demo %! ## `ismember` tests, element by element, whether each value appears in a second %! ## set given as category names or as another categorical array. %! %! C = categorical ({'M'; 'S'; 'XL'; 'L'}) %! ismember (C, {'S', 'M'}) pr0m1th3as-datatypes-9c9a8d3/inst/demos/categorical.ismissing000066400000000000000000000004631522766574100243500ustar00rootroot00000000000000%!demo %! ## For a categorical array `ismissing` is the same test as `isundefined`: the %! ## missing value *is* the `` element. It exists so categoricals %! ## plug into the generic missing-data tools (`rmmissing`, `standardizeMissing`). %! %! C = categorical ({'M'; ''; 'L'}) %! ismissing (C) pr0m1th3as-datatypes-9c9a8d3/inst/demos/categorical.isordinal000066400000000000000000000007301522766574100243240ustar00rootroot00000000000000%!demo %! ## `isordinal` reports whether the categories carry an order. A plain array is %! ## *nominal* — its categories are just labels with no ranking ... %! %! N = categorical ({'red'; 'green'; 'blue'}) %! isordinal (N) %! %! ## ... while one built with `'Ordinal', true` is ordered, which is what lets it %! ## be compared with `<` and `>` and sorted meaningfully. %! %! O = categorical ({'lo'; 'hi'; 'mid'}, {'lo', 'mid', 'hi'}, 'Ordinal', true) %! isordinal (O) pr0m1th3as-datatypes-9c9a8d3/inst/demos/categorical.isprotected000066400000000000000000000007111522766574100246640ustar00rootroot00000000000000%!demo %! ## `isprotected` reports whether the category list is locked. A nominal array %! ## is *unprotected*: assigning a new label simply extends its categories ... %! %! N = categorical ({'M'; 'S'}) %! isprotected (N) %! %! ## ... whereas ordinal arrays (and ones built with `'Protected', true`) are %! ## protected, so only their declared categories may be used. %! %! O = categorical ({'M'; 'S'}, {'S', 'M', 'L'}, 'Ordinal', true) %! isprotected (O) pr0m1th3as-datatypes-9c9a8d3/inst/demos/categorical.issorted000066400000000000000000000005511522766574100241750ustar00rootroot00000000000000%!demo %! ## `issorted` checks whether an ordinal array is already in non-decreasing %! ## category order — a cheap test to skip a needless `sort`. %! %! C = categorical ({'S'; 'M'; 'L'}, {'S', 'M', 'L'}, 'Ordinal', true) %! issorted (C) %! %! ## The same categories out of order: %! issorted (categorical ({'M'; 'S'; 'L'}, {'S', 'M', 'L'}, 'Ordinal', true)) pr0m1th3as-datatypes-9c9a8d3/inst/demos/categorical.isundefined000066400000000000000000000005631522766574100246410ustar00rootroot00000000000000%!demo %! ## `isundefined` flags the elements that belong to no category — the %! ## `` values that stand in for missing data. Empty labels become %! ## undefined at construction time. %! %! C = categorical ({'M'; ''; 'L'; 'M'}) %! isundefined (C) %! %! ## Dropping a category also undefines every element that used it. %! isundefined (removecats (C, 'M')) pr0m1th3as-datatypes-9c9a8d3/inst/demos/categorical.lt000066400000000000000000000007331522766574100227620ustar00rootroot00000000000000%!demo %! ## `lt` (the `<` operator) ranks values by category order, so it needs an %! ## *ordinal* array. It compares against a category name or another ordinal %! ## array. %! %! C = categorical ({'M'; 'S'; 'L'; 'M'}, {'S', 'M', 'L'}, 'Ordinal', true) %! %! ## Which sizes are strictly smaller than `L`? %! C < 'L' %! %! ## `>`, `<=` and `>=` behave the same way; the same test on a nominal array %! ## would raise an error, because unordered categories cannot be ranked. pr0m1th3as-datatypes-9c9a8d3/inst/demos/categorical.max000066400000000000000000000003341522766574100231250ustar00rootroot00000000000000%!demo %! ## `max` returns the highest-ranked category present in an ordinal array, %! ## by category order rather than by label. %! %! C = categorical ({'M'; 'L'; 'S'; 'M'}, {'S', 'M', 'L'}, 'Ordinal', true) %! max (C) pr0m1th3as-datatypes-9c9a8d3/inst/demos/categorical.median000066400000000000000000000003551522766574100236000ustar00rootroot00000000000000%!demo %! ## `median` returns the middle category by rank — defined for ordinal arrays, %! ## where the categories have a genuine order. %! %! C = categorical ({'S'; 'M'; 'L'; 'M'; 'L'}, {'S', 'M', 'L'}, 'Ordinal', true) %! median (C) pr0m1th3as-datatypes-9c9a8d3/inst/demos/categorical.mergecats000066400000000000000000000005101522766574100243060ustar00rootroot00000000000000%!demo %! ## `mergecats` collapses several categories into one, relabelling every element %! ## that used them — a quick way to coarsen a classification. %! %! C = categorical ({'S'; 'M'; 'L'; 'S'; 'M'}, {'S', 'M', 'L'}) %! %! ## Fold `S` and `M` together into a single `small` category. %! mergecats (C, {'S', 'M'}, 'small') pr0m1th3as-datatypes-9c9a8d3/inst/demos/categorical.min000066400000000000000000000005461522766574100231300ustar00rootroot00000000000000%!demo %! ## `min` returns the lowest-ranked category present in an ordinal array — %! ## ranking follows the category order, not the alphabetical label. %! %! C = categorical ({'M'; 'L'; 'S'; 'M'}, {'S', 'M', 'L'}, 'Ordinal', true) %! min (C) %! %! ## A second output gives the index of that element (as for numeric `min`). %! [lo, idx] = min (C); %! idx pr0m1th3as-datatypes-9c9a8d3/inst/demos/categorical.mode000066400000000000000000000004151522766574100232640ustar00rootroot00000000000000%!demo %! ## `mode` returns the most frequent category. It is the one central-tendency %! ## statistic that also works for *nominal* arrays, since it counts occurrences %! ## rather than ranking them. %! %! C = categorical ({'M'; 'S'; 'M'; 'L'; 'M'; 'S'}) %! mode (C) pr0m1th3as-datatypes-9c9a8d3/inst/demos/categorical.removecats000066400000000000000000000006041522766574100245100ustar00rootroot00000000000000%!demo %! ## `removecats` drops categories from an array. %! %! C = categorical ({'S'; 'M'; 'S'}, {'S', 'M', 'L'}) %! %! ## Naming a category removes it and turns its elements into ``. %! removecats (C, 'M') %! %! ## With no list only the *unused* categories go (values unchanged) — here the %! ## unused `L` disappears from the category list. %! categories (removecats (C)) pr0m1th3as-datatypes-9c9a8d3/inst/demos/categorical.renamecats000066400000000000000000000006021522766574100244600ustar00rootroot00000000000000%!demo %! ## `renamecats` changes category *names* while leaving the values in place. %! %! C = categorical ({'S'; 'M'; 'L'; 'S'}, {'S', 'M', 'L'}) %! %! ## Give a full set of new names (in category order) to rename them all ... %! renamecats (C, {'Small', 'Medium', 'Large'}) %! %! ## ... or rename only specific categories by old-name to new-name. %! renamecats (C, {'S'}, {'Small'}) pr0m1th3as-datatypes-9c9a8d3/inst/demos/categorical.reordercats000066400000000000000000000006621522766574100246610ustar00rootroot00000000000000%!demo %! ## `reordercats` changes the order of the categories. For an *ordinal* array %! ## this redefines the ranking used by `<`, `>`, `sort`, `min` and `max`. %! %! C = categorical ({'M'; 'S'; 'L'}, {'S', 'M', 'L'}, 'Ordinal', true) %! categories (C) %! %! ## Reverse the order so that `S` now ranks highest. %! B = reordercats (C, {'L', 'M', 'S'}); %! categories (B) %! %! ## The comparison follows the new ranking. %! B >= 'M' pr0m1th3as-datatypes-9c9a8d3/inst/demos/categorical.setcats000066400000000000000000000006101522766574100240030ustar00rootroot00000000000000%!demo %! ## `setcats` replaces the whole category list with the one you give: values %! ## that fall outside the new set become ``, and the categories end %! ## up as exactly the names you specified (in that order). %! %! C = categorical ({'S'; 'M'; 'L'; 'M'}, {'S', 'M', 'L'}) %! %! ## Keep only `S` and `M`; the `L` element is dropped to . %! setcats (C, {'S', 'M'}) pr0m1th3as-datatypes-9c9a8d3/inst/demos/categorical.setdiff000066400000000000000000000002711522766574100237640ustar00rootroot00000000000000%!demo %! ## `setdiff` returns the values present in the first array but not the second. %! %! A = categorical ({'S'; 'M'; 'L'}) %! B = categorical ({'M'; 'L'; 'XL'}) %! setdiff (A, B) pr0m1th3as-datatypes-9c9a8d3/inst/demos/categorical.setxor000066400000000000000000000003111522766574100236570ustar00rootroot00000000000000%!demo %! ## `setxor` returns the symmetric difference — values in exactly one of the two %! ## arrays. %! %! A = categorical ({'S'; 'M'; 'L'}) %! B = categorical ({'M'; 'L'; 'XL'}) %! setxor (A, B) pr0m1th3as-datatypes-9c9a8d3/inst/demos/categorical.sort000066400000000000000000000004101522766574100233220ustar00rootroot00000000000000%!demo %! ## `sort` orders an ordinal array by its category *ranking*, not alphabetically %! ## by label. %! %! C = categorical ({'L'; 'S'; 'M'; 'S'; 'L'}, {'S', 'M', 'L'}, 'Ordinal', true) %! sort (C) %! %! ## Any `` values sort to the end by default. pr0m1th3as-datatypes-9c9a8d3/inst/demos/categorical.summary000066400000000000000000000003641522766574100240400ustar00rootroot00000000000000%!demo %! ## `summary` tabulates the count of each category — including the %! ## `` element — giving a quick frequency overview of the array. %! %! C = categorical ({'M'; 'S'; 'L'; 'M'; ''; 'M'}, {'S', 'M', 'L'}) %! summary (C) pr0m1th3as-datatypes-9c9a8d3/inst/demos/categorical.union000066400000000000000000000002721522766574100234710ustar00rootroot00000000000000%!demo %! ## `union` returns the distinct values present in either array — the set union. %! %! A = categorical ({'S'; 'M'; 'L'}) %! B = categorical ({'M'; 'L'; 'XL'}) %! union (A, B) pr0m1th3as-datatypes-9c9a8d3/inst/demos/categorical.unique000066400000000000000000000004261522766574100236500ustar00rootroot00000000000000%!demo %! ## `unique` returns the distinct values *present* in the array, as a categorical %! ## array. (Contrast `categories`, which lists the full declared label set even %! ## when some categories are unused.) %! %! C = categorical ({'M'; 'S'; 'M'; 'L'; 'S'}) %! unique (C) pr0m1th3as-datatypes-9c9a8d3/inst/demos/categorical.vertcat000066400000000000000000000004261522766574100240120ustar00rootroot00000000000000%!demo %! ## Concatenating categorical arrays *unions* their category lists, so the result %! ## can hold categories that neither operand had alone. `[A; B]` stacks them. %! %! A = categorical ({'S'; 'M'}) %! B = categorical ({'L'; 'XL'}) %! C = [A; B]; %! C %! categories (C) pr0m1th3as-datatypes-9c9a8d3/inst/demos/duration.abs000066400000000000000000000001721522766574100224550ustar00rootroot00000000000000%!demo %! ## `abs` returns the magnitude of a (possibly negative) duration. %! %! d = minutes (30) - hours (2) %! abs (d) pr0m1th3as-datatypes-9c9a8d3/inst/demos/duration.bounds000066400000000000000000000002231522766574100231770ustar00rootroot00000000000000%!demo %! ## `bounds` returns the smallest and largest durations in one call. %! %! d = hours ([2, 5, 3, 9]) %! [lo, hi] = bounds (d); %! [lo, hi] pr0m1th3as-datatypes-9c9a8d3/inst/demos/duration.ceil000066400000000000000000000001461522766574100226250ustar00rootroot00000000000000%!demo %! ## `ceil` rounds a duration up to a whole unit. %! %! d = minutes (90) %! ceil (d, 'hours') pr0m1th3as-datatypes-9c9a8d3/inst/demos/duration.cellstr000066400000000000000000000002011522766574100233510ustar00rootroot00000000000000%!demo %! ## `cellstr` converts the array into a cell array of its display strings. %! %! d = minutes ([90, 150]) %! cellstr (d) pr0m1th3as-datatypes-9c9a8d3/inst/demos/duration.center000066400000000000000000000003011522766574100231620ustar00rootroot00000000000000%!demo %! ## `center` subtracts the mean from each element, so the result is centred on %! ## zero — the deviations from the average duration. %! %! d = hours ([1, 2, 3, 4, 5]) %! center (d) pr0m1th3as-datatypes-9c9a8d3/inst/demos/duration.char000066400000000000000000000002341522766574100226240ustar00rootroot00000000000000%!demo %! ## `char` converts the array into a character matrix of its display strings, %! ## one row per element. %! %! d = hours ([1.5; 2.25]) %! char (d) pr0m1th3as-datatypes-9c9a8d3/inst/demos/duration.colon000066400000000000000000000002731522766574100230240ustar00rootroot00000000000000%!demo %! ## The colon operator builds an evenly-spaced range of durations, using a %! ## duration step — here every half hour from 0 to 2 hours. %! %! hours (0):minutes (30):hours (2) pr0m1th3as-datatypes-9c9a8d3/inst/demos/duration.cummax000066400000000000000000000002361522766574100232030ustar00rootroot00000000000000%!demo %! ## `cummax` returns the running maximum — each element is the largest duration %! ## seen so far. %! %! d = hours ([2, 4, 3, 7, 5]) %! cummax (d) pr0m1th3as-datatypes-9c9a8d3/inst/demos/duration.cummin000066400000000000000000000002371522766574100232020ustar00rootroot00000000000000%!demo %! ## `cummin` returns the running minimum — each element is the smallest duration %! ## seen so far. %! %! d = hours ([7, 4, 5, 2, 3]) %! cummin (d) pr0m1th3as-datatypes-9c9a8d3/inst/demos/duration.cumsum000066400000000000000000000002041522766574100232150ustar00rootroot00000000000000%!demo %! ## `cumsum` returns the running total of the durations — a cumulative sum. %! %! d = hours ([1, 2, 3, 4]) %! cumsum (d) pr0m1th3as-datatypes-9c9a8d3/inst/demos/duration.datevec000066400000000000000000000003211522766574100233170ustar00rootroot00000000000000%!demo %! ## `datevec` returns the components as a numeric row `[Y MO D H MI S]` per %! ## element (a duration has no year/month/day, so those columns are zero). %! %! d = duration (26, 30, 15) %! datevec (d) pr0m1th3as-datatypes-9c9a8d3/inst/demos/duration.days000066400000000000000000000002221522766574100226440ustar00rootroot00000000000000%!demo %! ## As a `duration` method, `days` returns the duration expressed in %! ## fixed-length (24-hour) days. %! %! d = hours (36) %! days (d) pr0m1th3as-datatypes-9c9a8d3/inst/demos/duration.diff000066400000000000000000000002671522766574100226250ustar00rootroot00000000000000%!demo %! ## `diff` returns the differences between successive durations — for example the %! ## gaps between a series of elapsed times. %! %! d = hours ([1, 3, 6, 10]) %! diff (d) pr0m1th3as-datatypes-9c9a8d3/inst/demos/duration.duration000066400000000000000000000023431522766574100235370ustar00rootroot00000000000000%!demo %! ## `duration` represents a *fixed-length* elapsed time — unlike %! ## `calendarDuration`, here a day is always 24 hours and a year a fixed number %! ## of days. Give hours, minutes and seconds: %! %! duration (1, 30, 15) %!demo %! ## Values need not lie in the usual ranges — 25 hours is fine and stays 25 %! ## hours in the default `hh:mm:ss` display. A fourth argument adds milliseconds. %! %! duration (25, 30, 0) %! duration (0, 0, 1, 500) %!demo %! ## Build from time strings, optionally naming the `'InputFormat'`. %! %! duration ('36:15:00') %! duration ('06:15', 'InputFormat', 'mm:ss') %!demo %! ## The `'Format'` option controls display: a digital timer (`hh:mm:ss`, %! ## `dd:hh:mm:ss`, `mm:ss`, ...) or a single number in one unit (`y`, `d`, `h`, %! ## `m`, `s`). Here the SAME duration shown six ways: %! %! duration (30, 15, 0, 'Format', 'hh:mm:ss') %! duration (30, 15, 0, 'Format', 'dd:hh:mm:ss') %! duration (30, 15, 0, 'Format', 'h') %! duration (30, 15, 0, 'Format', 'm') %! duration (30, 15, 0, 'Format', 's') %! duration (30, 15, 0, 'Format', 'd') %!demo %! ## A numeric matrix builds a column of durations, one per row — columns are %! ## hours, minutes and seconds. %! %! duration ([1, 30, 0; 0, 45, 30]) pr0m1th3as-datatypes-9c9a8d3/inst/demos/duration.eq000066400000000000000000000003651522766574100223210ustar00rootroot00000000000000%!demo %! ## `eq` (`==`) tests durations for equality; equal spans compare equal even when %! ## built from different units. %! %! hours (1) == minutes (60) %! %! ## Element-wise against a scalar duration. %! minutes ([30, 60, 90]) == hours (1) pr0m1th3as-datatypes-9c9a8d3/inst/demos/duration.floor000066400000000000000000000001741522766574100230330ustar00rootroot00000000000000%!demo %! ## `floor` rounds a duration down to a whole unit (default: seconds). %! %! d = hours (2.9) %! floor (d, 'hours') pr0m1th3as-datatypes-9c9a8d3/inst/demos/duration.hms000066400000000000000000000002711522766574100224770ustar00rootroot00000000000000%!demo %! ## `hms` splits each duration into its hours, minutes and seconds as three %! ## separate numeric outputs. %! %! d = duration (1, 30, 45) %! [h, m, s] = hms (d); %! [h, m, s] pr0m1th3as-datatypes-9c9a8d3/inst/demos/duration.hours000066400000000000000000000004011522766574100230430ustar00rootroot00000000000000%!demo %! ## As a `duration` *method*, `hours` returns the duration expressed as a number %! ## of hours — the inverse of the `hours` builder. A single duration can be read %! ## out in any unit this way. %! %! d = hours (30) + minutes (15) %! hours (d) pr0m1th3as-datatypes-9c9a8d3/inst/demos/duration.interp1000066400000000000000000000003561522766574100232760ustar00rootroot00000000000000%!demo %! ## `interp1` interpolates duration-valued data at new sample points — useful for %! ## resampling a time series measured in durations. %! %! t = hours ([0, 1, 2]); %! v = minutes ([10, 20, 40]); %! interp1 (t, v, hours (0.5)) pr0m1th3as-datatypes-9c9a8d3/inst/demos/duration.intersect000066400000000000000000000002161522766574100237070ustar00rootroot00000000000000%!demo %! ## `intersect` returns the durations common to two arrays. %! %! A = hours ([1, 2, 3]) %! B = hours ([2, 3, 4]) %! intersect (A, B) pr0m1th3as-datatypes-9c9a8d3/inst/demos/duration.iqr000066400000000000000000000002611522766574100225020ustar00rootroot00000000000000%!demo %! ## `iqr` returns the interquartile range (the 75th minus the 25th percentile) — %! ## a robust measure of spread. %! %! d = hours ([2, 4, 4, 4, 5, 7, 9]) %! iqr (d) pr0m1th3as-datatypes-9c9a8d3/inst/demos/duration.isbetween000066400000000000000000000003061522766574100236740ustar00rootroot00000000000000%!demo %! ## `isbetween` tests, element by element, whether each duration lies within the %! ## closed interval `[lower, upper]`. %! %! d = hours ([1, 5, 10]) %! isbetween (d, hours (2), hours (8)) pr0m1th3as-datatypes-9c9a8d3/inst/demos/duration.isfinite000066400000000000000000000002011522766574100235130ustar00rootroot00000000000000%!demo %! ## `isfinite` flags the elements that are neither infinite nor NaN. %! %! d = hours ([1, Inf, NaN, 4]) %! isfinite (d) pr0m1th3as-datatypes-9c9a8d3/inst/demos/duration.isinf000066400000000000000000000001711522766574100230170ustar00rootroot00000000000000%!demo %! ## `isinf` flags infinite durations (from an infinite input value). %! %! d = hours ([1, Inf, 3]) %! isinf (d) pr0m1th3as-datatypes-9c9a8d3/inst/demos/duration.ismember000066400000000000000000000002471522766574100235160ustar00rootroot00000000000000%!demo %! ## `ismember` tests, element by element, whether each duration appears in a %! ## second set. %! %! d = hours ([1, 2, 5]) %! ismember (d, hours ([2, 3, 5])) pr0m1th3as-datatypes-9c9a8d3/inst/demos/duration.ismissing000066400000000000000000000003261522766574100237160ustar00rootroot00000000000000%!demo %! ## For a duration `ismissing` is the same test as `isnan`: a NaN duration is the %! ## missing value, so durations plug into the generic missing-data tools. %! %! d = hours ([1, NaN, 3]) %! ismissing (d) pr0m1th3as-datatypes-9c9a8d3/inst/demos/duration.isnan000066400000000000000000000001601522766574100230150ustar00rootroot00000000000000%!demo %! ## `isnan` flags the elements whose value is Not-a-Number. %! %! d = hours ([1, NaN, 3]) %! isnan (d) pr0m1th3as-datatypes-9c9a8d3/inst/demos/duration.issorted000066400000000000000000000002351522766574100235440ustar00rootroot00000000000000%!demo %! ## `issorted` checks whether the durations are already in non-decreasing order. %! %! issorted (hours ([1, 2, 3])) %! issorted (hours ([2, 1, 3])) pr0m1th3as-datatypes-9c9a8d3/inst/demos/duration.kurtosis000066400000000000000000000003131522766574100235700ustar00rootroot00000000000000%!demo %! ## `kurtosis` measures the "tailedness" of the duration distribution — a %! ## dimensionless shape statistic, returned as a plain number. %! %! d = hours ([1, 2, 2, 2, 3, 9]) %! kurtosis (d) pr0m1th3as-datatypes-9c9a8d3/inst/demos/duration.linspace000066400000000000000000000002171522766574100235060ustar00rootroot00000000000000%!demo %! ## `linspace` builds a fixed number of durations evenly spaced between two %! ## endpoints. %! %! linspace (hours (0), hours (2), 5) pr0m1th3as-datatypes-9c9a8d3/inst/demos/duration.lt000066400000000000000000000002671522766574100223340ustar00rootroot00000000000000%!demo %! ## `lt` (`<`) compares durations by length. `<`, `<=`, `>` and `>=` all work, %! ## since durations have a natural order. %! %! d = minutes ([30, 60, 90]) %! d < hours (1) pr0m1th3as-datatypes-9c9a8d3/inst/demos/duration.mad000066400000000000000000000002461522766574100224530ustar00rootroot00000000000000%!demo %! ## `mad` returns the mean absolute deviation of the durations — another robust %! ## spread statistic. %! %! d = hours ([2, 4, 4, 4, 5, 7, 9]) %! mad (d) pr0m1th3as-datatypes-9c9a8d3/inst/demos/duration.mape000066400000000000000000000004021522766574100226260ustar00rootroot00000000000000%!demo %! ## `mape` returns the mean absolute percentage error between two duration %! ## arrays. Being a ratio it is dimensionless — a plain percentage number. %! %! predicted = hours ([1, 2, 4]) %! actual = hours ([1, 2, 3]) %! mape (predicted, actual) pr0m1th3as-datatypes-9c9a8d3/inst/demos/duration.max000066400000000000000000000003231522766574100224730ustar00rootroot00000000000000%!demo %! ## `max` returns the longest duration in an array. %! %! d = minutes ([90, 30, 150, 45]) %! max (d) %! %! ## A second output gives its index (as for numeric `max`). %! [longest, idx] = max (d); %! idx pr0m1th3as-datatypes-9c9a8d3/inst/demos/duration.mean000066400000000000000000000001741522766574100226320ustar00rootroot00000000000000%!demo %! ## `mean` averages the durations in an array, returning a duration. %! %! d = hours ([1, 2, 3, 4, 5]) %! mean (d) pr0m1th3as-datatypes-9c9a8d3/inst/demos/duration.median000066400000000000000000000001731522766574100231460ustar00rootroot00000000000000%!demo %! ## `median` returns the middle duration of an array by value. %! %! d = minutes ([10, 20, 30, 90]) %! median (d) pr0m1th3as-datatypes-9c9a8d3/inst/demos/duration.milliseconds000066400000000000000000000002261522766574100243750ustar00rootroot00000000000000%!demo %! ## As a `duration` method, `milliseconds` returns the duration expressed in %! ## milliseconds. %! %! d = seconds (2.5) %! milliseconds (d) pr0m1th3as-datatypes-9c9a8d3/inst/demos/duration.min000066400000000000000000000001571522766574100224760ustar00rootroot00000000000000%!demo %! ## `min` returns the shortest duration in an array. %! %! d = minutes ([90, 30, 150, 45]) %! min (d) pr0m1th3as-datatypes-9c9a8d3/inst/demos/duration.minus000066400000000000000000000001501522766574100230370ustar00rootroot00000000000000%!demo %! ## `minus` (`-`) subtracts one duration from another. %! %! a = hours (3) %! a - minutes (45) pr0m1th3as-datatypes-9c9a8d3/inst/demos/duration.minutes000066400000000000000000000002301522766574100233670ustar00rootroot00000000000000%!demo %! ## As a `duration` method, `minutes` returns the whole duration expressed in %! ## minutes. %! %! d = hours (1) + minutes (30) %! minutes (d) pr0m1th3as-datatypes-9c9a8d3/inst/demos/duration.mode000066400000000000000000000001621522766574100226330ustar00rootroot00000000000000%!demo %! ## `mode` returns the most frequent duration in an array. %! %! d = hours ([1, 2, 2, 3, 2]) %! mode (d) pr0m1th3as-datatypes-9c9a8d3/inst/demos/duration.plus000066400000000000000000000001751522766574100226760ustar00rootroot00000000000000%!demo %! ## `plus` (`+`) adds durations together, giving a duration. %! %! a = hours (2) + minutes (30) %! a + seconds (45) pr0m1th3as-datatypes-9c9a8d3/inst/demos/duration.prctile000066400000000000000000000002261522766574100233520ustar00rootroot00000000000000%!demo %! ## `prctile` returns the requested percentiles of a duration array. %! %! d = hours ([1, 2, 3, 4, 5, 6, 7, 8]) %! prctile (d, [25, 50, 75]) pr0m1th3as-datatypes-9c9a8d3/inst/demos/duration.quantile000066400000000000000000000002321522766574100235270ustar00rootroot00000000000000%!demo %! ## `quantile` is the same idea as `prctile` but on a 0–1 scale. %! %! d = hours ([1, 2, 3, 4, 5, 6, 7, 8]) %! quantile (d, [0.25, 0.5, 0.75]) pr0m1th3as-datatypes-9c9a8d3/inst/demos/duration.range000066400000000000000000000001741522766574100230060ustar00rootroot00000000000000%!demo %! ## `range` returns the span from shortest to longest — `max - min`. %! %! d = hours ([2, 5, 3, 9]) %! range (d) pr0m1th3as-datatypes-9c9a8d3/inst/demos/duration.rdivide000066400000000000000000000003141522766574100233340ustar00rootroot00000000000000%!demo %! ## `rdivide` (`./`) divides a duration by a number to scale it down. %! %! hours (3) ./ 2 %! %! ## Dividing one duration by another gives their dimensionless ratio. %! hours (3) ./ minutes (30) pr0m1th3as-datatypes-9c9a8d3/inst/demos/duration.rmse000066400000000000000000000003521522766574100226560ustar00rootroot00000000000000%!demo %! ## `rmse` returns the root-mean-square error between two duration arrays — a %! ## duration measuring their typical difference. %! %! predicted = hours ([1, 2, 3]) %! actual = hours ([1, 2, 5]) %! rmse (predicted, actual) pr0m1th3as-datatypes-9c9a8d3/inst/demos/duration.round000066400000000000000000000003771522766574100230460ustar00rootroot00000000000000%!demo %! ## `round` rounds a duration. With no unit it rounds to the nearest second; %! ## pass a unit to round to whole hours, minutes, and so on. %! %! d = hours (2.4) %! round (d, 'hours') %! %! ## Default (nearest second): %! round (seconds (90.7)) pr0m1th3as-datatypes-9c9a8d3/inst/demos/duration.seconds000066400000000000000000000002161522766574100233450ustar00rootroot00000000000000%!demo %! ## As a `duration` method, `seconds` returns the duration expressed in seconds. %! %! d = minutes (2) + seconds (30) %! seconds (d) pr0m1th3as-datatypes-9c9a8d3/inst/demos/duration.setdiff000066400000000000000000000002331522766574100233320ustar00rootroot00000000000000%!demo %! ## `setdiff` returns the durations in the first array but not the second. %! %! A = hours ([1, 2, 3]) %! B = hours ([2, 3, 4]) %! setdiff (A, B) pr0m1th3as-datatypes-9c9a8d3/inst/demos/duration.setxor000066400000000000000000000002411522766574100232310ustar00rootroot00000000000000%!demo %! ## `setxor` returns the symmetric difference — durations in exactly one array. %! %! A = hours ([1, 2, 3]) %! B = hours ([2, 3, 4]) %! setxor (A, B) pr0m1th3as-datatypes-9c9a8d3/inst/demos/duration.sign000066400000000000000000000001731522766574100226510ustar00rootroot00000000000000%!demo %! ## `sign` returns -1, 0 or 1 for negative, zero and positive durations. %! %! d = hours ([-2, 0, 3]) %! sign (d) pr0m1th3as-datatypes-9c9a8d3/inst/demos/duration.skewness000066400000000000000000000003321522766574100235500ustar00rootroot00000000000000%!demo %! ## `skewness` measures the asymmetry of the duration distribution. Being a %! ## shape statistic it is dimensionless, so it comes back as a plain number. %! %! d = hours ([1, 1, 1, 2, 3, 8]) %! skewness (d) pr0m1th3as-datatypes-9c9a8d3/inst/demos/duration.sort000066400000000000000000000001651522766574100227010ustar00rootroot00000000000000%!demo %! ## `sort` orders the durations from shortest to longest. %! %! d = minutes ([180, 30, 90, 45]) %! sort (d) pr0m1th3as-datatypes-9c9a8d3/inst/demos/duration.std000066400000000000000000000002041522766574100224760ustar00rootroot00000000000000%!demo %! ## `std` returns the standard deviation of the durations, itself a duration. %! %! d = hours ([1, 2, 3, 4, 5]) %! std (d) pr0m1th3as-datatypes-9c9a8d3/inst/demos/duration.sum000066400000000000000000000001401522766574100225070ustar00rootroot00000000000000%!demo %! ## `sum` adds up the durations in an array. %! %! d = hours ([1, 2, 3, 4]) %! sum (d) pr0m1th3as-datatypes-9c9a8d3/inst/demos/duration.times000066400000000000000000000002761522766574100230360ustar00rootroot00000000000000%!demo %! ## `times` (`.*`) scales a duration by a numeric factor. %! %! hours (1.5) * 4 %! %! ## Element-wise scaling by an array yields an array of durations. %! minutes (30) .* [1, 2, 3] pr0m1th3as-datatypes-9c9a8d3/inst/demos/duration.union000066400000000000000000000002221522766574100230340ustar00rootroot00000000000000%!demo %! ## `union` returns the distinct durations present in either array. %! %! A = hours ([1, 2, 3]) %! B = hours ([2, 3, 4]) %! union (A, B) pr0m1th3as-datatypes-9c9a8d3/inst/demos/duration.unique000066400000000000000000000002131522766574100232120ustar00rootroot00000000000000%!demo %! ## `unique` returns the distinct durations in an array (equal spans count once). %! %! d = hours ([2, 1, 2, 3, 1]) %! unique (d) pr0m1th3as-datatypes-9c9a8d3/inst/demos/duration.vertcat000066400000000000000000000002761522766574100233650ustar00rootroot00000000000000%!demo %! ## Durations concatenate like ordinary arrays; `[a; b]` stacks them into one %! ## column, converting units as needed. %! %! a = hours ([1; 2]) %! b = minutes ([90; 30]) %! [a; b] pr0m1th3as-datatypes-9c9a8d3/inst/demos/duration.years000066400000000000000000000002401522766574100230270ustar00rootroot00000000000000%!demo %! ## As a `duration` method, `years` returns the duration expressed in %! ## fixed-length years (365.2425 days). %! %! d = days (365.2425) %! years (d) pr0m1th3as-datatypes-9c9a8d3/inst/demos/string.append000066400000000000000000000003231522766574100226360ustar00rootroot00000000000000%!demo %! ## `append` joins its arguments end to end, element-wise — like `strcat`, but it %! ## keeps trailing whitespace. %! %! first = string ({'Ann'; 'Bob'}) %! append (first, ' ', string ({'Lee'; 'Ng'})) pr0m1th3as-datatypes-9c9a8d3/inst/demos/string.cell000066400000000000000000000002041522766574100223040ustar00rootroot00000000000000%!demo %! ## `cell` also returns a cell array of character vectors (like `cellstr`). %! %! s = string ({'a'; 'b'; 'c'}) %! cell (s) pr0m1th3as-datatypes-9c9a8d3/inst/demos/string.cellstr000066400000000000000000000003071522766574100230410ustar00rootroot00000000000000%!demo %! ## `cellstr` converts a string array into a cell array of character vectors — %! ## the inverse of building a string from a cellstr. %! %! s = string ({'apple'; 'banana'}) %! cellstr (s) pr0m1th3as-datatypes-9c9a8d3/inst/demos/string.char000066400000000000000000000002711522766574100223060ustar00rootroot00000000000000%!demo %! ## `char` converts a string array to a plain character matrix, one row per %! ## element (shorter rows padded with spaces). %! %! s = string ({'small'; 'medium'}) %! char (s) pr0m1th3as-datatypes-9c9a8d3/inst/demos/string.compose000066400000000000000000000003701522766574100230360ustar00rootroot00000000000000%!demo %! ## `compose` applies a `printf`-style format to data, returning a string array — %! ## one element per row of the data. (The format itself is given as a string.) %! %! compose (string ('%s scored %d'), string ({'Ann'; 'Bob'}), [7; 9]) pr0m1th3as-datatypes-9c9a8d3/inst/demos/string.contains000066400000000000000000000002551522766574100232110ustar00rootroot00000000000000%!demo %! ## `contains` tests, element-wise, whether each string contains a substring. %! %! files = string ({'cat.txt'; 'dog.csv'; 'fish.txt'}) %! contains (files, '.txt') pr0m1th3as-datatypes-9c9a8d3/inst/demos/string.count000066400000000000000000000002101522766574100225120ustar00rootroot00000000000000%!demo %! ## `count` returns how many times a substring occurs in each string. %! %! s = string ({'banana'; 'apple'}) %! count (s, 'a') pr0m1th3as-datatypes-9c9a8d3/inst/demos/string.double000066400000000000000000000002431522766574100226420ustar00rootroot00000000000000%!demo %! ## `double` parses numeric text back into numbers; entries that are not numbers %! ## become `NaN`. %! %! s = string ({'3.14'; '42'; 'x'}) %! double (s) pr0m1th3as-datatypes-9c9a8d3/inst/demos/string.endsWith000066400000000000000000000002421522766574100231540ustar00rootroot00000000000000%!demo %! ## `endsWith` tests whether each string ends with a given suffix. %! %! files = string ({'cat.txt'; 'dog.csv'; 'fish.txt'}) %! endsWith (files, '.csv') pr0m1th3as-datatypes-9c9a8d3/inst/demos/string.eq000066400000000000000000000002031522766574100217710ustar00rootroot00000000000000%!demo %! ## `eq` (`==`) compares strings for equality, element by element. %! %! a = string ({'cat'; 'dog'; 'cat'}) %! a == 'cat' pr0m1th3as-datatypes-9c9a8d3/inst/demos/string.erase000066400000000000000000000002261522766574100224700ustar00rootroot00000000000000%!demo %! ## `erase` deletes every occurrence of a substring from each string. %! %! files = string ({'cat.txt'; 'dog.txt'}) %! erase (files, '.txt') pr0m1th3as-datatypes-9c9a8d3/inst/demos/string.eraseBetween000066400000000000000000000002741522766574100240050ustar00rootroot00000000000000%!demo %! ## `eraseBetween` deletes the text between two delimiters, keeping the %! ## delimiters themselves. %! %! s = string ({'ab'; 'xy'}) %! eraseBetween (s, '<', '>') pr0m1th3as-datatypes-9c9a8d3/inst/demos/string.extract000066400000000000000000000002661522766574100230470ustar00rootroot00000000000000%!demo %! ## `extract` pulls out every occurrence of a pattern, returning the matched %! ## pieces as a string array. %! %! s = string ('cat dog cat bird cat') %! extract (s, 'cat') pr0m1th3as-datatypes-9c9a8d3/inst/demos/string.extractAfter000066400000000000000000000003441522766574100240260ustar00rootroot00000000000000%!demo %! ## `extractAfter` returns the part of each string after the first occurrence of %! ## a substring — for example the extension after the dot. %! %! files = string ({'cat.txt'; 'dog.csv'}) %! extractAfter (files, '.') pr0m1th3as-datatypes-9c9a8d3/inst/demos/string.extractBefore000066400000000000000000000003631522766574100241700ustar00rootroot00000000000000%!demo %! ## `extractBefore` returns the part of each string before the first occurrence %! ## of a substring — for example a file's base name before its extension. %! %! files = string ({'cat.txt'; 'dog.csv'}) %! extractBefore (files, '.') pr0m1th3as-datatypes-9c9a8d3/inst/demos/string.extractBetween000066400000000000000000000002251522766574100243540ustar00rootroot00000000000000%!demo %! ## `extractBetween` returns the text lying between two delimiters. %! %! s = string ({'[abc]'; '[hello]'}) %! extractBetween (s, '[', ']') pr0m1th3as-datatypes-9c9a8d3/inst/demos/string.insertAfter000066400000000000000000000002431522766574100236560ustar00rootroot00000000000000%!demo %! ## `insertAfter` inserts text right after each occurrence of a substring. %! %! s = string ({'hello world'; 'good day'}) %! insertAfter (s, ' ', 'big ') pr0m1th3as-datatypes-9c9a8d3/inst/demos/string.insertBefore000066400000000000000000000003501522766574100240160ustar00rootroot00000000000000%!demo %! ## `insertBefore` inserts text right before each occurrence of a substring — %! ## for example a suffix just before a file extension. %! %! files = string ({'cat.txt'; 'dog.txt'}) %! insertBefore (files, '.txt', '_bak') pr0m1th3as-datatypes-9c9a8d3/inst/demos/string.ismember000066400000000000000000000002771522766574100232020ustar00rootroot00000000000000%!demo %! ## `ismember` tests, element by element, whether each string appears in a second %! ## set. %! %! s = string ({'apple'; 'kiwi'; 'pear'}) %! ismember (s, string ({'apple'; 'pear'})) pr0m1th3as-datatypes-9c9a8d3/inst/demos/string.ismissing000066400000000000000000000003351522766574100233770ustar00rootroot00000000000000%!demo %! ## `ismissing` flags the `` elements — the string form of missing data, %! ## distinct from an empty string `""`. %! %! s = string ({'a'; 'b'; 'c'}); %! s(2) = string (missing); %! s %! ismissing (s) pr0m1th3as-datatypes-9c9a8d3/inst/demos/string.issorted000066400000000000000000000002471522766574100232300ustar00rootroot00000000000000%!demo %! ## `issorted` checks whether the strings are already in alphabetical order. %! %! issorted (string ({'a'; 'b'; 'c'})) %! issorted (string ({'b'; 'a'; 'c'})) pr0m1th3as-datatypes-9c9a8d3/inst/demos/string.isstring000066400000000000000000000003671522766574100232410ustar00rootroot00000000000000%!demo %! ## `isstring` returns true only for string arrays — the reliable way to tell a %! ## string from a character vector, which (see `string`) look alike in source. %! %! isstring (string ('text')) %! isstring ('text') %! isstring ("text") pr0m1th3as-datatypes-9c9a8d3/inst/demos/string.join000066400000000000000000000002731522766574100223320ustar00rootroot00000000000000%!demo %! ## `join` combines the elements of a string array into a single string, placing %! ## a delimiter between them. %! %! parts = string ({'2024', '01', '15'}) %! join (parts, '-') pr0m1th3as-datatypes-9c9a8d3/inst/demos/string.lower000066400000000000000000000001541522766574100225210ustar00rootroot00000000000000%!demo %! ## `lower` converts each string to lower case. %! %! s = string ({'Hello'; 'WORLD'}) %! lower (s) pr0m1th3as-datatypes-9c9a8d3/inst/demos/string.matches000066400000000000000000000003311522766574100230120ustar00rootroot00000000000000%!demo %! ## `matches` tests, element-wise, whether each string equals a pattern *exactly* %! ## (contrast `contains`, which tests for a substring). %! %! s = string ({'cat'; 'category'; 'cat'}) %! matches (s, 'cat') pr0m1th3as-datatypes-9c9a8d3/inst/demos/string.pad000066400000000000000000000002461522766574100221370ustar00rootroot00000000000000%!demo %! ## `pad` pads each string with spaces to a common width — handy for aligning %! ## text into columns. %! %! s = string ({'a'; 'bb'; 'ccc'}) %! pad (s, 5) pr0m1th3as-datatypes-9c9a8d3/inst/demos/string.plus000066400000000000000000000003741522766574100223600ustar00rootroot00000000000000%!demo %! ## `plus` (`+`) concatenates two strings — a compact alternative to `append`. %! ## Both operands must be strings (a bare `'-'` is a char, so wrap it). %! %! a = string ({'Ann'; 'Bob'}) %! b = string ({'Lee'; 'Ng'}) %! a + string (' ') + b pr0m1th3as-datatypes-9c9a8d3/inst/demos/string.replace000066400000000000000000000002411522766574100230010ustar00rootroot00000000000000%!demo %! ## `replace` substitutes every occurrence of a substring with another. %! %! files = string ({'cat.txt'; 'dog.txt'}) %! replace (files, '.txt', '.md') pr0m1th3as-datatypes-9c9a8d3/inst/demos/string.replaceBetween000066400000000000000000000002341522766574100243150ustar00rootroot00000000000000%!demo %! ## `replaceBetween` substitutes the text lying between two delimiters. %! %! s = string ({'ab'; 'cd'}) %! replaceBetween (s, '<', '>', 'Z') pr0m1th3as-datatypes-9c9a8d3/inst/demos/string.reverse000066400000000000000000000001701522766574100230420ustar00rootroot00000000000000%!demo %! ## `reverse` reverses the characters within each string. %! %! s = string ({'Hello'; 'World'}) %! reverse (s) pr0m1th3as-datatypes-9c9a8d3/inst/demos/string.sort000066400000000000000000000001711522766574100223570ustar00rootroot00000000000000%!demo %! ## `sort` orders the strings alphabetically. %! %! s = string ({'pear'; 'apple'; 'fig'; 'banana'}) %! sort (s) pr0m1th3as-datatypes-9c9a8d3/inst/demos/string.split000066400000000000000000000002321522766574100225210ustar00rootroot00000000000000%!demo %! ## `split` breaks a string at each occurrence of a delimiter — the inverse of %! ## `join`. %! %! s = string ('2024-01-15') %! split (s, '-') pr0m1th3as-datatypes-9c9a8d3/inst/demos/string.splitlines000066400000000000000000000002531522766574100235570ustar00rootroot00000000000000%!demo %! ## `splitlines` breaks a string at its newline characters into one element per %! ## line. %! %! s = string (sprintf ('first\nsecond\nthird')) %! splitlines (s) pr0m1th3as-datatypes-9c9a8d3/inst/demos/string.startsWith000066400000000000000000000002401522766574100235410ustar00rootroot00000000000000%!demo %! ## `startsWith` tests whether each string begins with a given prefix. %! %! files = string ({'img_1'; 'img_2'; 'doc_1'}) %! startsWith (files, 'img') pr0m1th3as-datatypes-9c9a8d3/inst/demos/string.strcat000066400000000000000000000002311522766574100226650ustar00rootroot00000000000000%!demo %! ## `strcat` concatenates strings horizontally, element-wise across its inputs. %! %! name = string ({'Ann'; 'Bob'}) %! strcat (name, ' Smith') pr0m1th3as-datatypes-9c9a8d3/inst/demos/string.strcmp000066400000000000000000000003141522766574100226770ustar00rootroot00000000000000%!demo %! ## `strcmp` compares strings for exact equality — the same idea as `==`, kept %! ## for compatibility with the classic string functions. %! %! a = string ({'cat'; 'dog'}) %! strcmp (a, 'cat') pr0m1th3as-datatypes-9c9a8d3/inst/demos/string.string000066400000000000000000000020641522766574100227010ustar00rootroot00000000000000%!demo %! ## A `string` is MATLAB's string-array type. There is a crucial Octave %! ## difference to keep in mind: in core Octave BOTH `'text'` and `"text"` produce %! ## ordinary **character vectors** (class `char`) — unlike MATLAB, the double %! ## quote does *not* make a string. Only the `string` constructor produces a %! ## string array. %! %! class ('text') % single quotes -> char %! class ("text") % double quotes -> ALSO char in Octave %! class (string ('text')) % the constructor -> string %!demo %! ## Build a string array from a cell array of character vectors — one element %! ## per cell. A string array prints its elements in double quotes. %! %! string ({'apple'; 'banana'; 'cherry'}) %!demo %! ## Numbers convert to their text form. %! %! string ([3.14, 42, -1]) %!demo %! ## The special `` value is the string counterpart of `NaN`, and is %! ## distinct from an empty string `""`. Assign `string (missing)` to introduce %! ## one. %! %! s = string ({'a'; 'b'; 'c'}) %! s(2) = string (missing) %! ismissing (s) pr0m1th3as-datatypes-9c9a8d3/inst/demos/string.strip000066400000000000000000000002061522766574100225300ustar00rootroot00000000000000%!demo %! ## `strip` removes leading and trailing whitespace from each string. %! %! s = string ({' spaced '; 'tidy'}) %! strip (s) pr0m1th3as-datatypes-9c9a8d3/inst/demos/string.strlength000066400000000000000000000003201522766574100233760ustar00rootroot00000000000000%!demo %! ## `strlength` returns the number of characters in each string (contrast %! ## `length`/`numel`, which count the array's elements). %! %! s = string ({'a'; 'four'; 'twelve chars'}) %! strlength (s) pr0m1th3as-datatypes-9c9a8d3/inst/demos/string.unique000066400000000000000000000002031522766574100226720ustar00rootroot00000000000000%!demo %! ## `unique` returns the distinct strings in an array, sorted. %! %! s = string ({'b'; 'a'; 'b'; 'c'; 'a'}) %! unique (s) pr0m1th3as-datatypes-9c9a8d3/inst/demos/string.upper000066400000000000000000000001541522766574100225240ustar00rootroot00000000000000%!demo %! ## `upper` converts each string to upper case. %! %! s = string ({'Hello'; 'World'}) %! upper (s) pr0m1th3as-datatypes-9c9a8d3/inst/demos/string.vertcat000066400000000000000000000003021522766574100230340ustar00rootroot00000000000000%!demo %! ## String arrays concatenate like ordinary arrays; `[a; b]` stacks them into a %! ## single column. %! %! a = string ({'apple'; 'banana'}) %! b = string ({'cherry'; 'date'}) %! [a; b] pr0m1th3as-datatypes-9c9a8d3/inst/demos/table.DimensionNames000066400000000000000000000016241522766574100240660ustar00rootroot00000000000000%!demo %! ## Create a table and display its dimension names. You can access row %! ## names and data using dimension names with dot syntax. %! %! LastName = {'Sanchez'; 'Johnson'; 'Li'; 'Diaz'; 'Brown'}; %! Age = [38;43;38;40;49]; %! Smoker = logical ([1; 0; 1; 0; 1]); %! Height = [71; 69; 64; 67; 64]; %! Weight = [176; 163; 131; 133; 119]; %! BloodPressure = [124, 93; 109, 77; 125, 83; 117, 75; 122, 80]; %! T = table (Age, Smoker, Height, Weight, BloodPressure, 'RowNames', LastName) %! T.Properties.DimensionNames %! %! ## Access the row names using the first dimension name. %! T.Row %! %! ## Access the data using the second dimension name. %! T.Variables %! %! ## Modify the names of its dimensions using the Properties %! T.Properties.DimensionNames = {'Patient', 'Data'}; %! T.Properties %! %! ## Change a single dimension name %! T.Properties.DimensionNames(1) = 'Patients' %! T.Properties %! T.Patients pr0m1th3as-datatypes-9c9a8d3/inst/demos/table.RowNames000066400000000000000000000020701522766574100227040ustar00rootroot00000000000000%!demo %! ## Access rows by selecting row names %! %! LastName = {'Sanchez'; 'Johnson'; 'Lee'; 'Diaz'; 'Brown'}; %! Age = [38;43;38;40;49]; %! Height = [71;69;64;67;64]; %! Weight = [176;163;131;133;119]; %! T = table (Age, Weight, Height, 'RowNames', LastName) %! T('Lee',:) %! T{'Lee',:} %! T({'Lee', 'Diaz'},:) %! T{{'Lee', 'Diaz'},:} %!demo %! ## Set row names by assigning a new variable %! %! LastName = {'Sanchez'; 'Johnson'; 'Lee'; 'Diaz'; 'Brown'}; %! Age = [38;43;38;40;49]; %! Height = [71;69;64;67;64]; %! Weight = [176;163;131;133;119]; %! T = table (Age, Weight, Height) %! T.Properties.RowNames = LastName %!demo %! ## Set row names by assigning an existing variable %! %! LastName = {'Sanchez'; 'Johnson'; 'Lee'; 'Diaz'; 'Brown'}; %! Age = [38;43;38;40;49]; %! Height = [71;69;64;67;64]; %! Weight = [176;163;131;133;119]; %! T = table (Age, Weight, Height, LastName) %! %! ## In this case the variable persists in the table %! T.Properties.RowNames = T.LastName %! %! ## In this case the variable is removed from the table %! T.Properties.RowNames = 'LastName' pr0m1th3as-datatypes-9c9a8d3/inst/demos/table.VariableNames000066400000000000000000000012101522766574100236550ustar00rootroot00000000000000%!demo %! ## Store patient data in a table %! %! LastName = {'Sanchez'; 'Johnson'; 'Li'; 'Diaz'; 'Brown'}; %! Age = [38;43;38;40;49]; %! Smoker = logical ([1; 0; 1; 0; 1]); %! Height = [71; 69; 64; 67; 64]; %! Weight = [176; 163; 131; 133; 119]; %! BloodPressure = [124, 93; 109, 77; 125, 83; 117, 75; 122, 80]; %! T = table (LastName, Age, Smoker, Height, Weight, BloodPressure) %! %! ## Use indexing to access variables %! meanHeight = mean (T.Height) %! %! ## Calculate body mass index (BMI), and add it as a new table variable. %! pounds2kg = 0.4535924; %! inch2meter = 0.0254; %! T.BMI = (T.Weight * pounds2kg) ./ (T.Height * inch2meter) .^ 2 pr0m1th3as-datatypes-9c9a8d3/inst/demos/table.addprop000066400000000000000000000021251522766574100226030ustar00rootroot00000000000000%!demo %! ## `addprop` attaches your own metadata to a table. Each property is declared %! ## with a *scope*: a `'table'`-scoped property holds a single value for the whole %! ## table, while a `'variable'`-scoped property holds one value per variable, %! ## aligned with the table's columns. %! %! Age = [38; 43; 40]; %! Height = duration (0, 0, 0) + hours ([1.9; 1.8; 1.7]); %! Gender = categorical ({'M'; 'F'; 'M'}); %! T = table (Age, Height, Gender) %! T = addprop (T, {'DataSource', 'Instrument'}, {'table', 'variable'}); %! %! ## The table-scoped `DataSource` takes one value; the variable-scoped %! ## `Instrument` takes a cell with one entry per variable (Age, Height, Gender). %! T.Properties.CustomProperties.DataSource = 'Clinic A'; %! T.Properties.CustomProperties.Instrument = {'form', 'stadiometer', 'form'}; %! T.Properties.CustomProperties %! %! ## Because it is variable-scoped, `Instrument` follows column operations — after %! ## removing `Height` its entry drops out too, staying aligned with the columns. %! T2 = removevars (T, 'Height'); %! T2.Properties.CustomProperties.Instrument pr0m1th3as-datatypes-9c9a8d3/inst/demos/table.addvars000066400000000000000000000012041522766574100225730ustar00rootroot00000000000000%!demo %! ## `addvars` inserts one or more variables into a table. Appended at the end by %! ## default, the new columns can be named with `'NewVariableNames'` and may be of %! ## any type — here a `datetime` is added to a table of mixed variables. %! %! Name = string ({'Sanchez'; 'Johnson'; 'Li'}); %! Age = [38; 43; 40]; %! Gender = categorical ({'M'; 'M'; 'F'}); %! T = table (Name, Age, Gender) %! Visit = datetime (2024, [1; 2; 3], [5; 6; 7]); %! addvars (T, Visit) %! %! ## Use `'Before'` or `'After'` to place the new variable at a chosen position. %! addvars (T, logical ([1; 0; 1]), 'After', 'Age', 'NewVariableNames', {'Smoker'}) pr0m1th3as-datatypes-9c9a8d3/inst/demos/table.anymissing000066400000000000000000000010161522766574100233310ustar00rootroot00000000000000%!demo %! ## `anymissing` is a fast scalar check: `true` as soon as *any* element of the %! ## table is missing, across every variable and type — `NaN` for numeric, %! ## `NaT` for datetime, `` for string, `` for categorical. %! %! Name = string ({'Li'; 'Diaz'; 'Brown'}); %! Age = [38; 43; 40]; %! Grade = categorical ({'A'; ''; 'B'}); %! T = table (Name, Age, Grade) %! anymissing (T) %! %! ## A table with no gaps of any kind returns `false`. %! anymissing (table (string ({'Li'; 'Diaz'}), [38; 40])) pr0m1th3as-datatypes-9c9a8d3/inst/demos/table.convertvars000066400000000000000000000010221522766574100235210ustar00rootroot00000000000000%!demo %! ## `convertvars` changes the data type of selected variables, leaving the rest %! ## alone. The target type is given as a name, or as a conversion function. %! %! T = table ([1; 0; 1], [38; 43; 40], 'VariableNames', {'Smoker', 'Age'}) %! convertvars (T, 'Smoker', 'logical') %! %! ## Select several variables at once and hand them a conversion function. %! T2 = table ({'M'; 'F'; 'M'}, {'NY'; 'CA'; 'MA'}, ... %! 'VariableNames', {'Gender', 'State'}) %! convertvars (T2, {'Gender', 'State'}, 'categorical') pr0m1th3as-datatypes-9c9a8d3/inst/demos/table.fillmissing000066400000000000000000000013171522766574100234740ustar00rootroot00000000000000%!demo %! ## `fillmissing` replaces gaps rather than dropping rows. Fill with a constant, %! ## applied across the (numeric) variables. %! %! T = table ([38; NaN; 40], [71; 69; NaN], 'VariableNames', {'Age', 'Height'}) %! fillmissing (T, 'constant', 0) %!demo %! ## Interpolation rules fill from neighbouring rows and work across types — %! ## `'previous'` carries the last value forward (here through both a `datetime` %! ## and a numeric column), while `'linear'` interpolates numeric gaps. %! %! Date = datetime (2024, 1, [1; NaN; 3; NaN]); %! Reading = [10; NaN; 30; 40]; %! T = table (Date, Reading) %! fillmissing (T, 'previous') %! %! fillmissing (table ([1; NaN; NaN; 4], 'VariableNames', {'v'}), 'linear') pr0m1th3as-datatypes-9c9a8d3/inst/demos/table.findgroups000066400000000000000000000011251522766574100233310ustar00rootroot00000000000000%!demo %! ## `findgroups` assigns each row an integer group number based on its grouping %! ## variable(s) — the setup step for a `splitapply` computation. A second output %! ## returns a table naming each group. %! %! Species = {'setosa'; 'virginica'; 'setosa'; 'virginica'; 'setosa'}; %! Petal = [1.4; 5.1; 1.5; 5.9; 1.3]; %! T = table (Species, Petal) %! [G, groupNames] = findgroups (T(:, 'Species')); %! G' %! groupNames %! %! ## The group numbers index into `groupNames`, and feed straight into %! ## `splitapply` to aggregate `Petal` per species. %! splitapply (@mean, T(:, 'Petal'), G) pr0m1th3as-datatypes-9c9a8d3/inst/demos/table.groupcounts000066400000000000000000000004411522766574100235410ustar00rootroot00000000000000%!demo %! ## `groupcounts` tallies how many rows fall in each group — a frequency table. %! ## It also reports each group's share as a percentage of the total. %! %! Species = {'setosa'; 'virginica'; 'setosa'; 'virginica'; 'setosa'}; %! T = table (Species) %! groupcounts (T, 'Species') pr0m1th3as-datatypes-9c9a8d3/inst/demos/table.groupfilter000066400000000000000000000007671522766574100235260ustar00rootroot00000000000000%!demo %! ## `groupfilter` keeps the rows belonging to groups that satisfy a predicate — %! ## it filters *whole groups*, not individual rows. Here only species with more %! ## than two observations are retained. %! %! Species = {'setosa'; 'virginica'; 'setosa'; 'virginica'; 'setosa'}; %! Petal = [1.4; 5.1; 1.5; 5.9; 1.3]; %! T = table (Species, Petal) %! groupfilter (T, 'Species', @(x) numel (x) > 2) %! %! ## `setosa` has three rows and stays; `virginica` has two and is dropped %! ## entirely. pr0m1th3as-datatypes-9c9a8d3/inst/demos/table.groupsummary000066400000000000000000000010111522766574100237150ustar00rootroot00000000000000%!demo %! ## `groupsummary` is the one-call grouped aggregation: group by one or more %! ## variables, then apply a summary method to the data variables. It reports the %! ## group count alongside each requested statistic. %! %! Species = {'setosa'; 'virginica'; 'setosa'; 'virginica'; 'setosa'}; %! Petal = [1.4; 5.1; 1.5; 5.9; 1.3]; %! T = table (Species, Petal) %! groupsummary (T, 'Species', 'mean', 'Petal') %! %! ## Several methods can be requested at once. %! groupsummary (T, 'Species', {'min', 'max'}, 'Petal') pr0m1th3as-datatypes-9c9a8d3/inst/demos/table.grouptransform000066400000000000000000000006721522766574100242470ustar00rootroot00000000000000%!demo %! ## `grouptransform` applies a group-wise transform but returns a table of the %! ## *same height* as the input — each value replaced by its transformed version. %! ## Here every petal length is centred on its own species mean. %! %! Species = {'setosa'; 'virginica'; 'setosa'; 'virginica'; 'setosa'}; %! Petal = [1.4; 5.1; 1.5; 5.9; 1.3]; %! T = table (Species, Petal) %! grouptransform (T, 'Species', @(x) x - mean (x), 'Petal') pr0m1th3as-datatypes-9c9a8d3/inst/demos/table.head000066400000000000000000000010541522766574100220530ustar00rootroot00000000000000%!demo %! ## `head` previews the top of a table without you having to know its size. %! ## With no count it returns the first 8 rows (or fewer if the table is short) — %! ## handy for glancing at a wide, mixed-type table. %! %! n = (1:20)'; %! T = table (string (num2str (n, 'ID%02d')), datetime (2024, 1, n), ... %! categorical (mod (n, 3), 0:2, {'low', 'mid', 'hi'}), n .^ 2, ... %! 'VariableNames', {'Tag', 'Date', 'Band', 'Score'}); %! head (T) %! %! ## Pass a count to ask for a specific number of leading rows. %! head (T, 3) pr0m1th3as-datatypes-9c9a8d3/inst/demos/table.height000066400000000000000000000007151522766574100224250ustar00rootroot00000000000000%!demo %! ## `height` counts rows, independent of how many columns each variable spans. %! ## Here `BloodPressure` occupies two columns, yet the table still has 5 rows. %! %! Age = [38; 43; 38; 40; 49]; %! BloodPressure = [124, 93; 109, 77; 125, 83; 117, 75; 122, 80]; %! T = table (Age, BloodPressure) %! height (T) %! %! ## It is the row count you index against, so `height` is the natural bound %! ## for a loop or a final-row reference. %! T(height (T), :) pr0m1th3as-datatypes-9c9a8d3/inst/demos/table.horzcat000066400000000000000000000026571522766574100226360ustar00rootroot00000000000000%!demo %! ## `horzcat` joins tables side by side, keeping all their variables. The tables %! ## must have the same number of rows and distinct variable names. The `[A, B]` %! ## bracket syntax calls it for you, and variables of any type combine freely. %! %! A = table ([38; 43; 40], 'VariableNames', {'Age'}) %! B = table (categorical ({'M'; 'F'; 'M'}), logical ([1; 0; 1]), ... %! 'VariableNames', {'Gender', 'Smoker'}) %! [A, B] %!demo %! ## Custom properties are carried through the concatenation. A *variable*-scoped %! ## property is concatenated alongside its columns, so the merged table keeps one %! ## entry per variable; a *table*-scoped property is preserved when the operands %! ## agree on it. %! %! A = table ([38; 43], categorical ({'M'; 'F'}), 'VariableNames', {'Age', 'Gender'}) %! A = addprop (A, {'Study', 'Source'}, {'table', 'variable'}); %! A.Properties.CustomProperties.Study = 'Cohort-7'; %! A.Properties.CustomProperties.Source = {'intake', 'intake'}; %! %! B = table (duration (0, [15; 30], 0), 'VariableNames', {'Wait'}) %! B = addprop (B, {'Study', 'Source'}, {'table', 'variable'}); %! B.Properties.CustomProperties.Study = 'Cohort-7'; %! B.Properties.CustomProperties.Source = {'clock'}; %! %! H = [A, B]; %! ## The variable-scoped Source now spans all three variables ... %! H.Properties.CustomProperties.Source %! ## ... and the shared table-scoped Study is retained. %! H.Properties.CustomProperties.Study pr0m1th3as-datatypes-9c9a8d3/inst/demos/table.inner2outer000066400000000000000000000006261522766574100234320ustar00rootroot00000000000000%!demo %! ## `inner2outer` swaps the nesting of a table whose variables are themselves %! ## tables: the inner (nested) variables are lifted to the top level. %! %! Inner = table ([1; 2; 3], [4; 5; 6], 'VariableNames', {'x', 'y'}) %! NT = table ([10; 20; 30], Inner, 'VariableNames', {'id', 'inner'}) %! NT %! %! ## After `inner2outer` the nested `x` and `y` become ordinary variables. %! inner2outer (NT) pr0m1th3as-datatypes-9c9a8d3/inst/demos/table.innerjoin000066400000000000000000000014761522766574100231550ustar00rootroot00000000000000%!demo %! ## `innerjoin` keeps only rows whose key exists in *both* tables — unmatched %! ## rows are dropped — and, unlike `join`, the right key need not be unique. %! ## Pass `'Keys'` to match on several variables at once; here a row must agree on %! ## both `Name` and `Year` to match. %! %! L = table ({'Li'; 'Li'; 'Diaz'}, [2023; 2024; 2023], [10; 11; 12], ... %! 'VariableNames', {'Name', 'Year', 'Visits'}) %! R = table ({'Li'; 'Diaz'}, [2023; 2023], categorical ({'NY'; 'CA'}), ... %! 'VariableNames', {'Name', 'Year', 'State'}) %! innerjoin (L, R, 'Keys', {'Name', 'Year'}) %! %! ## Li's 2024 row has no match on the right, so it is excluded. Extra outputs %! ## give the source row indices in the left and right tables. %! [T, iL, iR] = innerjoin (L, R, 'Keys', {'Name', 'Year'}); %! [iL, iR] pr0m1th3as-datatypes-9c9a8d3/inst/demos/table.intersect000066400000000000000000000012371522766574100231550ustar00rootroot00000000000000%!demo %! ## `intersect` returns the rows common to both tables — the set intersection. %! ## A row matches only when *every* variable agrees, so mixed-type rows are %! ## compared field by field. %! %! A = table ({'Li'; 'Diaz'; 'Brown'}, [38; 40; 49], ... %! categorical ({'NY'; 'CA'; 'MA'}), ... %! 'VariableNames', {'Name', 'Age', 'State'}) %! B = table ({'Diaz'; 'Brown'; 'Lee'}, [40; 49; 30], ... %! categorical ({'CA'; 'MA'; 'TX'}), ... %! 'VariableNames', {'Name', 'Age', 'State'}) %! intersect (A, B) %! %! ## The extra outputs locate the shared rows within each input. %! [C, iA, iB] = intersect (A, B); %! [iA, iB] pr0m1th3as-datatypes-9c9a8d3/inst/demos/table.iscolumn000066400000000000000000000007241522766574100230060ustar00rootroot00000000000000%!demo %! ## A table is a *column* when it has a single variable — regardless of how %! ## many columns that variable spans internally. %! %! T1 = table ([1; 2; 3], 'VariableNames', {'x'}) %! iscolumn (T1) %! %! ## The lone variable may itself be multi-column; the table is still a column. %! T2 = table ([1 2; 3 4; 5 6], 'VariableNames', {'xy'}) %! iscolumn (T2) %! %! ## Two or more variables make it no longer a column. %! iscolumn (table ([1; 2; 3], [4; 5; 6])) pr0m1th3as-datatypes-9c9a8d3/inst/demos/table.isempty000066400000000000000000000005331522766574100226450ustar00rootroot00000000000000%!demo %! ## A table is empty when it has no rows **or** no variables. Note that a %! ## table with named variables but zero rows still counts as empty. %! %! T = table ('Size', [0, 3], 'VariableTypes', {'double', 'string', 'double'}) %! isempty (T) %! %! ## Adding a row makes it non-empty. %! T(1, :) = {42, string('S1'), 3.14}; %! isempty (T) pr0m1th3as-datatypes-9c9a8d3/inst/demos/table.ismember000066400000000000000000000013251522766574100227560ustar00rootroot00000000000000%!demo %! ## `ismember` tests, row by row, whether each row of the first table also occurs %! ## in the second — returning a logical column, not a new table. A row counts as %! ## a member only when all of its variables match a row of `B`. %! %! A = table ({'Li'; 'Diaz'; 'Brown'}, [38; 40; 49], ... %! categorical ({'NY'; 'CA'; 'MA'}), ... %! 'VariableNames', {'Name', 'Age', 'State'}) %! B = table ({'Diaz'; 'Brown'; 'Lee'}, [40; 49; 30], ... %! categorical ({'CA'; 'MA'; 'TX'}), ... %! 'VariableNames', {'Name', 'Age', 'State'}) %! ismember (A, B) %! %! ## A second output gives, for each matching row, its position in `B` (0 if none). %! [TF, loc] = ismember (A, B); %! loc pr0m1th3as-datatypes-9c9a8d3/inst/demos/table.ismissing000066400000000000000000000010661522766574100231620ustar00rootroot00000000000000%!demo %! ## Where `anymissing` gives a single yes/no, `ismissing` locates *every* gap, %! ## returning a logical array the size of the table — one flag per element. It %! ## recognises the missing value proper to each type at once. %! %! Name = string ({'Li'; 'Diaz'; 'Brown'}); %! Age = [38; NaN; 40]; %! Grade = categorical ({'A'; 'B'; ''}); %! Visit = datetime (2024, 1, [5; NaN; 7]); %! T = table (Name, Age, Grade, Visit) %! ismissing (T) %! %! ## Pass an indicator to also treat specific sentinel values as missing. %! ismissing (table ([1; -99; 3]), -99) pr0m1th3as-datatypes-9c9a8d3/inst/demos/table.isrow000066400000000000000000000005051522766574100223150ustar00rootroot00000000000000%!demo %! ## A table is a *row* when it has exactly one row of data, whatever the %! ## number of variables. %! %! T1 = table (38, true, 71, 'VariableNames', {'Age', 'Smoker', 'Height'}) %! isrow (T1) %! %! ## Add a second row and it is no longer a row vector. %! T2 = table ([38; 43], [true; false], [71; 69]) %! isrow (T2) pr0m1th3as-datatypes-9c9a8d3/inst/demos/table.issortedrows000066400000000000000000000007671522766574100237330ustar00rootroot00000000000000%!demo %! ## `issortedrows` tests whether the rows are already in sorted order, using the %! ## same key/direction syntax as `sortrows` — handy to skip a needless sort. %! %! T = table ([1; 3; 2], [10; 20; 30], 'VariableNames', {'Age', 'Height'}) %! issortedrows (T, 'Age') %! %! ## The `Height` column *is* ascending, so a check on that variable succeeds. %! issortedrows (T, 'Height') %! %! ## Directions are checked too: this asks whether `Age` is descending. %! issortedrows (T, 'Age', 'descend') pr0m1th3as-datatypes-9c9a8d3/inst/demos/table.isvector000066400000000000000000000006511522766574100230120ustar00rootroot00000000000000%!demo %! ## A table is a *vector* when it is a single row **or** a single variable — %! ## the table analogue of a 1-by-N or N-by-1 array. %! %! ## One variable, several rows: a column, hence a vector. %! isvector (table ([1; 2; 3])) %! %! ## One row, several variables: a row, hence a vector. %! isvector (table (1, 2, 3)) %! %! ## Multiple rows and multiple variables: not a vector. %! isvector (table ([1; 2], [3; 4])) pr0m1th3as-datatypes-9c9a8d3/inst/demos/table.join000066400000000000000000000012461522766574100221140ustar00rootroot00000000000000%!demo %! ## `join` performs a key-based merge: every row of the left table is matched to %! ## the one row of the right table sharing its key. The key is the variable the %! ## two tables have in common (`Name`), and it must be *unique* on the right. %! ## The remaining right-hand variables — here of several different types — are %! ## appended to each matched left row. %! %! L = table ({'Li'; 'Diaz'; 'Brown'}, [38; 40; 49], ... %! 'VariableNames', {'Name', 'Age'}) %! R = table ({'Li'; 'Diaz'; 'Brown'}, categorical ({'NY'; 'CA'; 'MA'}), ... %! logical ([1; 0; 1]), ... %! 'VariableNames', {'Name', 'State', 'Smoker'}) %! join (L, R) pr0m1th3as-datatypes-9c9a8d3/inst/demos/table.mergevars000066400000000000000000000006001522766574100231410ustar00rootroot00000000000000%!demo %! ## `mergevars` combines several variables into one multi-column variable — the %! ## inverse of `splitvars`. Name the merged variable with `'NewVariableName'`. %! %! T = table ([38; 43; 40], [124; 109; 125], [93; 77; 83], ... %! 'VariableNames', {'Age', 'Systolic', 'Diastolic'}) %! mergevars (T, {'Systolic', 'Diastolic'}, 'NewVariableName', 'BloodPressure') pr0m1th3as-datatypes-9c9a8d3/inst/demos/table.movevars000066400000000000000000000010241522766574100230110ustar00rootroot00000000000000%!demo %! ## `movevars` reorders variables, placing one (or several) `'Before'` or %! ## `'After'` another — the data is unchanged, only the column order shifts. %! %! Name = string ({'Sanchez'; 'Johnson'; 'Li'}); %! Age = [38; 43; 40]; %! Gender = categorical ({'M'; 'M'; 'F'}); %! Visit = datetime (2024, [1; 2; 3], [5; 6; 7]); %! T = table (Name, Age, Gender, Visit) %! movevars (T, 'Visit', 'After', 'Name') %! %! ## Move a variable to the front by placing it before the first one. %! movevars (T, 'Gender', 'Before', 'Name') pr0m1th3as-datatypes-9c9a8d3/inst/demos/table.numel000066400000000000000000000007501522766574100222740ustar00rootroot00000000000000%!demo %! ## For a table `numel` returns rows times variables — the number of table %! ## *cells* — not the count of underlying data elements. This 3-by-2 table %! ## therefore has 6 cells even though `BloodPressure` holds two columns. %! %! Age = [38; 43; 40]; %! Smoker = logical ([1; 0; 1]); %! BloodPressure = [124, 93; 109, 77; 117, 75]; %! T = table (Age, Smoker, BloodPressure) %! numel (T) %! %! ## It is simply the product of `height` and `width`. %! height (T) * width (T) pr0m1th3as-datatypes-9c9a8d3/inst/demos/table.outerjoin000066400000000000000000000012411522766574100231660ustar00rootroot00000000000000%!demo %! ## `outerjoin` keeps *all* rows from both tables, filling in missing values %! ## where a key has no match — each variable is filled with the missing value %! ## proper to its type (`NaN`, ``, empty string, ...). %! %! L = table ({'Li'; 'Diaz'; 'Brown'}, [38; 40; 49], ... %! 'VariableNames', {'Name', 'Age'}) %! R = table ({'Li'; 'Diaz'; 'Lee'}, categorical ({'NY'; 'CA'; 'TX'}), ... %! [176; 163; 150], ... %! 'VariableNames', {'Name', 'State', 'Weight'}) %! outerjoin (L, R, 'MergeKeys', true) %! %! ## Brown (left only) and Lee (right only) both survive, each with the other %! ## table's variables left missing. pr0m1th3as-datatypes-9c9a8d3/inst/demos/table.pivot000066400000000000000000000015101522766574100223100ustar00rootroot00000000000000%!demo %! ## `pivot` builds a cross-tabulation. With only `'Rows'` it counts the rows in %! ## each group — a one-way frequency table. %! %! Region = {'N'; 'N'; 'S'; 'S'; 'N'; 'S'}; %! Quarter = categorical ({'Q1'; 'Q2'; 'Q1'; 'Q2'; 'Q1'; 'Q2'}); %! Sales = [10; 20; 30; 40; 15; 25]; %! T = table (Region, Quarter, Sales) %! pivot (T, 'Rows', 'Region') %!demo %! ## Add `'Columns'` to spread a second grouping variable across the columns, and %! ## aggregate a data variable in each cell — here total `Sales` by region and %! ## quarter. %! %! Region = {'N'; 'N'; 'S'; 'S'; 'N'; 'S'}; %! Quarter = categorical ({'Q1'; 'Q2'; 'Q1'; 'Q2'; 'Q1'; 'Q2'}); %! Sales = [10; 20; 30; 40; 15; 25]; %! T = table (Region, Quarter, Sales) %! pivot (T, 'Rows', 'Region', 'Columns', 'Quarter', ... %! 'DataVariable', 'Sales', 'Method', 'sum') pr0m1th3as-datatypes-9c9a8d3/inst/demos/table.removevars000066400000000000000000000006521522766574100233460ustar00rootroot00000000000000%!demo %! ## `removevars` drops variables, selected by name or by position index. %! %! Name = string ({'Sanchez'; 'Johnson'; 'Li'}); %! Age = [38; 43; 40]; %! Gender = categorical ({'M'; 'M'; 'F'}); %! Visit = datetime (2024, [1; 2; 3], [5; 6; 7]); %! T = table (Name, Age, Gender, Visit) %! removevars (T, 'Gender') %! %! ## Several variables can go at once, here selected by their column numbers. %! removevars (T, [2, 4]) pr0m1th3as-datatypes-9c9a8d3/inst/demos/table.renamevars000066400000000000000000000006251522766574100233200ustar00rootroot00000000000000%!demo %! ## `renamevars` changes variable names in place, matching old names to new ones %! ## position by position. It touches only the names — the data, of whatever %! ## type, is untouched. %! %! Nm = string ({'Sanchez'; 'Johnson'; 'Li'}); %! Yrs = [38; 43; 40]; %! Sex = categorical ({'M'; 'M'; 'F'}); %! T = table (Nm, Yrs, Sex) %! renamevars (T, {'Nm', 'Yrs', 'Sex'}, {'Name', 'Age', 'Gender'}) pr0m1th3as-datatypes-9c9a8d3/inst/demos/table.repelem000066400000000000000000000006711522766574100226070ustar00rootroot00000000000000%!demo %! ## `repelem` repeats each row in place. With a scalar count every row is %! ## duplicated the same number of times, carrying all variable types along. %! %! Name = string ({'Li'; 'Diaz'; 'Brown'}); %! Grade = categorical ({'A'; 'B'; 'A'}); %! T = table (Name, Grade) %! repelem (T, 2, 1) %! %! ## A vector count sets the repeat count per row — here row 1 once, row 2 twice, %! ## row 3 three times. %! repelem (T, [1; 2; 3], 1) pr0m1th3as-datatypes-9c9a8d3/inst/demos/table.repmat000066400000000000000000000004501522766574100224410ustar00rootroot00000000000000%!demo %! ## `repmat` tiles a whole table, repeating the full block of rows. Contrast with %! ## `repelem`, which repeats each row in place: `repmat` copies the sequence. %! %! Name = string ({'Li'; 'Diaz'}); %! Visit = datetime (2024, 1, [5; 6]); %! T = table (Name, Visit) %! repmat (T, 3, 1) pr0m1th3as-datatypes-9c9a8d3/inst/demos/table.rmmissing000066400000000000000000000011311522766574100231560ustar00rootroot00000000000000%!demo %! ## `rmmissing` drops every row that has a missing value in any variable — %! ## listwise deletion — counting gaps of every type (`NaN`, `NaT`, ``, %! ## ``). Here the rows with a missing age, grade, or visit all go. %! %! Name = string ({'Li'; 'Diaz'; 'Brown'; 'Lee'}); %! Age = [38; NaN; 40; 49]; %! Grade = categorical ({'A'; 'B'; ''; 'C'}); %! Visit = datetime (2024, 1, [5; 6; 7; 8]); %! T = table (Name, Age, Grade, Visit) %! rmmissing (T) %! %! ## A second output is the logical mask of the rows that were removed. %! [C, removed] = rmmissing (T); %! removed' pr0m1th3as-datatypes-9c9a8d3/inst/demos/table.rmprop000066400000000000000000000012771522766574100225000ustar00rootroot00000000000000%!demo %! ## `rmprop` removes custom properties previously added with `addprop`, whatever %! ## their scope. Here a table starts with one property of each kind. %! %! Age = [38; 43]; %! Gender = categorical ({'M'; 'F'}); %! T = table (Age, Gender) %! T = addprop (T, {'DataSource', 'Instrument'}, {'table', 'variable'}); %! T.Properties.CustomProperties.DataSource = 'Clinic A'; %! T.Properties.CustomProperties.Instrument = {'form', 'form'}; %! T.Properties.CustomProperties %! %! ## Remove the variable-scoped `Instrument`; the table-scoped `DataSource` %! ## remains. The built-in properties (names, units, ...) are never affected. %! T = rmprop (T, 'Instrument'); %! T.Properties.CustomProperties pr0m1th3as-datatypes-9c9a8d3/inst/demos/table.rowfun000066400000000000000000000005101522766574100224660ustar00rootroot00000000000000%!demo %! ## `rowfun` applies a function to each row, passing that row's variables as %! ## separate arguments — a row-wise map, complementing the column-wise `varfun`. %! %! Weight = [176; 163; 133]; %! Height = [71; 69; 64]; %! T = table (Weight, Height) %! rowfun (@(w, h) 703 * w / h^2, T, 'OutputVariableNames', {'BMI'}) pr0m1th3as-datatypes-9c9a8d3/inst/demos/table.rows2vars000066400000000000000000000005661522766574100231310ustar00rootroot00000000000000%!demo %! ## `rows2vars` transposes a table: the original variables become rows and the %! ## original rows become variables. The old variable names move into a new %! ## leading variable, and the row names (if any) become the new headers. %! %! Age = [38; 43; 40]; %! Height = [71; 69; 64]; %! T = table (Age, Height, 'RowNames', {'Li', 'Diaz', 'Brown'}) %! rows2vars (T) pr0m1th3as-datatypes-9c9a8d3/inst/demos/table.setdiff000066400000000000000000000011561522766574100226010ustar00rootroot00000000000000%!demo %! ## `setdiff` returns the rows that are in the first table but not the second, %! ## comparing whole multi-variable rows across all their types. %! %! A = table ({'Li'; 'Diaz'; 'Brown'}, [38; 40; 49], ... %! categorical ({'NY'; 'CA'; 'MA'}), ... %! 'VariableNames', {'Name', 'Age', 'State'}) %! B = table ({'Diaz'; 'Brown'; 'Lee'}, [40; 49; 30], ... %! categorical ({'CA'; 'MA'; 'TX'}), ... %! 'VariableNames', {'Name', 'Age', 'State'}) %! setdiff (A, B) %! %! ## Only Li is unique to `A`; a second output gives its row index in `A`. %! [C, iA] = setdiff (A, B); %! iA pr0m1th3as-datatypes-9c9a8d3/inst/demos/table.setxor000066400000000000000000000011561522766574100225010ustar00rootroot00000000000000%!demo %! ## `setxor` returns the symmetric difference: rows in exactly one of the two %! ## tables, but not in both. Row identity spans every variable, of any type. %! %! A = table ({'Li'; 'Diaz'; 'Brown'}, [38; 40; 49], ... %! categorical ({'NY'; 'CA'; 'MA'}), ... %! 'VariableNames', {'Name', 'Age', 'State'}) %! B = table ({'Diaz'; 'Brown'; 'Lee'}, [40; 49; 30], ... %! categorical ({'CA'; 'MA'; 'TX'}), ... %! 'VariableNames', {'Name', 'Age', 'State'}) %! setxor (A, B) %! %! ## Diaz and Brown, shared by both, drop out — leaving Li (from `A`) and Lee %! ## (from `B`). pr0m1th3as-datatypes-9c9a8d3/inst/demos/table.size000066400000000000000000000007441522766574100221310ustar00rootroot00000000000000%!demo %! ## `size` reports `[height, width]` — rows by *variables*. A variable that %! ## spans several columns still counts as one, so the second element matches %! ## `width`, not the total number of underlying columns. %! %! Age = [38; 43; 38; 40; 49]; %! BloodPressure = [124, 93; 109, 77; 125, 83; 117, 75; 122, 80]; %! T = table (Age, BloodPressure) %! size (T) %! %! ## Query a single dimension, or capture both at once. %! nRows = size (T, 1) %! [nRows, nVars] = size (T) pr0m1th3as-datatypes-9c9a8d3/inst/demos/table.sortrows000066400000000000000000000017651522766574100230650ustar00rootroot00000000000000%!demo %! ## `sortrows` orders the rows by one or more variables of any comparable type. %! ## Name the variables to sort by; a trailing direction (or one per key) flips %! ## ascending to descending. Here an *ordinal* categorical `Grade` sets the %! ## primary order, with a `datetime` breaking ties. %! %! Name = string ({'Li'; 'Diaz'; 'Brown'; 'Lee'}); %! Visit = datetime (2024, [3; 1; 2; 1], [7; 5; 6; 2]); %! Grade = categorical ({'B'; 'A'; 'A'; 'B'}, {'A', 'B', 'C'}, 'Ordinal', true); %! T = table (Name, Visit, Grade) %! sortrows (T, {'Grade', 'Visit'}) %! %! ## Sort the same table by `Grade` descending instead. %! sortrows (T, 'Grade', 'descend') %!demo %! ## A second output returns the permutation index, and passing `'RowNames'` %! ## sorts by the row names themselves rather than by a data variable. %! %! Age = [38; 43; 38; 40]; %! Smoker = logical ([1; 0; 1; 0]); %! T = table (Age, Smoker, 'RowNames', {'Sanchez', 'Johnson', 'Li', 'Diaz'}) %! [S, idx] = sortrows (T, 'RowNames'); %! S %! idx pr0m1th3as-datatypes-9c9a8d3/inst/demos/table.splitapply000066400000000000000000000012021522766574100233460ustar00rootroot00000000000000%!demo %! ## `splitapply` divides a table by the group numbers from `findgroups`, applies %! ## a function to each group, and concatenates the results. Here it computes a %! ## mean petal length per species. %! %! Species = {'setosa'; 'virginica'; 'setosa'; 'virginica'; 'setosa'}; %! Petal = [1.4; 5.1; 1.5; 5.9; 1.3]; %! T = table (Species, Petal) %! G = findgroups (T(:, 'Species')); %! splitapply (@mean, T(:, 'Petal'), G) %! %! ## The applied function may return several outputs — one column each — and can %! ## read several variables at once. %! [lo, hi] = splitapply (@(x) deal (min (x), max (x)), T(:, 'Petal'), G); %! [lo, hi] pr0m1th3as-datatypes-9c9a8d3/inst/demos/table.splitvars000066400000000000000000000007571522766574100232120ustar00rootroot00000000000000%!demo %! ## `splitvars` expands a multi-column variable into several single-column %! ## variables — the inverse of `mergevars`. Auto-generated names get a numeric %! ## suffix; pass `'NewVariableNames'` to choose your own. %! %! BloodPressure = [124, 93; 109, 77; 125, 83]; %! T = table ([38; 43; 40], BloodPressure, 'VariableNames', {'Age', 'BP'}) %! splitvars (T, 'BP') %! %! ## Name the resulting columns explicitly. %! splitvars (T, 'BP', 'NewVariableNames', {'Systolic', 'Diastolic'}) pr0m1th3as-datatypes-9c9a8d3/inst/demos/table.stack000066400000000000000000000011401522766574100222530ustar00rootroot00000000000000%!demo %! ## `stack` reshapes wide data to tall: it stacks several data variables into a %! ## single variable and adds an indicator recording which one each value came %! ## from. Here the monthly columns collapse into one `Value` column. %! %! Name = {'Li'; 'Diaz'}; %! Jan = [1; 2]; %! Feb = [3; 4]; %! T = table (Name, Jan, Feb) %! stack (T, {'Jan', 'Feb'}, ... %! 'NewDataVariableName', 'Value', 'IndexVariableName', 'Month') %! %! ## The unstacked variables (`Name`) are repeated down each group; the indicator %! ## `Month` is a categorical naming the source column. `unstack` reverses this. pr0m1th3as-datatypes-9c9a8d3/inst/demos/table.standardizeMissing000066400000000000000000000007251522766574100250200ustar00rootroot00000000000000%!demo %! ## `standardizeMissing` turns your own sentinel codes into the *standard* %! ## missing value for each type (`NaN` here), so later `ismissing`/`rmmissing`/ %! ## `fillmissing` calls recognise them. This dataset uses `-99` for "no data". %! %! T = table ([38; -99; 40], [71; 69; -99], 'VariableNames', {'Age', 'Height'}) %! S = standardizeMissing (T, -99) %! %! ## The sentinels are now genuine missing values, ready to be filled or dropped. %! rmmissing (S) pr0m1th3as-datatypes-9c9a8d3/inst/demos/table.summary000066400000000000000000000007201522766574100226460ustar00rootroot00000000000000%!demo %! ## Create a Table from patient data %! %! load patients %! BloodPressure = [Systolic Diastolic]; %! T = table (Gender, Age, Smoker, BloodPressure, 'RowNames', LastName); %! %! ## Add descriptions and units to table %! %! T.Properties.Description = 'Simulated patient data'; %! T.Properties.VariableUnits = {'', 'Yrs', '', 'mm Hg'}; %! T.Properties.VariableDescriptions(4) = {'Systolic/Diastolic'}; %! %! ## Print a summary of the table %! %! summary (T) pr0m1th3as-datatypes-9c9a8d3/inst/demos/table.table000066400000000000000000000101501522766574100222360ustar00rootroot00000000000000%!demo %! ## Preallocate a table by specifying its size and the variable data types %! %! sz = [4, 3]; %! varTypes = {'double', 'datetime', 'string'}; %! T = table ('Size', sz, 'VariableTypes', varTypes) %!demo %! ## Specify variable names with the `VariableNames` name-value pair argument %! sz = [4, 3]; %! varTypes = {'double', 'datetime', 'string'}; %! varNames = {'Temperature', 'Time', 'Station'}; %! T2 = table ('Size', sz, 'VariableTypes', varTypes, 'VariableNames', varNames) %! %! ## Add rows of data to the first two rows of table T2 %! T2(1,:) = {75, datetime(2024, 2, 5), string('S1')}; %! T2(2,:) = {75, datetime(2024, 2, 6), string('S2')} %!demo %! ## Create a table from various types of arrays %! %! T = table (string ({'M';'F';'M'}), [45;32;34], ... %! {'NY';'CA';'MA'}, logical ([1;0;0]), ... %! 'VariableNames', {'Gender', 'Age', 'State', 'Vote'}) %!demo %! ## Create the same table using the state names as row names %! %! T = table (string ({'M';'F';'M'}), [45;32;34], logical ([1;0;0]), ... %! 'VariableNames', {'Gender', 'Age', 'Vote'}, ... %! 'RowNames', {'NY';'CA';'MA'}) %!demo %! ## Display a table with mixed cell arrays as unicolumnar variables %! ## and other types as multicolumnar variables %! %! Data_A = {[34, 32]; ['text';'picture']; 'text'; struct('c', 'data'); ... %! [true, false]; ['some','text']; {'some','text'}; 25.34}; %! Data_B = {32, 25; 0.2, 135; 0.123, 456; 42, 5; 154, 12; 32, 10; 4, 4; 9, 94}; %! Data_C = datetime (2000, [1:8;9:16]', 1); %! %! T = table (Data_A, Data_B, Data_C) %!demo %! ## Create a nested table %! T1 = table ([1; 2; 3], [4; 5; 6], [7; 8; 9]) %! T2 = table ({'a'; 'b'; 'c'}, {'d'; 'e'; 'f'}, {'g'; 'h'; 'i'}) %! NT = table ([1; 2; 3], T1, [4; 5; 6], T2, {5; 6; 7}, ... %! 'VariableNames', {'A', 'B', 'C', 'D', 'E'}) %!demo %! ## A table is *read* with three complementary indexing styles. Build a table %! ## whose variables span several types to see how each one behaves. %! %! Name = string ({'Sanchez'; 'Johnson'; 'Li'; 'Diaz'}); %! Age = [38; 43; 38; 40]; %! Visit = datetime (2024, [1; 2; 3; 4], [5; 6; 7; 8]); %! Gender = categorical ({'M'; 'M'; 'F'; 'F'}); %! T = table (Name, Age, Visit, Gender) %! %! ## Dot indexing extracts one variable as its own native type. %! T.Visit %! %! ## Parenthesis indexing selects a sub-table — the result is still a table. %! T(1:2, {'Name', 'Age'}) %! %! ## Brace indexing pulls the raw contents out of the selected variables. %! T{1:2, 'Age'} %!demo %! ## Assignment mirrors the three styles. Starting from a mixed-type table: %! %! Name = string ({'Sanchez'; 'Johnson'; 'Li'}); %! Age = [38; 43; 38]; %! Gender = categorical ({'M'; 'M'; 'F'}); %! T = table (Name, Age, Gender) %! %! ## Dot assignment adds or overwrites a whole variable (here computed from Age). %! T.Senior = T.Age >= 40 %! %! ## Parenthesis assignment with a cell sets a sub-table; a new row index grows %! ## the table, taking one cell per variable in variable order. %! T(4, :) = {string('Diaz'), 40, categorical({'F'}), true} %! %! ## Dot-then-index writes into part of an existing variable. %! T.Age(1) = 39; %! T.Age' %!demo %! ## This implementation fully supports **chained** referencing and assignment — %! ## indexing that reaches through several levels in a single expression. (Octave %! ## extension: MATLAB permits chained *reference* but not chained *assignment*.) %! %! Age = [38; 43; 38; 40]; %! Height = [71; 69; 64; 67]; %! T = table (Age, Height) %! %! ## Chained reference: select a sub-table, then a variable, then an element. %! T(2:3, :).Age(1) %! %! ## Chained assignment writes straight through that same path. %! T(2:3, :).Age(1) = 99; %! T.Age' %!demo %! ## Chaining is most useful with nested tables, reaching an inner variable %! ## directly — again both for reading and, unlike MATLAB, for writing. %! %! Inner = table ([1; 2; 3], [4; 5; 6], 'VariableNames', {'p', 'q'}) %! NT = table ([10; 20; 30], Inner, 'VariableNames', {'id', 'inner'}) %! %! ## Read the 2nd element of inner variable q. %! NT.inner.q(2) %! %! ## Assign straight into it in one chained expression. %! NT.inner.q(2) = 55; %! NT.inner pr0m1th3as-datatypes-9c9a8d3/inst/demos/table.table2array000066400000000000000000000007771522766574100233750ustar00rootroot00000000000000%!demo %! ## `table2array` horizontally concatenates the variables into a single array, %! ## so it is meant for tables whose variables share a common type. Multi-column %! ## variables keep all their columns, so the result can be wider than the table. %! %! Age = [38; 43; 40]; %! BloodPressure = [124, 93; 109, 77; 117, 75]; %! T = table (Age, BloodPressure) %! A = table2array (T) %! %! ## The 3-by-2 table becomes a 3-by-3 numeric matrix, because `BloodPressure` %! ## contributes two columns. %! size (A) pr0m1th3as-datatypes-9c9a8d3/inst/demos/table.table2cell000066400000000000000000000007451522766574100231710ustar00rootroot00000000000000%!demo %! ## `table2cell` unpacks a table into a cell array with one cell per element, %! ## preserving heterogeneous variable types that `table2array` could not merge. %! %! LastName = {'Sanchez'; 'Johnson'; 'Li'}; %! Age = [38; 43; 38]; %! Smoker = logical ([1; 0; 1]); %! T = table (LastName, Age, Smoker) %! C = table2cell (T) %! %! ## Each column of the cell array corresponds to one table variable, so the %! ## result is independent of the original variable types. %! class (C) pr0m1th3as-datatypes-9c9a8d3/inst/demos/table.table2csv000066400000000000000000000011661522766574100230430ustar00rootroot00000000000000%!demo %! ## `table2csv` writes a table to CSV in the package's round-trippable format: %! ## a metadata comment line, then header rows for the variable types, names, %! ## and units, followed by one row of data per table row. %! %! Age = [38; 43; 40]; %! Height = [71; 69; 64]; %! T = table (Age, Height) %! T.Properties.VariableUnits = {'Yrs', 'in'}; %! %! filename = fullfile (tempdir (), 'patients.csv'); %! table2csv (T, filename); %! type (filename) %! %! ## Because the type and unit metadata is preserved in the header, `csv2table` %! ## reconstructs the table faithfully. %! csv2table (filename) %! %! delete (filename); pr0m1th3as-datatypes-9c9a8d3/inst/demos/table.table2ods000066400000000000000000000014611522766574100230330ustar00rootroot00000000000000%!demo %! ## `table2ods` writes a table to an OpenDocument spreadsheet, one natively %! ## typed cell per value. The data lands on `Sheet1` by default. %! %! Age = [38; 43; 40]; %! Height = [71; 69; 64]; %! T = table (Age, Height) %! filename = fullfile (tempdir (), 'patients.ods'); %! table2ods (T, filename); %! ods2table (filename) %!demo %! ## Writing to a named `'Sheet'` of an existing workbook adds or replaces just %! ## that sheet, leaving the others intact — so several tables can share one file. %! %! filename = fullfile (tempdir (), 'workbook.ods'); %! table2ods (table ([38; 43], 'VariableNames', {'Age'}), filename, 'Sheet', 'Patients'); %! table2ods (table ([1; 2; 3], 'VariableNames', {'Visit'}), filename, 'Sheet', 'Visits'); %! ods2table (filename, 'Sheet', 'Visits') %! %! delete (filename); pr0m1th3as-datatypes-9c9a8d3/inst/demos/table.table2struct000066400000000000000000000013771522766574100236000ustar00rootroot00000000000000%!demo %! ## By default `table2struct` returns a **struct array**, one element per row, %! ## with one field per variable — handy for row-wise iteration. %! %! LastName = {'Sanchez'; 'Johnson'; 'Li'}; %! Age = [38; 43; 38]; %! Height = [71; 69; 64]; %! T = table (LastName, Age, Height) %! S = table2struct (T); %! S(2).LastName %! S(2).Age %!demo %! ## Pass `'ToScalar', true` to instead get a **scalar struct** whose fields are %! ## whole columns — the layout used by `struct2table` with `'AsArray', true`. %! %! LastName = {'Sanchez'; 'Johnson'; 'Li'}; %! Age = [38; 43; 38]; %! Height = [71; 69; 64]; %! T = table (LastName, Age, Height) %! S = table2struct (T, 'ToScalar', true) %! %! ## Now each field holds every value of that variable at once. %! S.Age pr0m1th3as-datatypes-9c9a8d3/inst/demos/table.tail000066400000000000000000000007471522766574100221130ustar00rootroot00000000000000%!demo %! ## `tail` mirrors `head`, previewing the *bottom* of a table. With no count it %! ## returns the last 8 rows (or fewer if the table is short). %! %! n = (1:20)'; %! T = table (string (num2str (n, 'ID%02d')), datetime (2024, 1, n), ... %! categorical (mod (n, 3), 0:2, {'low', 'mid', 'hi'}), n .^ 2, ... %! 'VariableNames', {'Tag', 'Date', 'Band', 'Score'}); %! tail (T) %! %! ## Pass a count to ask for a specific number of trailing rows. %! tail (T, 3) pr0m1th3as-datatypes-9c9a8d3/inst/demos/table.topkrows000066400000000000000000000011221522766574100230360ustar00rootroot00000000000000%!demo %! ## `topkrows` returns the top `k` rows by a sort key without sorting the whole %! ## table — like `sortrows` followed by `head`, but stated in one call. The %! ## default direction is descending, so the largest values come first. %! %! LastName = {'Sanchez'; 'Johnson'; 'Li'; 'Diaz'; 'Brown'}; %! Age = [38; 43; 38; 40; 49]; %! T = table (Age, 'RowNames', LastName) %! topkrows (T, 3, 'Age') %! %! ## Add a direction to take the *smallest* instead, and capture the index of %! ## the selected rows in the original table. %! [B, idx] = topkrows (T, 2, 'Age', 'ascend'); %! B %! idx pr0m1th3as-datatypes-9c9a8d3/inst/demos/table.union000066400000000000000000000013351522766574100223040ustar00rootroot00000000000000%!demo %! ## `union` returns the rows present in either table, with duplicates removed — %! ## set arithmetic over *whole rows*, comparing all variables together. Both %! ## tables must share the same variables; here each row spans three types. %! %! A = table ({'Li'; 'Diaz'; 'Brown'}, [38; 40; 49], ... %! categorical ({'NY'; 'CA'; 'MA'}), ... %! 'VariableNames', {'Name', 'Age', 'State'}) %! B = table ({'Diaz'; 'Brown'; 'Lee'}, [40; 49; 30], ... %! categorical ({'CA'; 'MA'; 'TX'}), ... %! 'VariableNames', {'Name', 'Age', 'State'}) %! union (A, B) %! %! ## Diaz and Brown match on every variable, so they appear once; the result has %! ## four distinct rows out of the six inputs. pr0m1th3as-datatypes-9c9a8d3/inst/demos/table.unique000066400000000000000000000010721522766574100224600ustar00rootroot00000000000000%!demo %! ## `unique` removes duplicate rows, comparing whole rows across all variables %! ## regardless of their types. By default the surviving rows come back sorted. %! %! Gender = categorical ({'M'; 'F'; 'M'; 'F'; 'M'}); %! State = string ({'NY'; 'CA'; 'NY'; 'CA'; 'NY'}); %! Visits = [1; 2; 1; 3; 1]; %! T = table (Gender, State, Visits) %! unique (T) %! %! ## Use `'stable'` to keep the rows in their order of first appearance instead, %! ## and capture the index vectors relating the inputs to the unique rows. %! [U, ia, ic] = unique (T, 'stable'); %! U %! ic' pr0m1th3as-datatypes-9c9a8d3/inst/demos/table.unstack000066400000000000000000000010031522766574100226140ustar00rootroot00000000000000%!demo %! ## `unstack` reshapes tall data to wide — the inverse of `stack`. It spreads %! ## the values of one data variable across new columns, one per level of an %! ## indicator variable, grouping by the remaining variables. %! %! Name = categorical ({'Li'; 'Li'; 'Diaz'; 'Diaz'}); %! Month = categorical ({'Jan'; 'Feb'; 'Jan'; 'Feb'}); %! Value = [1; 3; 2; 4]; %! T = table (Name, Month, Value) %! unstack (T, 'Value', 'Month') %! %! ## Each distinct `Month` becomes its own column, with one row per `Name`. pr0m1th3as-datatypes-9c9a8d3/inst/demos/table.varfun000066400000000000000000000006141522766574100224540ustar00rootroot00000000000000%!demo %! ## `varfun` applies a function to each variable of a table — a column-wise map. %! ## The result is itself a table, its variable names prefixed by the function. %! %! T = table ([38; 43; 40], [71; 69; 64], 'VariableNames', {'Age', 'Height'}) %! varfun (@mean, T) %! %! ## Ask for a plain array instead of a table with `'OutputFormat'`. %! varfun (@mean, T, 'OutputFormat', 'uniform') pr0m1th3as-datatypes-9c9a8d3/inst/demos/table.vertcat000066400000000000000000000025321522766574100226240ustar00rootroot00000000000000%!demo %! ## `vertcat` stacks tables on top of one another, appending rows. The tables %! ## must share the same variables (same names, compatible types). The `[A; B]` %! ## bracket syntax calls it for you. %! %! A = table (string ({'Li'; 'Diaz'}), [38; 40], categorical ({'M'; 'F'}), ... %! 'VariableNames', {'Name', 'Age', 'Gender'}) %! B = table (string ({'Brown'; 'Lee'}), [49; 30], categorical ({'M'; 'M'}), ... %! 'VariableNames', {'Name', 'Age', 'Gender'}) %! [A; B] %!demo %! ## Because vertical concatenation only adds rows, the column-aligned metadata is %! ## unchanged: a *variable*-scoped custom property carries straight through, and a %! ## *table*-scoped one is kept when the operands agree. %! %! A = table ([38; 40], categorical ({'M'; 'F'}), 'VariableNames', {'Age', 'Gender'}) %! A = addprop (A, {'Study', 'Source'}, {'table', 'variable'}); %! A.Properties.CustomProperties.Study = 'Cohort-7'; %! A.Properties.CustomProperties.Source = {'intake', 'intake'}; %! %! B = table ([49; 30], categorical ({'M'; 'M'}), 'VariableNames', {'Age', 'Gender'}) %! B = addprop (B, {'Study', 'Source'}, {'table', 'variable'}); %! B.Properties.CustomProperties.Study = 'Cohort-7'; %! B.Properties.CustomProperties.Source = {'intake', 'intake'}; %! %! V = [A; B]; %! V.Properties.CustomProperties.Source %! V.Properties.CustomProperties.Study pr0m1th3as-datatypes-9c9a8d3/inst/demos/table.width000066400000000000000000000007171522766574100222760ustar00rootroot00000000000000%!demo %! ## `width` counts *variables*, not the underlying columns. `BloodPressure` %! ## is a single variable spanning two columns, so the width is 2, not 3. %! %! Age = [38; 43; 38; 40; 49]; %! Smoker = logical ([1; 0; 1; 0; 1]); %! BloodPressure = [124, 93; 109, 77; 125, 83; 117, 75; 122, 80]; %! T = table (Age, Smoker, BloodPressure) %! width (T) %! %! ## Splitting the multi-column variable into scalar columns raises the width. %! width (splitvars (T)) pr0m1th3as-datatypes-9c9a8d3/inst/demos/table.writetable000066400000000000000000000017711522766574100233220ustar00rootroot00000000000000%!demo %! ## `writetable` writes a table in a **MATLAB-interoperable** format — a plain %! ## header row of variable names followed by the data, with no package-specific %! ## metadata. The file type follows the extension; `'Delimiter'` sets the %! ## separator for text files. %! %! T = table ([38; 43; 40], [71; 69; 64], 'VariableNames', {'Age', 'Height'}) %! filename = fullfile (tempdir (), 'patients.txt'); %! writetable (T, filename, 'Delimiter', 'tab'); %! type (filename) %! %! delete (filename); %!demo %! ## For spreadsheets, choose the target `'Sheet'` and, with `'WriteRowNames'`, %! ## emit the row names as the leading column — `readtable` reads it straight back. %! %! T = table ([38; 43], [71; 69], 'VariableNames', {'Age', 'Height'}, ... %! 'RowNames', {'Li', 'Diaz'}) %! filename = fullfile (tempdir (), 'patients.ods'); %! writetable (T, filename, 'Sheet', 'Cohort', 'WriteRowNames', true); %! readtable (filename, 'Sheet', 'Cohort', 'ReadRowNames', true) %! %! delete (filename); pr0m1th3as-datatypes-9c9a8d3/inst/duration.m000066400000000000000000005445611522766574100210540ustar00rootroot00000000000000## Copyright (C) 2024-2026 Andreas Bertsatos ## ## This file is part of the datatypes package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . classdef duration ## -*- texinfo -*- ## @deftp {datatypes} duration ## ## Array representing durations of time using fixed-length time units. ## ## @qcode{duration} values are stored internally as @qcode{double} type array ## representing numbers of elapsed days as a fixed-length time unit. By ## default, fractional seconds of duration values are not displayed, but their ## actual precision is closer to nanoseconds for typical time lengths. ## ## @code{duration} arrays can be created through their constructor by ## combining numeric arrays representing individual fixed-length elapsed time ## units or through the functions @code{years}, @code{days}, @code{hours}, ## @code{minutes}, @code{seconds}, and @code{calyears}, which create ## fixed-length durations in terms of a single duration units. These ## functions are also available as methods of @code{duration} arrays to ## extract individual duration units as numeric arrays. ## ## @seealso{calendarDuration, datetime} ## @end deftp properties ## -*- texinfo -*- ## @deftp {duration} {property} Format ## ## Display format ## ## Display format, specified as a character vector or string scalar. If ## specified as a string scalar, it is converted and stored internally as ## a character vector. ## ## @end deftp Format = 'hh:mm:ss' endproperties properties (SetAccess = private, Hidden) ## Duration length in days Days = 0 endproperties methods (Hidden) ## Custom display function display (this) in_name = inputname (1); if (! isempty (in_name)) fprintf ('%s =\n', in_name); endif __disp__ (this, 'duration', in_name); endfunction ## Custom display function disp (this) __disp__ (this, 'duration'); endfunction endmethods ################################################################################ ## ** Create and convert 'duration' type ** ## ################################################################################ ## Available Methods ## ## ## ## 'duration' 'dispstrings' 'cellstr' 'char' ## ## 'datevec' 'hms' 'years' 'days' ## ## 'hours' 'minutes' 'seconds' 'milliseconds' ## ## ## ################################################################################ methods (Access = public) ## -*- texinfo -*- ## @deftypefn {duration} {@var{D} =} duration (@var{X}) ## @deftypefnx {duration} {@var{D} =} duration (@var{H}, @var{MI}, @var{S}) ## @deftypefnx {duration} {@var{D} =} duration (@var{H}, @var{MI}, @var{S}, @var{MS}) ## @deftypefnx {duration} {@var{D} =} duration (@var{TimeStrings}) ## @deftypefnx {duration} {@var{D} =} duration (@var{TimeStrings}, @qcode{'InputFormat'}, @var{INFMT}) ## @deftypefnx {duration} {@var{D} =} duration (@dots{}, @qcode{'Format'}, @var{FMT}) ## ## Create a new array of fixed-length time durations. ## ## @code{@var{D} = duration (@var{X})} creates a column vector of durations ## from a numeric matrix. ## ## @code{@var{D} = duration (@var{H}, @var{MI}, @var{S})} creates a duration ## array from numeric arrays containing the number of hours, minutes, and ## seconds specified by @var{H}, @var{MI} and @var{S}, respectively. ## ## @code{@var{D} = duration (@var{H}, @var{MI}, @var{S}, @var{MS})} creates ## a duration array from numeric arrays containing the number of hours, ## minutes, seconds, and milliseconds specified by @var{H}, @var{MI}, ## @var{S}, and @var{MS}, respectively. ## ## @code{@var{D} = duration (@var{TimeStrings})} creates a duration array ## from text that represents elapsed times. @var{TimeStrings} can be a ## character vector, a cell array of character vectors, or a string array ## representing times using either the @qcode{'hh:mm:ss'} or the ## @qcode{'dd:hh:mm:ss'} format. ## ## @code{@var{D} = duration (@var{TimeStrings}, @qcode{'InputFormat'}, ## @var{INFMT})} creates a duration array from text that represents elapsed ## times according to the format specified by @var{INFMT}, which can be any ## of the following: ## ## @itemize ## @item @qcode{'dd:hh:mm:ss'} ## @item @qcode{'hh:mm:ss'} ## @item @qcode{'mm:ss'} ## @item @qcode{'hh:mm'} ## @item Any of the first three formats can also be appended with up to nine ## @qcode{S} characters to indicate fractional second digits, such as ## @qcode{'dd:hh:mm:ss.SS'} or @qcode{'mm:ss.SS'}. ## @end itemize ## ## @code{@var{D} = duration (@dots{}, @qcode{'Format'}, @var{FMT})} ## specifies the format in which @var{D} is displayed. @var{FMT} can ## specify either a digital timer, which can have any of the valid formats ## for @qcode{'InputFormat'} as shown above or a single number with time ## units by specifying one of the following: ## ## @itemize ## @item @qcode{'y'} fixed-length years (1 year equals 365.2425 days) ## @item @qcode{'d'} fixed-length days (1 day equals 24 hours) ## @item @qcode{'h'} hours ## @item @qcode{'m'} minutes ## @item @qcode{'s'} seconds ## @end itemize ## ## @code{@var{D} = duration ()} returns a scalar array of durations with an ## elapsed time value of zero. To create an empty duration array, use ## @code{duration ([], [], [])}. ## ## @seealso{years, days, hours, minutes, seconds, milliseconds, duration, ## isduration, calendarDuration, datetime} ## @end deftypefn function this = duration (varargin) ## Return a scalar duration object if (nargin == 0) return endif ## Parse optional Name-Value paired arguments optNames = {'Format', 'InputFormat'}; dfValues = {[], []}; [Format, inputFormat, args] = ... parsePairedArguments (optNames, dfValues, varargin(:)); ## Check optional 'Format' and 'InputFormat' arguments if (! isempty (Format)) ## Convert string to character vector if necessary if (isstring (Format)) if (! isscalar (Format)) error (strcat ("duration: 'Format' must be a character", ... " vector or a string scalar.")); endif Format = char (Format); elseif (! (ischar (Format) && isrow (Format))) error (strcat ("duration: 'Format' must be a character", ... " vector or a string scalar.")); endif errmsg = checkFormatString (Format); if (! isempty (errmsg)) error ("duration: %s", errmsg); endif this.Format = Format; endif if (! isempty (inputFormat)) ## Convert string to character vector if necessary if (isstring (inputFormat)) if (! isscalar (inputFormat)) error (strcat ("duration: 'InputFormat' must be a", ... " character vector or a string scalar.")); endif inputFormat = char (inputFormat); elseif (! (ischar (inputFormat) && isrow (inputFormat))) error (strcat ("duration: 'InputFormat' must be a character", ... " vector or a string scalar.")); endif errmsg = checkInputFormatString (inputFormat); if (! isempty (errmsg)) error ("duration: %s", errmsg); endif endif ## Parse inputs switch (numel (args)) ## this = duration () case 0 return ## this = duration (X) case 1 X = args{1}; if (isnumeric (X)) if (! ismatrix (X)) error ("duration: numeric X must be a matrix."); endif if (! isreal (X)) error ("duration: numeric X must be real."); endif if (size (X, 2) == 3) H = X(:,1); MI = X(:,2); S = X(:,3); else error ("duration: X must have 3 columns."); endif [~, this.Days] = hms2days (H, MI, S); ## Return a warning if InputFormat is defined if (! isempty (inputFormat)) warning (strcat ("duration: 'InputFormat' has no effect", ... " on numeric data.")); endif elseif (iscellstr (X) || ischar (X) || isa (X, "string")) if (! iscellstr (X)) X = cellstr (X); endif this.Days = timestrings2days (X, inputFormat); else error ("duration: invalid type of single input data argument."); endif ## this = duration (H, MI, S) case 3 [H, MI, S] = args{:}; if (! (isnumeric (H) && isnumeric (MI) && isnumeric (S))) error ("duration: H, MI, and S must be numeric arrays."); endif if (! (isreal (H) && isreal (MI) && isreal (S))) error ("duration: H, MI, and S must be real."); endif ## Expansion is handled by the helper function [err, days] = hms2days (H, MI, S); if (err > 0) error ("duration: H, MI, and S must be of common size or scalars."); endif this.Days = days; ## Return a warning if InputFormat is defined if (! isempty (inputFormat)) warning ("duration: 'InputFormat' has no effect on numeric data."); endif ## this = duration (H, MI, S, MS) case 4 [H, MI, S, MS] = args{:}; if (! (isnumeric (H) && isnumeric (MI) && isnumeric (S) && ... isnumeric (MS))) error ("duration: H, MI, S, and MS must be numeric arrays."); endif if (! (isreal (H) && isreal (MI) && isreal (S) && isreal (MS))) error ("duration: H, MI, S, and MS must be real."); endif ## Expansion is handled by the helper function [err, days] = hms2days (H, MI, S, MS); if (err > 0) error (strcat ("duration: H, MI, S, and MS must be of", ... " common size or scalars.")); endif this.Days = days; ## Return a warning if InputFormat is defined if (! isempty (inputFormat)) warning ("duration: 'InputFormat' has no effect on numeric data."); endif otherwise error ("duration: invalid number of input arguments."); endswitch endfunction ## -*- texinfo -*- ## @deftypefn {duration} {@var{cstr} =} dispstrings (@var{D}) ## ## Get display formatted strings for each element of a duration array. ## ## @code{@var{cstr} = dispstrings (@var{D})} returns a cellstr array of ## character vectors, @var{cstr}, which has the same size as the input ## duration @var{D}. ## ## @end deftypefn function cstr = dispstrings (this) ## Get display format fmt = strsplit (this.Format, '.')'; if (numel (fmt) == 1) fmt = fmt{1}; fracSec = false; else fracSec = true; fdigits = numel (fmt{2}); pat = sprintf ('.%%0%dd', fdigits); fmt = fmt{1}; endif ## Process all elements sz = size (this); cstr = cell (sz); for i = 1:prod (sz) d = this.Days(i); ## Handle NaNs and Infs early if (! isfinite (d)) switch (fmt) case 'y' cstr{i} = sprintf ('%g yrs', d); case 'd' cstr{i} = sprintf ('%g days', d); case 'h' cstr{i} = sprintf ('%g hr', d); case 'm' cstr{i} = sprintf ('%g min', d); case 's' cstr{i} = sprintf ('%g sec', d); otherwise cstr{i} = sprintf ('%g', d); endswitch else ## Get sign for positive/negative duration str = ''; if (d < 0) str = [str, '-']; d = abs (d); endif ## Build string according to display format if (strcmp (fmt, 'y')) years = d / 365.2425; if (years == 1) str = [str, sprintf('%g yr', years)]; else str = [str, sprintf('%g yrs', years)]; endif elseif (strcmp (fmt, 'd')) if (d == 1) str = [str, sprintf('%g day', d)]; else str = [str, sprintf('%g days', d)]; endif elseif (strcmp (fmt, 'h')) str = [str, sprintf('%g hr', d * 24)]; elseif (strcmp (fmt, 'm')) str = [str, sprintf('%g min', d * 1440)]; elseif (strcmp (fmt, 's')) str = [str, sprintf('%g sec', d * 86400)]; elseif (strcmp (fmt, 'hh:mm')) ## Convert to minutes x = d * 1440; ## Calculate hours tmp = x / 60; h = fix (tmp); ## Fix round-off errors with threshold scaled by hours if (abs (tmp - h - 1) < 1e-13 * (1 + h)) h++; endif ## Calculate remaining minutes tmp = x - h * 60; m = fix (tmp); ## Fix round-off errors with threshold scaled by minutes if (abs (tmp - m - 1) < 1e-13 * (1 + m)) m++; endif str = [str, sprintf('%02d:%02d', h, m)]; elseif (strcmp (fmt, 'mm:ss')) ## Convert to seconds x = d * 86400; ## Calculate minutes tmp = x / 60; m = fix (tmp); ## Fix round-off errors with threshold scaled by minutes if (abs (tmp - m - 1) < 1e-13 * (1 + m)) m++; endif ## Calculate remaining seconds tmp = x - m * 60; s = fix (tmp); ## Fix round-off errors with threshold scaled by seconds if (abs (tmp - s - 1) < 1e-12 * (1 + s)) s++; endif str = [str, sprintf('%02d:%02d', m, s)]; if (fracSec) fs = rem (x, 60) - s; # fraction of a second ## Promote to integer value according to requested digits ms = abs (fix (fs * 10 ^ fdigits)); str = [str, sprintf(pat, ms)]; endif elseif (strcmp (fmt, 'hh:mm:ss')) ## Convert to seconds x = d * 86400; ## Calculate hours tmp = x / 3600; h = fix (tmp); ## Fix round-off errors with threshold scaled by hours if (abs (tmp - h - 1) < 1e-13 * (1 + h)) h++; endif ## Calculate remaining duration in seconds x = x - h * 3600; ## Calculate remaining minutes tmp = x / 60; m = fix (tmp); ## Fix round-off errors with threshold scaled by minutes if (abs (tmp - m - 1) < 1e-13 * (1 + m)) m++; endif ## Calculate remaining seconds x = x - m * 60; s = fix (x); ## Fix round-off errors with threshold scaled by seconds if (abs (x - s - 1) < 1e-12 * (1 + s)) s++; endif str = [str, sprintf('%02d:%02d:%02d', h, m, s)]; if (fracSec) fs = x - s; # fraction of a second ## Round to nearest nanosecond fs = round (fs * 1e+9) * 1e-9; ## Promote to integer value according to requested digits ms = floor (fs * 10 ^ fdigits); str = [str, sprintf(pat, ms)]; endif elseif (strcmp (fmt, 'dd:hh:mm:ss')) ## Calculate days tmp = fix (d); ## Fix round-off errors with threshold scaled by days if (abs (d - tmp - 1) < 1e-15 * (1 + d)) tmp++; endif x = d - tmp; d = tmp; ## Calculate remaining duration in seconds x = x * 86400; ## Calculate hours tmp = x / 3600; h = fix (tmp); ## Fix round-off errors with threshold scaled by hours if (abs (tmp - h - 1) < 1e-13 * (1 + h)) h++; endif ## Calculate remaining duration in seconds x = x - h * 3600; ## Calculate remaining minutes tmp = x / 60; m = fix (tmp); ## Fix round-off errors with threshold scaled by minutes if (abs (tmp - m - 1) < 1e-13 * (1 + m)) m++; endif ## Calculate remaining seconds x = x - m * 60; s = fix (x); ## Fix round-off errors with threshold scaled by seconds if (abs (x - s - 1) < 1e-12 * (1 + s)) s++; endif if (fix (d) > 0) str = [str, sprintf('%02d:%02d:%02d:%02d', d, h, m, s)]; else str = [str, sprintf('%02d:%02d:%02d', h, m, s)]; endif if (fracSec) fs = x - s; # fraction of a second ## Round to nearest nanosecond fs = round (fs * 1e+9) * 1e-9; ## Promote to integer value according to requested digits ms = floor (fs * 10 ^ fdigits); str = [str, sprintf(pat, ms)]; endif endif cstr{i} = str; endif endfor endfunction ## -*- texinfo -*- ## @deftypefn {duration} {@var{cstr} =} cellstr (@var{D}) ## @deftypefnx {duration} {@var{cstr} =} cellstr (@var{D}, @var{Format}) ## ## Convert duration array to a cell array of character vectors. ## ## @code{@var{cstr} = cellstr (@var{D})} returns a cellstr array of ## character vectors, @var{cstr}, which has the same size as the input ## duration @var{D}. ## ## @end deftypefn function cstr = cellstr (this, Format = '') if (! isempty (Format)) ## Convert string to character vector if necessary if (isstring (Format)) if (! isscalar (Format)) error (strcat ("duration.cellstr: FORMAT must be a", ... " character vector or a string scalar.")); endif Format = char (Format); elseif (! (ischar (Format) && isrow (Format))) error (strcat ("duration.cellstr: FORMAT must be a", ... " character vector or a string scalar.")); endif errmsg = checkFormatString (Format); if (! isempty (errmsg)) error ("duration.cellstr: %s", errmsg); endif this.Format = Format; endif cstr = dispstrings (this); endfunction ## -*- texinfo -*- ## @deftypefn {duration} {@var{cmat} =} char (@var{D}) ## ## Convert duration array to a character matrix. ## ## @code{@var{cmat} = char (@var{D})} returns a character matrix with one ## row per element in @var{D}. ## ## @end deftypefn function cmat = char (this, Format = '') if (! isempty (Format)) ## Convert string to character vector if necessary if (isstring (Format)) if (! isscalar (Format)) error (strcat ("duration.char: FORMAT must be a", ... " character vector or a string scalar.")); endif Format = char (Format); elseif (! (ischar (Format) && isrow (Format))) error (strcat ("duration.char: FORMAT must be a", ... " character vector or a string scalar.")); endif errmsg = checkFormatString (Format); if (! isempty (errmsg)) error ("duration.char: %s", errmsg); endif this.Format = Format; endif cmat = char (dispstrings (this)); endfunction ## -*- texinfo -*- ## @deftypefn {duration} {@var{DV} =} datevec (@var{DT}) ## @deftypefnx {duration} {[@var{Y}, @var{MO}, @var{D}, @var{h}, @var{mi}, @var{s}] =} datevec (@var{DT}) ## ## Convert duration array to date vectors. ## ## @code{@var{DV} = datevec (@var{DT})} returns an @math{N*6} numeric matrix ## whose rows represent each element in @var{DT} and each column corresponds ## to years, months, days, hours, minutes, and seconds, respectively. Since ## months cannot be represented as a fixed length of time, the second column ## of @var{DV} is always zero. @var{DV} represents a length of time split ## across different fixed-length elapsed time units. The number of rows in ## @var{DV} equals to the number of elements in the duration array @var{DT}. ## ## @code{[@var{Y}, @var{MO}, @var{D}, @var{h}, @var{mi}, @var{s}] = datevec ## (@var{DT})} returns the components of @var{DT} as individual variables, ## but unlike @var{DV} in the previous syntax, each variable has the same ## size as the duration array @var{DT}. ## ## Values containing a fractional portion less than 1 picosecond are rounded ## to the nearest picosecond. ## ## @end deftypefn function varargout = datevec (this) d = this.Days; tmp = d / 365.2425; y = fix (tmp); ## Fix round-off errors with threshold scaled by years idx = abs (tmp - y - 1) < 1e-15 * y; y(idx) += 1; x = rem (d, 365.2425); d = fix (x); x = x - d; ## Get remaining duration in seconds x = x * 86400; tmp = x / 3600; h = fix (tmp); idx = abs (tmp - h - 1) < 1e-15 * h; h(idx) += 1; x = x - h * 3600; tmp = x / 60; m = fix (tmp); idx = abs (tmp - m - 1) < 1e-15 * m; m(idx) += 1; s = x - m * 60; ## Fix floating point precision to nearest picosecond s = round (s * 1e+12) * 1e-12; ## Add months column mo = zeros (size (s)); mo(isnan (d)) = NaN; ## Transform to matrix DV = [y(:), mo(:), d(:), h(:), m(:), s(:)]; if (nargout == 0 || nargout == 1) varargout{1} = DV; elseif (nargout <= 6) for i = 1:nargout ## Multiple outputs are reshaped to original input size varargout{i} = reshape (DV(:,i), size (this)); endfor else error ("duration.datevec: too many output arguments."); endif endfunction ## -*- texinfo -*- ## @deftypefn {duration} {@var{H} =} hms (@var{D}) ## @deftypefnx {duration} {[@var{H}, @var{M}] =} hms (@var{D}) ## @deftypefnx {duration} {[@var{H}, @var{M}, @var{S}] =} hms (@var{D}) ## ## Split duration array into separate time unit values. ## ## @code{[@var{H}, @var{M}, @var{S}] = hms (@var{D})} splits the duration ## array @var{D} into separate numeric arrays @var{H}, @var{M}, and @var{S}, ## which correspond to hours, minutes, and seconds, respectively. Hours and ## minutes are returned as whole numbers, while seconds may also have a ## fractional part. ## ## Values containing a fractional portion less than 1 picosecond are rounded ## to the nearest picosecond. ## ## @end deftypefn function varargout = hms (this) x = this.Days * 86400; h = fix (x / 3600); tmp = x - h * 3600; idx = 3600 - tmp < 1e-12; # find round-off errors to whole hours if (any (idx, 'all')) h(idx) += 1; x(idx) -= h(idx) * 3600; x(! idx) = tmp(! idx); else x = tmp; endif m = fix (x / 60); tmp = x - m * 60; idx = 60 - tmp < 1e-12; # find round-off errors to whole minutes if (any (idx, 'all')) m(idx) += 1; x(idx) -= m(idx) * 60; x(! idx) = tmp(! idx); else x = tmp; endif s = x; idx = x < 1e-12; # find round-off errors to whole seconds if (any (idx, 'all')) s(idx) = 0; endif ## Fix floating point precision to nearest picosecond s = round (s * 1e12) / 1e12; if (nargout == 0 || nargout == 1) varargout{1} = h; elseif (nargout == 2) varargout{1} = h; varargout{2} = m; elseif (nargout == 3) varargout{1} = h; varargout{2} = m; varargout{3} = s; else error ("duration.hms: too many output arguments."); endif endfunction ## -*- texinfo -*- ## @deftypefn {duration} {@var{X} =} years (@var{D}) ## ## Duration equivalent numeric values in fixed-length years. ## ## @code{@var{X} = years (@var{D})} converts durations in @var{D} to the ## equivalent number of fixed-length years (1 year equals 365.2425 days). ## @var{X} is a double array of the same size as @var{D}. ## ## @end deftypefn function out = years (this) out = this.Days / 365.2425; endfunction ## -*- texinfo -*- ## @deftypefn {duration} {@var{X} =} days (@var{D}) ## ## Duration equivalent numeric values in fixed-length days. ## ## @code{@var{X} = days (@var{D})} converts durations in @var{D} to the ## equivalent number of fixed-length days (1 day equals 24 hours). @var{X} ## is a double array of the same size as @var{D}. ## ## @end deftypefn function out = days (this) out = this.Days; endfunction ## -*- texinfo -*- ## @deftypefn {duration} {@var{X} =} hours (@var{D}) ## ## Duration equivalent numeric values in hours. ## ## @code{@var{X} = hours (@var{D})} converts durations in @var{D} to the ## equivalent number of hours. @var{X} is a double array of the same size ## as @var{D}. ## ## @end deftypefn function out = hours (this) out = this.Days * 24; endfunction ## -*- texinfo -*- ## @deftypefn {duration} {@var{X} =} minutes (@var{D}) ## ## Duration equivalent numeric values in minutes. ## ## @code{@var{X} = minutes (@var{D})} converts durations in @var{D} to the ## equivalent number of minutes. @var{X} is a double array of the same size ## as @var{D}. ## ## Values containing a fractional portion less than 1 picosecond are rounded ## to the nearest picosecond. ## ## @end deftypefn function out = minutes (this) out = this.Days * 1440; ## Fix floating point precision to nearest picosecond out = round (out * 1e13) / 1e13; endfunction ## -*- texinfo -*- ## @deftypefn {duration} {@var{X} =} seconds (@var{D}) ## ## Duration equivalent numeric values in seconds. ## ## @code{@var{X} = seconds (@var{D})} converts durations in @var{D} to the ## equivalent number of seconds. @var{X} is a double array of the same size ## as @var{D}. ## ## Values containing a fractional portion less than 1 picosecond are rounded ## to the nearest picosecond. ## ## @end deftypefn function out = seconds (this) out = this.Days * 86400; ## Fix floating point precision to nearest picosecond out = round (out * 1e12) / 1e12; endfunction ## -*- texinfo -*- ## @deftypefn {duration} {@var{X} =} milliseconds (@var{D}) ## ## Duration equivalent numeric values in milliseconds. ## ## @code{@var{X} = milliseconds (@var{D})} converts durations in @var{D} to ## the equivalent number of milliseconds. @var{X} is a double array of the ## same size as @var{D}. ## ## Values containing a fractional portion less than 1 picosecond are rounded ## to the nearest picosecond. ## ## @end deftypefn function out = milliseconds (this) out = this.Days * 86400000; ## Fix floating point precision to nearest picosecond out = round (out * 1e9) / 1e9; endfunction endmethods ################################################################################ ## ** Summary Information ** ## ################################################################################ ## Available Methods ## ## ## ## 'size' 'ndims' 'numel' 'nnz' ## ## 'length' 'keyHash' ## ## ## ################################################################################ methods (Access = public) ## -*- texinfo -*- ## @deftypefn {duration} {@var{sz} =} size (@var{D}) ## @deftypefnx {duration} {@var{dim_sz} =} size (@var{D}, @var{dim}) ## @deftypefnx {duration} {@var{dim_sz} =} size (@var{D}, @var{d1}, @var{d2}, @dots{}) ## @deftypefnx {duration} {[@var{rows}, @var{columns}, @dots{}, @var{dim_n_sz}] =} size (@dots{}) ## ## Return the size of a duration array. ## ## @code{@var{sz} = size (@var{D})} returns a row vector with the size ## (number of elements) of each dimension for the duration array @var{D}. ## ## @code{@var{dim_sz} = size (@var{D}, @var{dim})} returns the size of ## the corresponding dimension specified in @var{dim}. If @var{dim} is a ## vector, then @var{dim_sz} is a vector of the same length and with each ## element corresponding to a specified dimension. Multiple dimensions may ## also be specified as separate arguments. ## ## With a single output argument, @code{size} returns a row vector. When ## called with multiple output arguments, @code{size} returns the size of ## dimension N in the Nth argument. ## ## @end deftypefn function varargout = size (this, varargin) if (! isempty (varargin)) sz = size (this.Days, varargin{:}); else sz = size (this.Days); endif if (nargout == 0 || nargout == 1) varargout{1} = sz; elseif (numel (sz) != nargout) error (strcat ("duration.size: nargout > 1 but does not", ... " match number of requested dimensions.")); else for i = 1:nargout varargout{i} = sz(i); endfor endif endfunction ## -*- texinfo -*- ## @deftypefn {duration} {@var{out} =} ndims (@var{D}) ## ## Number of dimensions in a duration array. ## ## @code{@var{out} = ndims (@var{D})} returns the number of dimensions of ## the duration array @var{D}. ## ## @end deftypefn function out = ndims (this) out = ndims (this.Days); endfunction ## -*- texinfo -*- ## @deftypefn {duration} {@var{out} =} numel (@var{D}) ## ## Total number of elements in a duration array. ## ## @code{@var{out} = numel (@var{D})} returns the number of elements in the ## duration array @var{D}. ## ## @end deftypefn function out = numel (this, varargin) out = numel (this.Days); endfunction ## -*- texinfo -*- ## @deftypefn {duration} {@var{out} =} nnz (@var{D}) ## ## Number of nonzero elements in duration array. ## ## @code{@var{out} = nnz (@var{D})} returns the number of nonzero ## elements in the duration array @var{D}. ## ## @end deftypefn function out = nnz (this) d = this.Days(:); out = numel (d) - sum (d == 0); endfunction ## -*- texinfo -*- ## @deftypefn {duration} {@var{N} =} length (@var{D}) ## ## Length of a duration vector. ## ## @code{@var{N} = length (@var{D})} returns the size of the longest ## dimension of the duration array @var{D}, unless any of its dimensions has ## zero length, in which case @code{length (@var{D})} returns 0. ## ## @end deftypefn function N = length (this) if (isempty (this.Days)) N = 0; else N = max (size (this.Days)); endif endfunction ## -*- texinfo -*- ## @deftypefn {duration} {@var{hey} =} keyHash (@var{D}) ## ## Generate a hash code for duration array. ## ## @code{@var{h} = keyHash (@var{D})} generates a @qcode{uint64} scalar that ## represents the input array @var{D}. @code{keyHash} utilizes the 64-bit ## FNV-1a variant of the Fowler-Noll-Vo non-cryptographic hash function. ## ## @code{@var{h} = keyHash (@var{D}, @var{base})} also generates a 64-bit ## hash code using @var{base} as the offset basis for the FNV-1a hash ## algorithm. @var{base} must be a @qcode{uint64} integer type scalar. Use ## this syntax to cascade @code{keyHash} on multiple objects for which a ## single hash code is required. ## ## Note that unlike MATLAB, this implementation does not use any random ## seed. As a result, @code{keyHash} will always generate the exact same ## hash key for any particular input across different workers and Octave ## sessions. ## ## @end deftypefn function key = keyHash (this, base = []) ## Initialize string with size and class name size_str = sprintf ('%dx', size (this.Days))(1:end-1); init_str = [size_str 'duration']; if (base) if (! (isscalar (base) && isa (base, 'uint64'))) error ("duration.keyHash: BASE must be a UINT64 scalar."); endif key = __ckeyHash__(init_str, base); else key = __ckeyHash__(init_str); endif if (! isempty (this.Days)) key = __nkeyHash__(this.Days(:), key); endif endfunction endmethods ################################################################################ ## ** Query Operations ** ## ################################################################################ ## Available Methods ## ## ## ## 'isbetween' 'iscolumn' 'isempty' 'isequal' ## ## 'isequaln' 'isfinite' 'isinf' 'ismatrix' ## ## 'ismember' 'ismissing' 'isnan' 'isregular' ## ## 'isrow' 'isscalar' 'issorted' 'issortedrows' ## ## 'isvector' ## ## ## ################################################################################ methods (Access = public) ## -*- texinfo -*- ## @deftypefn {duration} {@var{TF} =} isbetween (@var{D}, @var{lower}, @var{upper}) ## @deftypefnx {duration} {@var{TF} =} isbetween (@var{D}, @var{lower}, @var{upper}, @var{intervalType}) ## ## Find duration elements within specified range. ## ## @code{@var{TF} = isbetween (@var{D}, @var{lower}, @var{upper})} returns a ## logical array, @var{TF}, which is the same size as the input duration ## array @var{D} and it contains @qcode{true} for each corresponding element ## which is within the range specified by @var{lower} and @var{upper} and ## @qcode{false} otherwise. @var{lower} and @var{upper} must be duration ## arrays of compatible size with @var{D} or alternatively they can be ## specified as a character vector, a cell array of character vectors or a ## string array containing valid text duration representations. ## ## @code{@var{TF} = isbetween (@var{D}, @var{lower}, @var{upper}, ## @var{intervalType})} specifies the type of interval for the @var{lower} ## and @var{upper} bounds and it can be one of the following values. ## ## @itemize ## @item @qcode{'closed'} (default) includes lower and upper bounds. ## ## @item @qcode{'open'} excludes lower and upper bounds. ## ## @item @qcode{'openleft'} or @qcode{'closedright'} exclude the lower and ## include the upper bound. They have identical behavior. ## ## @item @qcode{'closedleft'} or @qcode{'openright'} include the lower and ## exclude the upper bound. They have identical behavior. ## @end itemize ## ## @var{intervalType} can be specified either as a character vector or a ## string scalar. ## ## @end deftypefn function TF = isbetween (this, lower, upper, varargin) if (nargin < 3) error ("duration.isbetween: too few input arguments."); elseif (nargin == 3) intervaltype = 'closed'; elseif (nargin == 4) intervaltype = varargin{1}; elseif (nargin > 4) error (strcat ("duration.isbetween: optional paired arguments", ... " are not supported for duration arrays.")); endif if (isnumeric (lower) || isnumeric (upper)) error ("duration.isbetween: LOWER and UPPER cannot be numeric."); endif if (strcmpi (intervaltype, 'closed')) TF = lower <= this & this <= upper; elseif (strcmpi (intervaltype, 'open')) TF = lower < this & this < upper; elseif (any (strcmpi (intervaltype, {'openleft', 'closedright'}))) TF = lower < this & this <= upper; elseif (any (strcmpi (intervaltype, {'openright', 'closedleft'}))) TF = lower <= this & this < upper; else error ("duration.isbetween: invalid INTERVALTYPE option."); endif endfunction ## -*- texinfo -*- ## @deftypefn {duration} {@var{TF} =} iscolumn (@var{D}) ## ## Return true if duration array is a column vector. ## ## @code{@var{TF} = iscolumn (@var{D})} returns a logical scalar @var{TF}, ## which is @qcode{true} if the duration array @var{D} is a column vector ## and @qcode{false} otherwise. A column vector is a 2-D array for which ## @code{size (@var{D})} returns @code{[@var{N}, 1]} with non-negative ## @var{N}. By definition, a scalar is also a column vector. ## ## @end deftypefn function TF = iscolumn (this) TF = iscolumn (this.Days); endfunction ## -*- texinfo -*- ## @deftypefn {duration} {@var{TF} =} isempty (@var{D}) ## ## Return true if duration array is empty. ## ## @code{@var{TF} = isempty (@var{D})} returns a logical scalar @var{TF}, ## which is @qcode{true} if the duration array @var{D} is empty and ## @qcode{false} otherwise. ## ## @end deftypefn function TF = isempty (this) TF = isempty (this.Days); endfunction ## -*- texinfo -*- ## @deftypefn {duration} {@var{TF} =} isequal (@var{D1}, @var{D2}) ## @deftypefnx {duration} {@var{TF} =} isequal (@var{D1}, @var{D2}, @dots{}) ## ## Return true if duration arrays are equal. ## ## @code{@var{TF} = isequal (@var{D1}, @var{D2})} returns a logical scalar ## @var{TF}, which is @qcode{true}, if the duration arrays @var{D1} and ## @var{D2} contain the same values, and @qcode{false} otherwise. Either ## @var{D1} or @var{D2} can also be specified as a character vector, a cell ## array of character vectors or a string array containing valid text ## duration representations. ## ## @code{@var{TF} = isequal (@var{D1}, @var{D2}, @dots{})} returns a logical ## scalar @var{TF}, which is @qcode{true}, if all input arguments are equal, ## and @qcode{false} otherwise. ## ## @end deftypefn function TF = isequal (varargin) args = varargin; [args{:}] = promote (varargin{:}); days = cellfun (@(obj) obj.Days, args, 'UniformOutput', false); TF = isequal (days{:}); endfunction ## -*- texinfo -*- ## @deftypefn {duration} {@var{TF} =} isequaln (@var{D1}, @var{D2}) ## @deftypefnx {duration} {@var{TF} =} isequaln (@var{D1}, @var{D2}, @dots{}) ## ## Return true if duration arrays are equal under the assumption that ## missing elements are equal. ## ## @code{@var{TF} = isequaln (@var{D1}, @var{D2})} returns a logical scalar ## @var{TF}, which is @qcode{true}, if the duration arrays @var{D1} and ## @var{D2} contain the same values or corresponding missing elements, and ## @qcode{false} otherwise. Either @var{D1} or @var{D2} can also be ## specified as a character vector, a cell array of character vectors or a ## string array containing valid text duration representations. ## ## @code{@var{TF} = isequaln (@var{D1}, @var{D2}, @dots{})} returns a ## logical scalar @var{TF}, which is @qcode{true}, if all input arguments ## are equal under the assumption that missing elements are equal, and ## @qcode{false} otherwise. ## ## @end deftypefn function TF = isequaln (varargin) args = varargin; [args{:}] = promote (varargin{:}); days = cellfun (@(obj) obj.Days, args, 'UniformOutput', false); TF = isequaln (days{:}); endfunction ## -*- texinfo -*- ## @deftypefn {duration} {@var{TF} =} isfinite (@var{D}) ## ## Return true for duration elements that are finite. ## ## @code{@var{TF} = isfinite (@var{D})} returns a logical array @var{TF} ## of the same size as @var{calD} containing @qcode{true} for each ## corresponding element of @var{D} that is finite and @qcode{false} ## otherwise. Finite elements are those which are neither infinite nor ## Not-A-Number. ## ## @end deftypefn function TF = isfinite (this) TF = isfinite (this.Days); endfunction ## -*- texinfo -*- ## @deftypefn {duration} {@var{TF} =} isinf (@var{D}) ## ## Return true for duration elements that are infinite. ## ## @code{@var{TF} = isinf (@var{D})} returns a logical array @var{TF} ## of the same size as @var{D} containing @qcode{true} for each ## corresponding element of @var{calD} that is either @qcode{Inf} or ## @qcode{-Inf} and @qcode{false} otherwise. ## ## @end deftypefn function TF = isinf (this) TF = isinf (this.Days); endfunction ## -*- texinfo -*- ## @deftypefn {duration} {@var{TF} =} ismatrix (@var{D}) ## ## Return true if duration array is a 2-D array. ## ## @code{@var{TF} = ismatrix (@var{D})} returns a logical scalar @var{TF}, ## which is @qcode{true} if the duration array @var{D} is a matrix and ## @qcode{false} otherwise. A matrix is an array of any type where ## @code{ndims (@var{D}) == 2}. By definition, a scalar is also a matrix. ## ## @end deftypefn function TF = ismatrix (this) TF = ismatrix (this.Days); endfunction ## -*- texinfo -*- ## @deftypefn {duration} {@var{TF} =} ismember (@var{A}, @var{B}) ## @deftypefnx {duration} {@var{TF} =} ismember (@var{A}, @var{B}, @qcode{'rows'}) ## @deftypefnx {duration} {[@var{TF}, @var{index}] =} ismember (@dots{}) ## @deftypefnx {duration} {[@var{TF}, @var{index}] =} ismember (@dots{}, @qcode{'legacy'}) ## ## Find duration elements in a set. ## ## @code{@var{TF} = ismember (@var{A}, @var{B})} returns a logical array ## @var{TF} of the same size as @var{A} containing @qcode{true} for each ## corresponding element of @var{A} that is in @var{B} and @qcode{false} ## otherwise. @qcode{NaN} elements are not equal with each other and always ## return @qcode{false}. ## ## @code{@var{TF} = ismember (@var{A}, @var{B}, @qcode{'rows'})} only ## applies to duration matrices with the same number of columns, in which ## case the logical vector @var{TF} contains @qcode{true} for each row of ## @var{A} that is also a row in @var{B}. @var{TF} has the same number of ## rows as @var{A}. ## ## @code{[@var{TF}, @var{index}] = ismember (@var{A}, @var{B})} also returns ## an index array of the same size as @var{A} containing the lowest index in ## @var{B} for each element of @var{A} that is a member of @var{B} and 0 ## otherwise. If the @qcode{'rows'} optional argument is used, then the ## returning index is a column vector with the same rows as @var{A} and it ## contains the lowest index in @var{B} for each row of @var{A} that is a ## member of @var{B} and 0 otherwise. If the @qcode{'legacy'} optional ## argument is specified, then the highest index of matched elements is ## returned. Unless multiple matches exist, the @qcode{'legacy'} option has ## no effect on the returned @var{index}. ## ## @end deftypefn function varargout = ismember (A, B, varargin) ## Check input arguments if (! isa (B, 'duration')) B = promote (B); endif ## Check for 'rows' and 'legacy' optional arguments if (! isempty (varargin)) if (! cellfun ('ischar', varargin)); error ("duration.ismember: all options must be character vectors."); elseif (! all (strcmpi (varargin, 'rows') | ... strcmpi (varargin, 'legacy'))) error (strcat ("duration.ismember: only 'rows' and 'legacy'", ... " are valid options.")); endif do_rows = any (strcmpi ('rows', varargin)); if (do_rows) if (ndims (A) != 2 || ndims (A) != ndims (B)) error ("duration.ismember: 'rows' applies only to 2-D matrices."); endif if (size (A, 2) != size (B, 2)) error (strcat ("duration.ismember: 'rows' requires same", ... " number of columns.")); endif endif if (nargout > 1) [varargout{1}, varargout{2}] = __ismember__ (A.Days, B.Days, ... varargin{:}); else varargout{1} = __ismember__ (A.Days, B.Days, varargin{:}); endif else if (nargout > 1) [varargout{1}, varargout{2}] = __ismember__ (A.Days, B.Days); else varargout{1} = __ismember__ (A.Days, B.Days); endif endif endfunction ## -*- texinfo -*- ## @deftypefn {duration} {@var{TF} =} ismissing (@var{D}) ## @deftypefnx {duration} {@var{TF} =} ismissing (@var{D}, @var{indicator}) ## ## Find missing elements in duration array. ## ## @code{@var{TF} = ismissing (@var{D})} returns a logical array, @var{TF}, ## with the same dimensions as @var{D}, where @code{true} values match the ## standard missing values in the input duration array. ## ## The optional input @var{indicator} can be a scalar or a vector duration ## array, specifying alternative missing values in the input data. When ## specifying @var{indicator} values, the standard missing values are ## ignored, unless explicitly stated in the @var{indicator}. ## ## @end deftypefn function TF = ismissing (this, varargin) if (nargin > 2) error ("duration.ismissing: too many input arguments."); endif if (! isempty (varargin)) indicator = varargin{1}; TF = false (size (this)); if (isvector (indicator)) if (isa (indicator, 'duration')) for i = 1:length (indicator) TF(this.Days == indicator.Days(i)) = true; endfor else error ("duration.ismissing: INDICATOR must be a 'duration' array."); endif else error ("duration.ismissing: INDICATOR must be a vector."); endif else TF = isnan (this.Days); endif endfunction ## -*- texinfo -*- ## @deftypefn {duration} {@var{TF} =} isnan (@var{D}) ## ## Return true for duration elements that are Not-A-Number. ## ## @code{@var{TF} = isnan (@var{D})} returns a logical array @var{TF} of ## the same size as @var{D} containing @qcode{true} for each corresponding ## element of @var{calD} that is @qcode{NaN} and @qcode{false} otherwise. ## ## @end deftypefn function TF = isnan (this) TF = isnan (this.Days); endfunction ## -*- texinfo -*- ## @deftypefn {duration} {@var{TF} =} isrow (@var{D}) ## ## Return true if duration array is a row vector. ## ## @code{@var{TF} = isrow (@var{D})} returns a logical scalar @var{TF}, ## which is @qcode{true} if the duration array @var{D} is a row vector ## and @qcode{false} otherwise. A row vector is a 2-D array for which ## @code{size (@var{D})} returns @code{[1, @var{N}]} with non-negative ## @var{N}. By definition, a scalar is also a row vector. ## ## @end deftypefn function TF = isrow (this) TF = isrow (this.Days); endfunction ## -*- texinfo -*- ## @deftypefn {duration} {@var{TF} =} isscalar (@var{D}) ## ## Return true if duration array is a scalar. ## ## @code{@var{TF} = isscalar (@var{D})} returns a logical scalar @var{TF}, ## which is @qcode{true} if the duration array @var{D} is also a scalar ## and @qcode{false} otherwise. A scalar is a single element object for ## which @code{size (@var{D})} returns @code{[1, 1]}. ## ## @end deftypefn function TF = isscalar (this) TF = isscalar (this.Days); endfunction ## -*- texinfo -*- ## @deftypefn {duration} {@var{TF} =} issorted (@var{D}) ## @deftypefnx {duration} {@var{TF} =} issorted (@var{D}, @var{dim}) ## @deftypefnx {duration} {@var{TF} =} issorted (@var{D}, @var{direction}) ## @deftypefnx {duration} {@var{TF} =} issorted (@var{D}, @var{dim}, @var{direction}) ## @deftypefnx {duration} {@var{TF} =} issorted (@dots{}, @qcode{'MissingPlacement'}, @var{MP}) ## @deftypefnx {duration} {@var{TF} =} issorted (@dots{}, @qcode{'ComparisonMethod'}, @var{CM}) ## ## Return true if duration array is sorted. ## ## @code{@var{TF} = issorted (@var{D})} returns a logical scalar @var{TF}, ## which is @qcode{true}, if the duration array @var{D} is sorted in ## ascending order, and @qcode{false} otherwise. ## ## @code{@var{TF} = issorted (@var{D}, @var{dim})} returns a logical scalar ## @var{TF}, which is @qcode{true}, if the duration array @var{D} is ## sorted in ascending order along the dimension @var{dim}, and ## @qcode{false} otherwise. ## ## @code{@var{TF} = issorted (@var{D}, @var{direction})} returns a logical ## scalar @var{TF}, which is @qcode{true}, if the duration array @var{D} ## is sorted in the direction specified by @var{direction}, and ## @qcode{false} otherwise. @var{direction} can be any of the following ## options: ## ## @itemize ## @item @qcode{'ascend'}, which is the default, checks is elements are in ## ascending order. ## @item @qcode{'descend'} checks if elements are in descending order. ## @item @qcode{'monotonic'} checks if elements are either in ascending or ## descending order. ## @item @qcode{'strictascend'} checks if elements are in ascending order ## and there are no duplicate or undefined elements. ## @item @qcode{'strictdescend'} checks if elements are in descending order ## and there are no duplicate or undefined elements. ## @item @qcode{'strictmonotonic'} checks if elements are either in ## ascending or descending order and there are no duplicate or undefined ## elements. ## @end itemize ## ## @code{@var{TF} = issorted (@dots{}, @qcode{'MissingPlacement'}, ## @var{MP})} ## specifies where missing elements (@qcode{NaN}) are placed with one of the ## following options specified in @var{MP}: ## ## @itemize ## @item @qcode{'auto'}, which is the default, places missing elements last ## for ascending sort and first for descending sort. ## @item @qcode{'first'} places missing elements first. ## @item @qcode{'last'} places missing elements last. ## @end itemize ## ## @code{@var{TF} = issorted (@dots{}, @qcode{'ComparisonMethod'}, ## @var{CM})} ## specifies the comparison method for determining the order of elements ## with one of following options specified in @var{CM}: ## ## @itemize ## @item @qcode{'auto'}, which is the default, sorts by @code{real (A)}. ## @item @qcode{'real'} sorts by @code{real (A)}. ## @item @qcode{'abs'} sorts by @code{abs (A)}. ## @end itemize ## ## @end deftypefn function TF = issorted (this, varargin) ## Single input argument if (nargin == 1) TF = isequaln (this, sort (this)); return; endif ## Get operating dimension cid = cellfun (@isnumeric, varargin); if (any (cid)) dim = varargin{cid}; else sz = size (this); dim = find (sz != 1, 1); if (isempty (dim)) # scalar dim = 1; endif endif ## Force strings to character vectors [varargin{:}] = convertStringsToChars (varargin{:}); ## Get direction from input argument list valid_direction = {'ascend', 'descend', 'monotonic', 'strictascend', ... 'strictdescend', 'strictmonotonic'}; fcn = @(x) ischar (x) && ismember (x, valid_direction); cid = cellfun (fcn, varargin); if (any (cid)) direction = varargin{cid}; switch (direction) case {'ascend', 'descend'} TF = isequaln (this, sort (this, varargin{:})); case 'monotonic' ## Check for either ascending or descending varargin{cid} = 'ascend'; TF = isequaln (this, sort (this, varargin{:})); if (TF) return; endif varargin{cid} = 'descend'; TF = isequaln (this, sort (this, varargin{:})); case 'strictascend' ## Check for missing values first (fast) if (any (ismissing (this), 'all')) TF = false; return; endif varargin{cid} = strrep (direction, 'strict', ''); sorted = sort (this, varargin{:}); if (any (diff (sorted, 1, dim) <= 0, 'all')) TF = false; return; endif TF = isequaln (this, sorted); case 'strictdescend' ## Check for missing values first (fast) if (any (ismissing (this), 'all')) TF = false; return; endif varargin{cid} = strrep (direction, 'strict', ''); sorted = sort (this, varargin{:}); if (any (diff (sorted, 1, dim) >= 0, 'all')) TF = false; return; endif TF = isequaln (this, sorted); case 'strictmonotonic' ## Check missing values first (fast) if (any (ismissing (this), 'all')) TF = false; return; endif ## Check for either ascending or descending varargin{cid} = 'ascend'; sorted = sort (this, varargin{:}); if (any (diff (sorted, 1, dim) <= 0, 'all')) TF = false; return; endif TF = isequaln (this, sorted); if (TF) return; endif varargin{cid} = 'descend'; sorted = sort (this, varargin{:}); if (any (diff (sorted, 1, dim) >= 0, 'all')) TF = false; return; endif TF = isequaln (this, sorted); endswitch else ## No DIRECTION input argument TF = isequaln (this, sort (this, varargin{:})); endif endfunction ## -*- texinfo -*- ## @deftypefn {duration} {@var{TF} =} issortedrows (@var{D}) ## @deftypefnx {duration} {@var{TF} =} issortedrows (@var{D}, @var{col}) ## @deftypefnx {duration} {@var{TF} =} issortedrows (@var{D}, @var{direction}) ## @deftypefnx {duration} {@var{TF} =} issortedrows (@var{D}, @var{col}, @var{direction}) ## @deftypefnx {duration} {@var{TF} =} issortedrows (@dots{}, @qcode{'MissingPlacement'}, @var{MP}) ## @deftypefnx {duration} {@var{TF} =} issortedrows (@dots{}, @qcode{'ComparisonMethod'}, @var{CM}) ## ## Return true if duration matrix rows are sorted. ## ## @code{@var{TF} = issortedrows (@var{D})} returns a logical scalar ## @var{TF}, which is @qcode{true}, if the rows in the 2-D duration array ## @var{D} are sorted in ascending order, and @qcode{false} otherwise. ## ## @code{@var{TF} = issortedrows (@var{D}, @var{col})} returns a logical ## scalar @var{TF}, which is @qcode{true}, if the duration array @var{D} ## is sorted according to the columns specified by the vector @var{col}, and ## @qcode{false} otherwise. @var{col} must explicitly contain non-zero ## integers whose absolute values index existing columns in @var{D}. ## Positive elements sort the corresponding columns in ascending order, ## while negative elements sort the corresponding columns in descending ## order. ## ## @code{@var{TF} = issortedrows (@var{D}, @var{direction})} checks if the ## rows in @var{D} are sorted according to the specified direction, which ## can be one of the following options: ## ## @itemize ## @item @qcode{'ascend'}, which is the default, checks is elements are in ## ascending order. ## @item @qcode{'descend'} checks if elements are in descending order. ## @item @qcode{'monotonic'} checks if elements are either in ascending or ## descending order. ## @item @qcode{'strictascend'} checks if elements are in ascending order ## and there are no duplicate or undefined elements. ## @item @qcode{'strictdescend'} checks if elements are in descending order ## and there are no duplicate or undefined elements. ## @item @qcode{'strictmonotonic'} checks if elements are either in ## ascending or descending order and there are no duplicate or undefined ## elements. ## @end itemize ## ## Alternatively, @var{direction} can be a cell array of character ## vectors specifying the sorting direction for each individual column of ## @var{D}, in which case the number of elements in @var{direction} must ## equal the number of columns in @var{D}. ## ## @code{@var{TF} = issortedrows (@var{D}, @var{col}, @var{direction})} ## checks if the rows in the duration array @var{D} are sorted according ## to the columns specified in @var{col} using the corresponding sorting ## direction specified in @var{direction}. In this case, the sign of the ## values in @var{col} is ignored. @var{col} and @var{direction} must have ## the same length, but not necessarily the same number of elements as the ## columns in @var{D}. ## ## @code{@var{TF} = issortedrows (@dots{}, @qcode{'MissingPlacement'}, ## @var{MP})} specifies where missing elements (@qcode{}) are ## placed with one of the following options specified in @var{MP}: ## ## @itemize ## @item @qcode{'auto'}, which is the default, places missing elements last ## for ascending sort and first for descending sort. ## @item @qcode{'first'} places missing elements first. ## @item @qcode{'last'} places missing elements last. ## @end itemize ## ## @code{@var{TF} = issortedrows (@dots{}, @qcode{'ComparisonMethod'}, ## @var{CM})} specifies the comparison method for determining the order of ## elements with one of following options specified in @var{CM}: ## ## @itemize ## @item @qcode{'auto'}, which is the default, sorts by @code{real (A)}. ## @item @qcode{'real'} sorts by @code{real (A)}. ## @item @qcode{'abs'} sorts by @code{abs (A)}. ## @end itemize ## ## @end deftypefn function TF = issortedrows (this, varargin) ## Single input argument if (nargin == 1) TF = isequaln (this, sortrows (this)); return; endif ## Force strings to character vectors or cell arrays of character vectors [varargin{:}] = convertStringsToChars (varargin{:}); ## Get valid direction(s) from input argument list valid = {'ascend', 'descend', 'monotonic', 'strictascend', ... 'strictdescend', 'strictmonotonic'}; fcn = @(x) (ischar (x) && ismember (x, valid)) || iscellstr (x); cid = cellfun (fcn, varargin); if (any (cid)) direction = cellstr (varargin{cid}); ## Check for valid type of directions in cellstring if (! all (cellfun (@(x) ismember (x, valid), direction))) error ("duration.issortedrows: invalid DIRECTION value."); endif ## Handle non-strict modes first if (all (cellfun (@(x) ismember (x, {'ascend', 'descend'}), direction))) TF = isequaln (this, sortrows (this, varargin{:})); return; endif simple_types = {'ascend', 'descend', 'monotonic'}; if (all (cellfun (@(x) ismember (x, simple_types), direction))) idx = strcmp (direction, 'monotonic'); direction{idx} = 'ascend'; varargin{cid} = direction; TF = isequaln (this, sortrows (this, varargin{:})); if (TF) return; endif direction{idx} = 'descend'; varargin{cid} = direction; TF = isequaln (this, sortrows (this, varargin{:})); return; endif ## Handle strict modes. Determine the sort-key column order and the ## sort position holding the first strict direction, then derive the ## prefix (the columns sorted up to and including that position, on ## which the duplicate test operates) and the binding strict column ## (the single column on which the missing test operates). A missing ## value in the binding column, or two consecutive sorted rows equal ## across the prefix columns, rules out a strict ordering; strict ## positions after the first are subsumed by it. strict_types = {'strictascend', 'strictdescend', 'strictmonotonic'}; sflag = cellfun (@(x) ismember (x, strict_types), direction); nc = size (this, 2); nummask = cellfun (@isnumeric, varargin); if (any (nummask)) col = varargin{nummask}; ocols = abs (col(:)'); if (isscalar (direction)) sflag = repmat (sflag, 1, numel (col)); elseif (numel (direction) != numel (col)) error ("duration.issortedrows: COL and DIRECTION mismatch."); endif else ocols = 1:nc; if (isscalar (direction)) sflag = repmat (sflag, 1, nc); endif endif K = find (sflag, 1); prefix = ocols(1:K); bindcol = ocols(K); ## A missing value in the binding strict column rules out strictness. if (any (isnan (this.Days(:,bindcol)), 'all')) TF = false; return; endif ## Replace strict modes with their plain counterparts for the sort. direction = strrep (direction, 'strict', ''); varargin{cid} = direction; if (all (cellfun (@(x) ismember (x, {'ascend', 'descend'}), direction))) sorted = sortrows (this, varargin{:}); ## No two consecutive rows may tie across the prefix columns. tmpcol = sorted.Days(:, prefix); if (any (all (diff (tmpcol, 1, 1) == 0, 2))) TF = false; return; endif TF = isequaln (this, sorted); else # a 'monotonic' position also exists idx = strcmp (direction, 'monotonic'); direction(idx) = {'ascend'}; varargin{cid} = direction; sorted = sortrows (this, varargin{:}); tmpcol = sorted.Days(:, prefix); if (any (all (diff (tmpcol, 1, 1) == 0, 2))) TF = false; return; endif TF = isequaln (this, sorted); if (TF) return; endif direction(idx) = {'descend'}; varargin{cid} = direction; sorted = sortrows (this, varargin{:}); tmpcol = sorted.Days(:, prefix); if (any (all (diff (tmpcol, 1, 1) == 0, 2))) TF = false; return; endif TF = isequaln (this, sorted); endif else ## No DIRECTION input argument TF = isequaln (this, sortrows (this, varargin{:})); endif endfunction ## -*- texinfo -*- ## @deftypefn {duration} {@var{TF} =} isvector (@var{D}) ## ## Return true if duration array is a vector. ## ## @code{@var{TF} = isvector (@var{D})} returns a logical scalar @var{TF}, ## which is @qcode{true} if the duration array @var{D} is a vector and ## @qcode{false} otherwise. A vector is a 2-D array for which one of the ## dimensions is equal to 1 (either @math{1*N} or @math{N*1}). By ## definition, a scalar is also a vector. ## ## @end deftypefn function TF = isvector (this) TF = isvector (this.Days); endfunction endmethods ################################################################################ ## ** Relational Operations ** ## ################################################################################ ## Available Methods ## ## ## ## 'eq' 'ge' 'gt' 'le' ## ## 'lt' 'ne' ## ## ## ################################################################################ methods (Access = public) ## -*- texinfo -*- ## @deftypefn {duration} {@var{TF} =} eq (@var{A}, @var{B}) ## ## Equality for duration arrays. ## ## @code{@var{TF} = eq (@var{A}, @var{B})} is the equivalent of the syntax ## @code{@var{TF} = @var{A} == @var{B}} and returns a logical array whose ## elements set to @qcode{true} where the corresponding elements of @var{A} ## and @var{B} are equal and set to @qcode{false} otherwise. ## ## @var{A} and @var{B} must be size compatible, which translates to they can ## be the same size, one can be scalar, or for every dimension, their ## dimension sizes must be equal or one of them must be 1. The size of ## @var{C} is determined by the size compatibility of @var{A} and @var{B}. ## ## One of the input arguments can also be a character vector, a cell array ## of character vectors, or a string array representing duration strings or ## a numeric array representing days. ## ## @end deftypefn function TF = eq (A, B) [A, B] = promote (A, B); TF = A.Days == B.Days; endfunction ## -*- texinfo -*- ## @deftypefn {duration} {@var{TF} =} ge (@var{A}, @var{B}) ## ## Equality for duration arrays. ## ## @code{@var{TF} = ge (@var{A}, @var{B})} is the equivalent of the syntax ## @code{@var{TF} = @var{A} >= @var{B}} and returns a logical array whose ## elements set to @qcode{true} where the corresponding elements of @var{A} ## are greater than or equal to @var{B} and set to @qcode{false} otherwise. ## ## @var{A} and @var{B} must be size compatible, which translates to they can ## be the same size, one can be scalar, or for every dimension, their ## dimension sizes must be equal or one of them must be 1. The size of ## @var{C} is determined by the size compatibility of @var{A} and @var{B}. ## ## One of the input arguments can also be a character vector, a cell array ## of character vectors, or a string array representing duration strings or ## a numeric array representing days. ## ## @end deftypefn function TF = ge (A, B) [A, B] = promote (A, B); TF = A.Days >= B.Days; endfunction ## -*- texinfo -*- ## @deftypefn {duration} {@var{TF} =} gt (@var{A}, @var{B}) ## ## Equality for duration arrays. ## ## @code{@var{TF} = gt (@var{A}, @var{B})} is the equivalent of the syntax ## @code{@var{TF} = @var{A} > @var{B}} and returns a logical array whose ## elements set to @qcode{true} where the corresponding elements of @var{A} ## are greater than @var{B} and set to @qcode{false} otherwise. ## ## @var{A} and @var{B} must be size compatible, which translates to they can ## be the same size, one can be scalar, or for every dimension, their ## dimension sizes must be equal or one of them must be 1. The size of ## @var{C} is determined by the size compatibility of @var{A} and @var{B}. ## ## One of the input arguments can also be a character vector, a cell array ## of character vectors, or a string array representing duration strings or ## a numeric array representing days. ## ## @end deftypefn function TF = gt (A, B) [A, B] = promote (A, B); TF = A.Days > B.Days; endfunction ## -*- texinfo -*- ## @deftypefn {duration} {@var{TF} =} le (@var{A}, @var{B}) ## ## Equality for duration arrays. ## ## @code{@var{TF} = le (@var{A}, @var{B})} is the equivalent of the syntax ## @code{@var{TF} = @var{A} <= @var{B}} and returns a logical array whose ## elements set to @qcode{true} where the corresponding elements of @var{A} ## are less than or equal to @var{B} and set to @qcode{false} otherwise. ## ## @var{A} and @var{B} must be size compatible, which translates to they can ## be the same size, one can be scalar, or for every dimension, their ## dimension sizes must be equal or one of them must be 1. The size of ## @var{C} is determined by the size compatibility of @var{A} and @var{B}. ## ## One of the input arguments can also be a character vector, a cell array ## of character vectors, or a string array representing duration strings or ## a numeric array representing days. ## ## @end deftypefn function TF = le (A, B) [A, B] = promote (A, B); TF = A.Days <= B.Days; endfunction ## -*- texinfo -*- ## @deftypefn {duration} {@var{TF} =} lt (@var{A}, @var{B}) ## ## Equality for duration arrays. ## ## @code{@var{TF} = lt (@var{A}, @var{B})} is the equivalent of the syntax ## @code{@var{TF} = @var{A} < @var{B}} and returns a logical array whose ## elements set to @qcode{true} where the corresponding elements of @var{A} ## are less than @var{B} and set to @qcode{false} otherwise. ## ## @var{A} and @var{B} must be size compatible, which translates to they can ## be the same size, one can be scalar, or for every dimension, their ## dimension sizes must be equal or one of them must be 1. The size of ## @var{C} is determined by the size compatibility of @var{A} and @var{B}. ## ## One of the input arguments can also be a character vector, a cell array ## of character vectors, or a string array representing duration strings or ## a numeric array representing days. ## ## @end deftypefn function TF = lt (A, B) [A, B] = promote (A, B); TF = A.Days < B.Days; endfunction ## -*- texinfo -*- ## @deftypefn {duration} {@var{TF} =} ne (@var{A}, @var{B}) ## ## Equality for duration arrays. ## ## @code{@var{TF} = ne (@var{A}, @var{B})} is the equivalent of the syntax ## @code{@var{TF} = @var{A} != @var{B}} and returns a logical array whose ## elements set to @qcode{true} where the corresponding elements of @var{A} ## and @var{B} are not equal and set to @qcode{false} otherwise. ## ## @var{A} and @var{B} must be size compatible, which translates to they can ## be the same size, one can be scalar, or for every dimension, their ## dimension sizes must be equal or one of them must be 1. The size of ## @var{C} is determined by the size compatibility of @var{A} and @var{B}. ## ## One of the input arguments can also be a character vector, a cell array ## of character vectors, or a string array representing duration strings or ## a numeric array representing days. ## ## @end deftypefn function TF = ne (A, B) [A, B] = promote (A, B); TF = A.Days != B.Days; endfunction endmethods ################################################################################ ## ** Mathematical Operations ** ## ################################################################################ ## Available Methods ## ## ## ## 'abs' 'plus' 'uplus' 'minus' ## ## 'uminus' 'times' 'mtimes' 'ldivide' ## ## 'rdivide' 'colon' 'linspace' 'diff' ## ## 'sum' 'cumsum' 'min' 'cummin' ## ## 'max' 'cummax' 'floor' 'ceil' ## ## 'round' 'sign' ## ## ## ################################################################################ methods (Access = public) ## -*- texinfo -*- ## @deftypefn {duration} {@var{B} =} abs (@var{A}) ## ## Absolute value of the elements of a duration array. ## ## @code{@var{B} = abs (@var{A})} returns the absolute value of each element ## in the duration array @var{A}. The returned duration array @var{B} has ## the same size as the input array @var{A}. ## ## @end deftypefn function this = abs (this) this.Days = abs (this.Days); endfunction ## -*- texinfo -*- ## @deftypefn {duration} {@var{C} =} plus (@var{A}, @var{B}) ## ## Addition for duration arrays. ## ## @code{@var{C} = plus (@var{A}, @var{B})} is the equivalent of the syntax ## @code{@var{C} = @var{A} + @var{B}} and returns the sum of @var{A} and ## @var{B} by adding the corresponding elements. @var{A} and @var{B} must ## be size compatible, which translates to they can be the same size, one ## can be scalar, or for every dimension, their dimension sizes must be ## equal or one of them must be 1. The size of @var{C} is determined by the ## size compatibility of @var{A} and @var{B}. ## ## One of the input arguments can also be a numeric array in which case its ## elements are treated as a number of 24-hour days. If the second argument ## @var{B} is a calendarDuration arrays, then the returned array @var{C} is ## also a calendarDuration array. ## ## @end deftypefn function C = plus (A, B) ## Overload methods for certain data types if (isa (B, 'datetime')) C = B + A; return; endif if (isa (B, 'calendarDuration')) C = B + A; return; endif if (isa (A, 'duration') && isa (B, 'duration')) C = A; C.Days = A.Days + B.Days; elseif (isnumeric (A)) C = B; C.Days = double (A) + B.Days; elseif (isnumeric (B)) C = A; C.Days = A.Days + double (B); else error (strcat ("duration: addition is not defined between", ... " '%s' and '%s' arrays."), class (A), class (B)); endif C = fix_zero_precision (C); endfunction ## -*- texinfo -*- ## @deftypefn {duration} {@var{B} =} uplus (@var{A}) ## ## Unary plus for duration arrays. ## ## @code{@var{B} = uplus (@var{A})} is the equivalent of the syntax ## @code{@var{B} = + @var{A}} and returns the input array unaltered. ## ## @end deftypefn function this = uplus (this) endfunction ## -*- texinfo -*- ## @deftypefn {duration} {@var{C} =} minus (@var{A}, @var{B}) ## ## Subtraction for duration arrays. ## ## @code{@var{C} = minus (@var{A}, @var{B})} is the equivalent of the syntax ## @code{@var{C} = @var{A} - @var{B}} and returns the subtraction of @var{B} ## from @var{A} by subtracting the corresponding elements. @var{A} and ## @var{B} must be size compatible, which translates to they can be the same ## size, one can be scalar, or for every dimension, their dimension sizes ## must be equal or one of them must be 1. The size of @var{C} is ## determined by the size compatibility of @var{A} and @var{B}. ## ## One of the input arguments can also be a numeric array in which case its ## elements are treated as a number of 24-hour days. If the second argument ## @var{B} is a calendarDuration arrays, then the returned array @var{C} is ## also a calendarDuration array. ## ## @end deftypefn function C = minus (A, B) ## Overload methods for certain data types if (isa (B, 'calendarDuration')) C = -B + A; return; endif if (isa (A, 'duration') && isa (B, 'duration')) C = A; C.Days = A.Days - B.Days; elseif (isnumeric (A)) C = B; C.Days = double (A) - B.Days; elseif (isnumeric (B)) C = A; C.Days = A.Days - double (B); else error (strcat ("duration: subtraction is not defined between", ... " '%s' and '%s' arrays."), class (A), class (B)); endif C = fix_zero_precision (C); endfunction ## -*- texinfo -*- ## @deftypefn {duration} {@var{B} =} uminus (@var{A}) ## ## Unary minus for duration arrays. ## ## @code{@var{B} = uminus (@var{A})} is the equivalent of the syntax ## @code{@var{B} = - @var{A}} and returns the input array with its elements ## negated. ## ## @end deftypefn function this = uminus (this) this.Days = - this.Days; endfunction ## -*- texinfo -*- ## @deftypefn {duration} {@var{C} =} times (@var{A}, @var{B}) ## ## Element-wise multiplication for duration arrays. ## ## @code{@var{C} = times (@var{A}, @var{B})} is the equivalent of the syntax ## @code{@var{C} = @var{A} .* @var{B}} and returns the element-by-element ## multiplication product between the corresponding elements of input arrays ## @var{A} and @var{B}, one of which must be a numeric array and the other a ## duration array. ## ## @var{A} and @var{B} must be size compatible, which translates to they ## can be the same size, one can be scalar, or for every dimension, their ## dimension sizes must be equal or one of them must be 1. The size of ## @var{C} is determined by the size compatibility of @var{A} and @var{B}. ## ## @end deftypefn function C = times (A, B) if (isa (A, 'duration') && isnumeric (B)) C = A; C.Days = A.Days .* double (B); elseif (isnumeric (A) && isa (B, 'duration')) C = B; C.Days = double (A) .* B.Days; else error (strcat ("duration: multiplication is not defined between", ... " '%s' and '%s' arrays."), class (A), class (B)); endif endfunction ## -*- texinfo -*- ## @deftypefn {duration} {@var{C} =} times (@var{A}, @var{B}) ## ## Matrix multiplication for duration arrays. ## ## @code{@var{C} = mtimes (@var{A}, @var{B})} is the equivalent of the ## syntax @code{@var{C} = @var{A} * @var{B}} and returns the matrix ## multiplication product of input arrays @var{A} and @var{B}, one of which ## must be a numeric array and the other a duration array. ## ## The columns of @var{A} must equal the rows of @var{B} and the size of ## @var{C} is determined by the rows of @var{A} and the columns of @var{B}. ## ## @end deftypefn function C = mtimes (A, B) if (isa (A, 'duration') && isnumeric (B)) C = A; C.Days = A.Days * double (B); elseif (isnumeric (A) && isa (B, 'duration')) C = B; C.Days = double (A) * B.Days; else error (strcat ("duration: matrix multiplication is not defined", ... " between '%s' and '%s' arrays."), class (A), class (B)); endif endfunction ## -*- texinfo -*- ## @deftypefn {duration} {@var{C} =} ldivide (@var{A}, @var{B}) ## ## Element-wise left division for duration arrays. ## ## @code{@var{C} = ldivide (@var{A}, @var{B})} is the equivalent of the ## syntax @code{@var{C} = @var{A} .\ @var{B}} and returns the element-wise ## division of the duration array @var{B} by the corresponding elements of ## input array @var{A}, which can either be a duration or a numeric array. ## If @var{A} is a duration array, then @var{C} is a double numeric array. ## If @var{A} is a numeric array, then @var{C} is a duration array. ## ## @var{A} and @var{B} must be size compatible, which translates to they ## can be the same size, one can be scalar, or for every dimension, their ## dimension sizes must be equal or one of them must be 1. The size of ## @var{C} is determined by the size compatibility of @var{A} and @var{B}. ## ## @end deftypefn function C = ldivide (A, B) if (! isa (B, 'duration')) error (strcat ("duration: right-hand side must be a duration", ... " array for left division: got '%s'"), class (B)); endif if (isa (A, 'duration')) C = A.Days .\ B.Days; elseif (isnumeric (A)) C = B; C.Days = double (A) .\ B.Days; C = fix_zero_precision (C); else error (strcat ("duration: left division is not defined", ... " between '%s' and 'duration' arrays."), class (A)); endif endfunction ## -*- texinfo -*- ## @deftypefn {duration} {@var{C} =} rdivide (@var{A}, @var{B}) ## ## Element-wise right division for duration arrays. ## ## @code{@var{C} = rdivide (@var{A}, @var{B})} is the equivalent of the ## syntax @code{@var{C} = @var{A} ./ @var{B}} and returns the element-wise ## division of the duration array @var{A} by the corresponding elements of ## input array @var{B}, which can either be a duration or a numeric array. ## If @var{B} is a duration array, then @var{C} is a double numeric array. ## If @var{B} is a numeric array, then @var{C} is a duration array. ## ## @var{A} and @var{B} must be size compatible, which translates to they ## can be the same size, one can be scalar, or for every dimension, their ## dimension sizes must be equal or one of them must be 1. The size of ## @var{C} is determined by the size compatibility of @var{A} and @var{B}. ## ## @end deftypefn function C = rdivide (A, B) if (! isa (A, 'duration')) error (strcat ("duration: left-hand side must be a duration", ... " array for right division: got '%s'"), class (A)); endif if (isa (B, 'duration')) C = A.Days ./ B.Days; elseif (isnumeric (B)) C = A; C.Days = A.Days ./ double (B); C = fix_zero_precision (C); else error (strcat ("duration: right division is not defined", ... " between 'duration' and '%s' arrays."), class (B)); endif endfunction ## -*- texinfo -*- ## @deftypefn {duration} {@var{R} =} colon (@var{Base}, @var{Limit}) ## @deftypefnx {duration} {@var{R} =} colon (@var{Base}, @var{Increment}, @var{Limit}) ## ## Create a range of durations in a vector. ## ## @code{@var{R} = colon (@var{Base}, @var{Limit})} is the equivalent of the ## syntax @code{@var{C} = @var{Base}:@var{Limit}} and returns a duration ## vector in the range from @var{Base} to @var{Limit} incremented by 24-hour ## days. ## ## @code{@var{R} = colon (@var{Base}, @var{Increment}, @var{Limit})} is ## equivalent to @code{@var{C} = @var{Base}:@var{Increment}:@var{Limit}}. ## dimension sizes must be equal or one of them must be 1. The size of ## @var{C} is determined by the size compatibility of @var{A} and @var{B}. ## ## As long as one of the inputs is a duration scalar, the following types ## are additionally supported for the remaining input arguments: ## ## @itemize ## @item numeric scalar (24-hour day) ## @item character vector (duration string) ## @item cellstr scalar (duration string) ## @item string scalar (duration string) ## @end itemize ## ## @end deftypefn function R = colon (varargin) if (nargin < 2 || nargin > 3) error ("duration.colon: invalid number of input arguments."); endif idx = find (cellfun ('isduration', varargin), 1); R = varargin{idx}; if (nargin == 2) [from, to] = promote (varargin{:}); if (! isscalar (from) || ! isscalar (to)) error ("duration.colon: input arguments must be scalars."); endif increment = days (1); else [from, increment, to] = promote (varargin{:}); if (! isscalar (from) || ! isscalar (increment) || ! isscalar (to)) error ("duration.colon: input arguments must be scalars."); endif endif R.Days = from.Days:increment.Days:to.Days; R = fix_zero_precision (R); endfunction ## -*- texinfo -*- ## @deftypefn {duration} {@var{R} =} linspace (@var{Start}, @var{End}) ## @deftypefnx {duration} {@var{R} =} linspace (@var{Start}, @var{End}, @var{N}) ## ## Create linearly spaced duration elements. ## ## @code{@var{R} = linspace (@var{Start}, @var{End})} returns 100 linearly ## spaced elements between @var{Start} and @var{End}. If @var{Start} and ## @var{End} are scalars, then @var{R} is a vector. If one or both inputs ## are vectors, then @var{R} is a matrix where each row is an independent ## sequence between @code{@var{Start}(idx_N)} and @code{@var{End}(idx_N)}. ## ## @code{@var{R} = linspace (@var{Start}, @var{End}, @var{N})} specifies the ## number (default is 100) of equally spaced elements between @var{Start} ## and @var{End}. If @var{N} is not an integer value, then it is floored ## to the nearest integer. If @var{N} is zero or negative, then an empty ## matrix is returned. If @var{N} is one, then @var{End} is returned. ## If @var{N} greater than one, then @var{Start} and @var{End} are always ## included in the range. ## ## Either @var{Start} or @var{End} input arguments can also be one of the ## following types: ## ## @itemize ## @item numeric scalar or vector (24-hour days) ## @item character vector (duration string) ## @item cellstr scalar or vector (duration strings) ## @item string scalar or vector (duration strings) ## @end itemize ## ## @end deftypefn function R = linspace (A, B, n = 100) if (nargin < 2) error ("duration.linspace: too few input arguments."); endif if (isduration (A)) R = A; else R = B; endif [A, B] = promote (A, B); R.Days = linspace (A.Days, B.Days, n); R = fix_zero_precision (R); endfunction ## -*- texinfo -*- ## @deftypefn {duration} {@var{DT} =} diff (@var{D}) ## @deftypefnx {duration} {@var{DT} =} diff (@var{D}, @var{K}) ## @deftypefnx {duration} {@var{DT} =} diff (@var{D}, @var{K}, @var{DIM}) ## ## Compute differences between adjacent elements in a duration array. ## ## This method overloads the core @code{diff} function for duration arrays. ## The functionality is identical to core @code{diff} function. Type ## @code{help diff} for more information. ## ## @end deftypefn function this = diff (this, varargin) this.Days = diff (this.Days, varargin{:}); this = fix_zero_precision (this); endfunction ## -*- texinfo -*- ## @deftypefn {duration} {@var{S} =} sum (@var{D}) ## @deftypefnx {duration} {@var{S} =} sum (@var{D}, @var{dim}) ## @deftypefnx {duration} {@var{S} =} sum (@var{D}, @var{vecdim}) ## @deftypefnx {duration} {@var{S} =} sum (@var{D}, @qcode{'all'}) ## @deftypefnx {duration} {@var{S} =} sum (@dots{}, @var{nanflag}) ## ## Compute the sum of the elements of a duration array. ## ## This method overloads the core @code{sum} function for duration arrays. ## The functionality is identical to core @code{sum} function. Type ## @code{help sum} for more information. ## ## @end deftypefn function this = sum (this, varargin) ## Force strings to character vectors or cell arrays of character vectors if (any (cellfun ('isstring', varargin))) [varargin{:}] = convertStringsToChars (varargin{:}); endif this.Days = sum (this.Days, varargin{:}); this = fix_zero_precision (this); endfunction ## -*- texinfo -*- ## @deftypefn {duration} {@var{CS} =} cumsum (@var{D}) ## @deftypefnx {duration} {@var{CS} =} cumsum (@var{D}, @var{dim}) ## @deftypefnx {duration} {@var{CS} =} cumsum (@var{D}, @var{vecdim}) ## @deftypefnx {duration} {@var{CS} =} cumsum (@var{D}, @qcode{'all'}) ## @deftypefnx {duration} {@var{CS} =} cumsum (@dots{}, @var{direction}) ## @deftypefnx {duration} {@var{CS} =} cumsum (@dots{}, @var{nanflag}) ## ## Compute the cumulative sum of the elements of a duration array. ## ## This method overloads the core @code{cumsum} function for duration ## arrays. The functionality is identical to core @code{cumsum} function. ## Type @code{help cumsum} for more information. ## ## @end deftypefn function this = cumsum (this, varargin) ## Force strings to character vectors or cell arrays of character vectors if (any (cellfun ('isstring', varargin))) [varargin{:}] = convertStringsToChars (varargin{:}); endif this.Days = cumsum (this.Days, varargin{:}); this = fix_zero_precision (this); endfunction ## -*- texinfo -*- ## @deftypefn {duration} {@var{M} =} min (@var{D}) ## @deftypefnx {duration} {@var{M} =} min (@var{D}, @qcode{[]}, @var{dim}) ## @deftypefnx {duration} {@var{M} =} min (@var{D}, @qcode{[]}, @var{vecdim}) ## @deftypefnx {duration} {@var{M} =} min (@var{D}, @qcode{[]}, @qcode{'all'}) ## @deftypefnx {duration} {@var{M} =} min (@var{D}, @qcode{[]}, @var{nanflag}) ## @deftypefnx {duration} {@var{M} =} min (@var{D}, @qcode{[]}, @dots{}, @var{nanflag}) ## @deftypefnx {duration} {[@var{M}, @var{index}] =} min (@dots{}) ## @deftypefnx {duration} {[@var{M}, @var{index}] =} min (@dots{}, @qcode{'linear'}) ## @deftypefnx {duration} {@var{M} =} min (@var{D1}, @var{D2}) ## @deftypefnx {duration} {@var{M} =} min (@var{D1}, @var{D2}, @var{nanflag}) ## @deftypefnx {duration} {@dots{} =} min (@dots{}, @qcode{'ComparisonMethod'}, @var{method}) ## ## Find minimum values in duration arrays. ## ## This method overloads the core @code{min} function for duration ## arrays. The functionality is identical to core @code{min} function. ## Type @code{help min} for more information. ## ## @end deftypefn function varargout = min (D, varargin) M = D; if (isempty (varargin)) if (nargout > 1) [M.Days, varargout{2}] = min (D.Days); else M.Days = min (D.Days); endif varargout{1} = M; else ## Force strings to character vectors or cell arrays of character ## vectors if (any (cellfun ('isstring', varargin))) [varargin{:}] = convertStringsToChars (varargin{:}); endif ## Second argument is a duration if (isduration (varargin{1})) D2 = varargin{1}; varargin(1) = []; ## Make sure first argument is also a duration if (! isduration (D)) D = promote (D); M = D2; endif M.Days = min (D.Days, D2.Days, varargin{:}); varargout{1} = M; else if (nargout > 1) [M.Days, varargout{2}] = min (D.Days, varargin{:}); else M.Days = min (D.Days, varargin{:}); endif varargout{1} = M; endif endif endfunction ## -*- texinfo -*- ## @deftypefn {duration} {@var{M} =} cummin (@var{D}) ## @deftypefnx {duration} {@var{M} =} cummin (@var{D}, @var{dim}) ## @deftypefnx {duration} {@var{M} =} cummin (@var{D}, @var{vecdim}) ## @deftypefnx {duration} {@var{M} =} cummin (@var{D}, @qcode{'all'}) ## @deftypefnx {duration} {@var{M} =} cummin (@qcode{[]}, @var{nanflag}) ## @deftypefnx {duration} {@var{M} =} cummin (@qcode{[]}, @var{direction}) ## @deftypefnx {duration} {[@var{M}, @var{index}] =} cummin (@dots{}) ## @deftypefnx {duration} {[@var{M}, @var{index}] =} cummin (@dots{}, @qcode{'linear'}) ## @deftypefnx {duration} {@dots{} =} cummin (@dots{}, @qcode{'ComparisonMethod'}, @var{method}) ## ## Return the cumulative minimum values in duration arrays. ## ## This method overloads the core @code{cummin} function for duration ## arrays. The functionality is identical to core @code{cummin} function. ## Type @code{help cummin} for more information. ## ## @end deftypefn function varargout = cummin (this, varargin) if (isempty (varargin)) if (nargout > 1) [this.Days, varargout{2}] = cummin (this.Days); else this.Days = cummin (this.Days); endif varargout{1} = this; else ## Force strings to character vectors or cell arrays of character ## vectors if (any (cellfun ('isstring', varargin))) [varargin{:}] = convertStringsToChars (varargin{:}); endif if (nargout > 1) [this.Days, varargout{2}] = cummin (this.Days, varargin{:}); else this.Days = cummin (this.Days, varargin{:}); endif varargout{1} = this; endif endfunction ## -*- texinfo -*- ## @deftypefn {duration} {@var{M} =} max (@var{D}) ## @deftypefnx {duration} {@var{M} =} max (@var{D}, @qcode{[]}, @var{dim}) ## @deftypefnx {duration} {@var{M} =} max (@var{D}, @qcode{[]}, @var{vecdim}) ## @deftypefnx {duration} {@var{M} =} max (@var{D}, @qcode{[]}, @qcode{'all'}) ## @deftypefnx {duration} {@var{M} =} max (@var{D}, @qcode{[]}, @var{nanflag}) ## @deftypefnx {duration} {@var{M} =} max (@var{D}, @qcode{[]}, @dots{}, @var{nanflag}) ## @deftypefnx {duration} {[@var{M}, @var{index}] =} max (@dots{}) ## @deftypefnx {duration} {[@var{M}, @var{index}] =} max (@dots{}, @qcode{'linear'}) ## @deftypefnx {duration} {@var{M} =} max (@var{D1}, @var{D2}) ## @deftypefnx {duration} {@var{M} =} max (@var{D1}, @var{D2}, @var{nanflag}) ## @deftypefnx {duration} {@dots{} =} max (@dots{}, @qcode{'ComparisonMethod'}, @var{method}) ## ## Find maximum values in duration arrays. ## ## This method overloads the core @code{max} function for duration ## arrays. The functionality is identical to core @code{max} function. ## Type @code{help max} for more information. ## ## @end deftypefn function varargout = max (D, varargin) M = D; if (isempty (varargin)) if (nargout > 1) [M.Days, varargout{2}] = max (D.Days); else M.Days = max (D.Days); endif varargout{1} = M; else ## Force strings to character vectors or cell arrays of character ## vectors if (any (cellfun ('isstring', varargin))) [varargin{:}] = convertStringsToChars (varargin{:}); endif ## Second argument is a duration if (isduration (varargin{1})) D2 = varargin{1}; varargin(1) = []; ## Make sure first argument is also a duration if (! isduration (D)) D = promote (D); M = D2; endif M.Days = max (D.Days, D2.Days, varargin{:}); varargout{1} = M; else if (nargout > 1) [M.Days, varargout{2}] = max (D.Days, varargin{:}); else M.Days = max (D.Days, varargin{:}); endif varargout{1} = M; endif endif endfunction ## -*- texinfo -*- ## @deftypefn {duration} {@var{M} =} cummax (@var{D}) ## @deftypefnx {duration} {@var{M} =} cummax (@var{D}, @var{dim}) ## @deftypefnx {duration} {@var{M} =} cummax (@var{D}, @var{vecdim}) ## @deftypefnx {duration} {@var{M} =} cummax (@var{D}, @qcode{'all'}) ## @deftypefnx {duration} {@var{M} =} cummax (@qcode{[]}, @var{nanflag}) ## @deftypefnx {duration} {@var{M} =} cummax (@qcode{[]}, @var{direction}) ## @deftypefnx {duration} {[@var{M}, @var{index}] =} cummax (@dots{}) ## @deftypefnx {duration} {[@var{M}, @var{index}] =} cummax (@dots{}, @qcode{'linear'}) ## @deftypefnx {duration} {@dots{} =} cummax (@dots{}, @qcode{'ComparisonMethod'}, @var{method}) ## ## Return the cumulative maximum values in duration arrays. ## ## This method overloads the core @code{cummax} function for duration ## arrays. The functionality is identical to core @code{cummax} function. ## Type @code{help cummax} for more information. ## ## @end deftypefn function varargout = cummax (this, varargin) if (isempty (varargin)) if (nargout > 1) [this.Days, varargout{2}] = cummax (this.Days); else this.Days = cummax (this.Days); endif varargout{1} = this; else ## Force strings to character vectors or cell arrays of character ## vectors if (any (cellfun ('isstring', varargin))) [varargin{:}] = convertStringsToChars (varargin{:}); endif if (nargout > 1) [this.Days, varargout{2}] = cummax (this.Days, varargin{:}); else this.Days = cummax (this.Days, varargin{:}); endif varargout{1} = this; endif endfunction ## -*- texinfo -*- ## @deftypefn {duration} {@var{Y} =} floor (@var{D}) ## @deftypefnx {duration} {@var{Y} =} floor (@var{D}, @var{unit}) ## ## Round toward negative infinity for duration arrays. ## ## @code{@var{Y} = floor (@var{D})} rounds each element of the duration ## array @var{D} to the largest integer number of seconds not greater than ## that element. ## ## @code{@var{Y} = floor (@var{D}, @var{unit})} rounds each element of the ## duration array @var{D} to the largest integer number of the specified ## unit of time not greater than that element. @var{unit} must be one of ## the following values: ## ## @itemize ## @item @qcode{'seconds'} (default) ## @item @qcode{'minutes'} ## @item @qcode{'hours'} ## @item @qcode{'days'} ## @item @qcode{'years'} ## @end itemize ## ## @end deftypefn function this = floor (this, unit = 'seconds') if (strcmpi (unit, 'seconds')) this.Days = floor (seconds (this)) / 86400; elseif (strcmpi (unit, 'minutes')) this.Days = floor (minutes (this)) / 1440; elseif (strcmpi (unit, 'hours')) this.Days = floor (hours (this)) / 24; elseif (strcmpi (unit, 'days')) this.Days = floor (this.Days); elseif (strcmpi (unit, 'years')) this.Days = floor (years (this)) * 365.2425; else error ("duration.floor: invalid UNIT."); endif endfunction ## -*- texinfo -*- ## @deftypefn {duration} {@var{Y} =} ceil (@var{D}) ## @deftypefnx {duration} {@var{Y} =} ceil (@var{D}, @var{unit}) ## ## Round toward negative infinity for duration arrays. ## ## @code{@var{Y} = ceil (@var{D})} rounds each element of the duration ## array @var{D} to the smallest integer number of seconds not less than ## that element. ## ## @code{@var{Y} = ceil (@var{D}, @var{unit})} rounds each element of the ## duration array @var{D} to the smallest integer number of the specified ## unit of time not less than that element. @var{unit} must be one of the ## following values: ## ## @itemize ## @item @qcode{'seconds'} (default) ## @item @qcode{'minutes'} ## @item @qcode{'hours'} ## @item @qcode{'days'} ## @item @qcode{'years'} ## @end itemize ## ## @end deftypefn function this = ceil (this, unit = 'seconds') if (strcmpi (unit, 'seconds')) this.Days = ceil (seconds (this)) / 86400; elseif (strcmpi (unit, 'minutes')) this.Days = ceil (minutes (this)) / 1440; elseif (strcmpi (unit, 'hours')) this.Days = ceil (hours (this)) / 24; elseif (strcmpi (unit, 'days')) this.Days = ceil (this.Days); elseif (strcmpi (unit, 'years')) this.Days = ceil (years (this)) * 365.2425; else error ("duration.ceil: invalid UNIT."); endif endfunction ## -*- texinfo -*- ## @deftypefn {duration} {@var{Y} =} round (@var{D}) ## @deftypefnx {duration} {@var{Y} =} round (@var{D}, @var{unit}) ## ## Round to the nearest integer time unit for duration arrays. ## ## @code{@var{Y} = round (@var{D})} rounds each element of the duration ## array @var{D} to the nearest integer number of seconds to that element. ## In case of a tie, return the one further away from zero. ## ## @code{@var{Y} = round (@var{D}, @var{unit})} rounds each element of the ## duration array @var{D} to the nearest integer number of the specified ## unit of time to that element. @var{unit} must be one of the following ## values: ## ## @itemize ## @item @qcode{'seconds'} (default) ## @item @qcode{'minutes'} ## @item @qcode{'hours'} ## @item @qcode{'days'} ## @item @qcode{'years'} ## @end itemize ## ## @end deftypefn function this = round (this, unit = 'seconds') if (strcmpi (unit, 'seconds')) this.Days = round (seconds (this)) / 86400; elseif (strcmpi (unit, 'minutes')) this.Days = round (minutes (this)) / 1440; elseif (strcmpi (unit, 'hours')) this.Days = round (hours (this)) / 24; elseif (strcmpi (unit, 'days')) this.Days = round (this.Days); elseif (strcmpi (unit, 'years')) this.Days = round (years (this)) * 365.2425; else error ("duration.round: invalid UNIT."); endif endfunction ## -*- texinfo -*- ## @deftypefn {duration} {@var{out} =} sign (@var{D}) ## @deftypefnx {duration} {@var{out} =} sign (@var{D}, @var{unit}) ## ## Compute the signum function for duration arrays. ## ## @code{@var{out} = sign (@var{D})} returns an array of doubles, @var{out}, ## the same size as the duration array @var{D}, where each element has one ## following values: ## ## @itemize ## @item @qcode{1} if the corresponding element of D is greater than 0. ## @item @qcode{0} if the corresponding element of D is equal to 0. ## @item @qcode{-1} if the corresponding element of D is less than 0. ## @item @qcode{NaN} if the corresponding element of D is a missing value. ## @end itemize ## ## @end deftypefn function out = sign (this) out = sign (this.Days); endfunction endmethods ################################################################################ ## ** Statistical Operations ** ## ################################################################################ ## Available Methods ## ## ## ## 'bounds' 'center' 'histc' 'iqr' ## ## 'kurtosis' 'mad' 'mape' 'mean' ## ## 'median' 'mode' 'prctile' 'quantile' ## ## 'range' 'rmse' 'skewness' 'std' ## ## ## ################################################################################ methods (Access = public) ## -*- texinfo -*- ## @deftypefn {duration} {[@var{s}, @var{l}] =} bounds (@var{D}) ## @deftypefnx {duration} {[@var{s}, @var{l}] =} bounds (@var{D}, @var{dim}) ## @deftypefnx {duration} {[@var{s}, @var{l}] =} bounds (@var{D}, @var{vecdim}) ## @deftypefnx {duration} {[@var{s}, @var{l}] =} bounds (@var{D}, @qcode{'all'}) ## @deftypefnx {duration} {[@var{s}, @var{l}] =} bounds (@dots{}, @var{nanflag}) ## ## Return the smallest and largest values of a duration array. ## ## This method is a specialization of the core @code{bounds} function for ## duration arrays. The functionality is identical to core @code{bounds} ## function. Type @code{help bounds} for more information. ## ## @end deftypefn function [s, l] = bounds (this, varargin) if (nargin < 1 || nargin > 3) error ("duration.bounds: invalid number of input arguments."); endif if (isempty (varargin)) s = min (this); l = max (this); else s = min (this, [], varargin{:}); l = max (this, [], varargin{:}); endif endfunction ## -*- texinfo -*- ## @deftypefn {duration} {@var{C} =} center (@var{D}) ## @deftypefnx {duration} {@var{C} =} center (@var{D}, @var{dim}) ## @deftypefnx {duration} {@var{C} =} center (@var{D}, @var{vecdim}) ## @deftypefnx {duration} {@var{C} =} center (@var{D}, @qcode{'all'}) ## @deftypefnx {duration} {@var{C} =} center (@dots{}, @var{nanflag}) ## ## Center values in a duration array. ## ## This method overloads the core @code{center} function for duration ## arrays. The functionality is identical to core @code{center} function. ## Type @code{help center} for more information. ## ## @end deftypefn function this = center (this, varargin) this.Days = center (this.Days, varargin{:}); endfunction ## -*- texinfo -*- ## @deftypefn {duration} {@var{n} =} histc (@var{D}, @var{edges}) ## @deftypefnx {duration} {@var{n} =} histc (@var{D}, @var{edges}, @var{dim}) ## @deftypefnx {duration} {[@var{n}, @var{idx}] =} histc (@dots{}) ## ## Compute histogram counts in a duration array. ## ## This method overloads the core @code{histc} function for duration ## arrays. The functionality is identical to core @code{histc} function. ## Type @code{help histc} for more information. ## ## @end deftypefn function varargout = histc (D, varargin) if (nargout > 1) [varargout{1}, varargout{2}] = histc (D.Days, varargin{:}); else varargout{1} = histc (D.Days, varargin{:}); endif endfunction ## -*- texinfo -*- ## @deftypefn {duration} {@var{r} =} iqr (@var{D}) ## @deftypefnx {duration} {@var{r} =} iqr (@var{D}, @var{dim}) ## @deftypefnx {duration} {@var{r} =} iqr (@var{D}, @var{vecdim}) ## @deftypefnx {duration} {@var{r} =} iqr (@var{D}, @qcode{'all'}) ## @deftypefnx {duration} {[@var{r}, @var{q}] =} iqr (@dots{}) ## ## Compute the interquartile range of a duration array. ## ## This method overloads the core @code{iqr} function for duration arrays. ## The functionality is identical to core @code{iqr} function. ## Type @code{help iqr} for more information. ## ## @end deftypefn function varargout = iqr (this, varargin) if (nargout > 1) [this.Days, varargout{2}] = iqr (this.Days, varargin{:}); else this.Days = iqr (this.Days, varargin{:}); endif varargout{1} = this; endfunction ## -*- texinfo -*- ## @deftypefn {duration} {@var{k} =} kurtosis (@var{D}) ## @deftypefnx {duration} {@var{k} =} kurtosis (@var{D}, @var{flag}) ## @deftypefnx {duration} {@var{k} =} kurtosis (@var{D}, @var{flag}, @var{dim}) ## @deftypefnx {duration} {@var{k} =} kurtosis (@var{D}, @var{flag}, @var{vecdim}) ## @deftypefnx {duration} {@var{k} =} kurtosis (@var{D}, @var{flag}, @qcode{'all'}) ## ## Compute the sample kurtosis of a duration array. ## ## This method overloads the core @code{kurtosis} function for duration ## arrays. The functionality is identical to core @code{kurtosis} function. ## Type @code{help kurtosis} for more information. ## ## Note that kurtosis is a dimensionless quantity Thus, the returned ## argument is a numeric array of double type and not a duration array. ## ## @end deftypefn function k = kurtosis (D, varargin) k = kurtosis (D.Days, varargin{:}); endfunction ## -*- texinfo -*- ## @deftypefn {duration} {@var{M} =} mad (@var{D}) ## @deftypefnx {duration} {@var{M} =} mad (@var{D}, @var{opt}) ## @deftypefnx {duration} {@var{M} =} mad (@var{D}, @var{opt}, @var{dim}) ## @deftypefnx {duration} {@var{M} =} mad (@var{D}, @var{opt}, @var{vecdim}) ## @deftypefnx {duration} {@var{M} =} mad (@var{D}, @var{opt}, @qcode{'all'}) ## ## Compute the mean or median absolute deviation of a duration array. ## ## This method overloads the core @code{mad} function for duration arrays. ## The functionality is identical to core @code{mad} function. ## Type @code{help mad} for more information. ## ## @end deftypefn function this = mad (this, varargin) this.Days = mad (this.Days, varargin{:}); endfunction ## -*- texinfo -*- ## @deftypefn {duration} {@var{E} =} mape (@var{F}, @var{A}) ## @deftypefnx {duration} {@var{E} =} mape (@var{F}, @var{A}, @var{dim}) ## @deftypefnx {duration} {@var{E} =} mape (@var{F}, @var{A}, @var{vecdim}) ## @deftypefnx {duration} {@var{E} =} mape (@var{F}, @var{A}, @qcode{'all'}) ## @deftypefnx {duration} {@var{E} =} mape (@dots{}, @var{nanflag}) ## @deftypefnx {duration} {@var{E} =} mape (@dots{}, @var{zeroflag}) ## @deftypefnx {duration} {@var{E} =} mape (@dots{}, @qcode{'Weights'}, @var{W}) ## ## Compute the mean absolute percentage error between duration arrays. ## ## This method overloads the core @code{mape} function for duration arrays. ## The functionality is identical to core @code{mape} function. ## Type @code{help mape} for more information. ## ## Note that MAPE is expressed as a percentage. Thus, the returned argument ## is a numeric array of double type and not a duration array. However, ## both @var{F} and @var{A} must be duration arrays. ## ## @end deftypefn function E = mape (F, A, varargin) if (! isduration (F) || ! isduration (A)) error ("duration.mape: both F and A must be duration arrays."); endif E = mape (F.Days, A.Days, varargin{:}); endfunction ## -*- texinfo -*- ## @deftypefn {duration} {@var{M} =} mean (@var{D}) ## @deftypefnx {duration} {@var{M} =} mean (@var{D}, @var{dim}) ## @deftypefnx {duration} {@var{M} =} mean (@var{D}, @var{vecdim}) ## @deftypefnx {duration} {@var{M} =} mean (@var{D}, @qcode{'all'}) ## @deftypefnx {duration} {@var{M} =} mean (@dots{}, @var{nanflag}) ## @deftypefnx {duration} {@var{M} =} mean (@dots{}, @var{outtype}) ## @deftypefnx {duration} {@var{M} =} mean (@dots{}, @qcode{'Weights'}, @var{W}) ## ## Compute the mean of the elements of a duration array. ## ## This method overloads the core @code{mean} function for duration arrays. ## The functionality is identical to core @code{mean} function. ## Type @code{help mean} for more information. ## ## @end deftypefn function this = mean (this, varargin) this.Days = mean (this.Days, varargin{:}); endfunction ## -*- texinfo -*- ## @deftypefn {duration} {@var{M} =} median (@var{D}) ## @deftypefnx {duration} {@var{M} =} median (@var{D}, @var{dim}) ## @deftypefnx {duration} {@var{M} =} median (@var{D}, @var{vecdim}) ## @deftypefnx {duration} {@var{M} =} median (@var{D}, @qcode{'all'}) ## @deftypefnx {duration} {@var{M} =} median (@dots{}, @var{nanflag}) ## @deftypefnx {duration} {@var{M} =} median (@dots{}, @var{outtype}) ## ## Compute the median value of the elements of a duration array. ## ## This method overloads the core @code{median} function for duration ## arrays. The functionality is identical to core @code{median} function. ## Type @code{help median} for more information. ## ## @end deftypefn function this = median (this, varargin) this.Days = median (this.Days, varargin{:}); endfunction ## -*- texinfo -*- ## @deftypefn {duration} {@var{M} =} mode (@var{D}) ## @deftypefnx {duration} {@var{M} =} mode (@var{D}, @var{dim}) ## @deftypefnx {duration} {@var{M} =} mode (@var{D}, @var{vecdim}) ## @deftypefnx {duration} {@var{M} =} mode (@var{D}, @qcode{'all'}) ## @deftypefnx {duration} {[@var{M}, @var{F}, @var{C}] =} mode (@dots{}) ## ## Compute the most frequently occurring value in a duration array. ## ## This method overloads the core @code{mode} function for duration arrays. ## The functionality is identical to core @code{mode} function. ## Type @code{help mode} for more information. ## ## @end deftypefn function [this, F, C] = mode (this, varargin) [this.Days, F, C] = mode (this.Days, varargin{:}); if (nargout == 3) C = cellfun ('days', C, 'UniformOutput', false); endif endfunction ## -*- texinfo -*- ## @deftypefn {duration} {@var{Q} =} prctile (@var{D}) ## @deftypefnx {duration} {@var{q} =} prctile (@var{D}, @var{p}) ## @deftypefnx {duration} {@var{Q} =} prctile (@var{D}, @var{p}, @var{dim}) ## @deftypefnx {duration} {@var{Q} =} prctile (@var{D}, @var{p}, @var{vecdim}) ## @deftypefnx {duration} {@var{Q} =} prctile (@var{D}, @var{p}, @qcode{'all'}) ## @deftypefnx {duration} {@var{Q} =} prctile (@var{D}, @var{p}, @dots{}, @var{method}) ## ## Compute the percentiles of a duration array. ## ## This method overloads the core @code{prctile} function for duration ## arrays. The functionality is identical to core @code{prctile} function. ## Type @code{help prctile} for more information. ## ## @end deftypefn function this = prctile (this, varargin) this.Days = prctile (this.Days, varargin{:}); endfunction ## -*- texinfo -*- ## @deftypefn {duration} {@var{Q} =} quantile (@var{D}) ## @deftypefnx {duration} {@var{Q} =} quantile (@var{D}, @var{p}) ## @deftypefnx {duration} {@var{Q} =} quantile (@var{D}, @var{n}) ## @deftypefnx {duration} {@var{Q} =} quantile (@var{D}, @dots{}, @var{dim}) ## @deftypefnx {duration} {@var{Q} =} quantile (@var{D}, @dots{}, @var{vecdim}) ## @deftypefnx {duration} {@var{Q} =} quantile (@var{D}, @dots{}, @qcode{'all'}) ## @deftypefnx {duration} {@var{Q} =} quantile (@var{D}, @var{p}, @dots{}, @var{method}) ## @deftypefnx {duration} {@var{Q} =} quantile (@var{D}, @var{n}, @dots{}, @var{method}) ## ## Compute the quantiles of a duration array. ## ## This method overloads the core @code{quantile} function for duration ## arrays. The functionality is identical to core @code{quantile} function. ## Type @code{help quantile} for more information. ## ## @end deftypefn function this = quantile (this, varargin) this.Days = quantile (this.Days, varargin{:}); endfunction ## -*- texinfo -*- ## @deftypefn {duration} {@var{R} =} range (@var{D}) ## @deftypefnx {duration} {@var{R} =} range (@var{D}, @var{dim}) ## @deftypefnx {duration} {@var{R} =} range (@var{D}, @var{vecdim}) ## @deftypefnx {duration} {@var{R} =} range (@var{D}, @qcode{'all'}) ## @deftypefnx {duration} {@var{R} =} range (@dots{}, @var{nanflag}) ## ## Compute the range of a duration array. ## ## This method overloads the core @code{range} function for duration arrays. ## The functionality is identical to core @code{range} function. ## Type @code{help range} for more information. ## ## @end deftypefn function this = range (this, varargin) this.Days = range (this.Days, varargin{:}); endfunction ## -*- texinfo -*- ## @deftypefn {duration} {@var{E} =} rmse (@var{F}, @var{A}) ## @deftypefnx {duration} {@var{E} =} rmse (@var{F}, @var{A}, @var{dim}) ## @deftypefnx {duration} {@var{E} =} rmse (@var{F}, @var{A}, @var{vecdim}) ## @deftypefnx {duration} {@var{E} =} rmse (@var{F}, @var{A}, @qcode{'all'}) ## @deftypefnx {duration} {@var{E} =} rmse (@dots{}, @var{nanflag}) ## @deftypefnx {duration} {@var{E} =} rmse (@dots{}, @qcode{'Weights'}, @var{W}) ## ## Compute the root mean squared error between duration arrays. ## ## This method overloads the core @code{rmse} function for duration arrays. ## The functionality is identical to core @code{rmse} function. ## Type @code{help rmse} for more information. ## ## @end deftypefn function this = rmse (this, A, varargin) if (! isduration (this) || ! isduration (A)) error ("duration.rmse: both F and A must be duration arrays."); endif this.Days = rmse (this.Days, A.Days, varargin{:}); endfunction ## -*- texinfo -*- ## @deftypefn {duration} {@var{y} =} skewness (@var{D}) ## @deftypefnx {duration} {@var{y} =} skewness (@var{D}, @var{flag}) ## @deftypefnx {duration} {@var{y} =} skewness (@var{D}, @var{flag}, @var{dim}) ## @deftypefnx {duration} {@var{y} =} skewness (@var{D}, @var{flag}, @var{vecdim}) ## @deftypefnx {duration} {@var{y} =} skewness (@var{D}, @var{flag}, @qcode{'all'}) ## ## Compute the sample skewness of a duration array. ## ## This method overloads the core @code{skewness} function for duration ## arrays. The functionality is identical to core @code{skewness} function. ## Type @code{help skewness} for more information. ## ## Note that skewness is a dimensionless quantity Thus, the returned ## argument is a numeric array of double type and not a duration array. ## ## @end deftypefn function y = skewness (D, varargin) y = skewness (D.Days, varargin{:}); endfunction ## -*- texinfo -*- ## @deftypefn {duration} {@var{S} =} std (@var{D}) ## @deftypefnx {duration} {@var{S} =} std (@var{D}, @var{w}) ## @deftypefnx {duration} {@var{S} =} std (@var{D}, @var{w}, @var{dim}) ## @deftypefnx {duration} {@var{S} =} std (@var{D}, @var{w}, @var{vecdim}) ## @deftypefnx {duration} {@var{S} =} std (@var{D}, @var{w}, @qcode{'all'}) ## @deftypefnx {duration} {@var{S} =} std (@dots{}, @var{nanflag}) ## @deftypefnx {duration} {[@var{S}, @var{M}] =} std (@dots{}) ## ## Compute the standard deviation of a duration array. ## ## This method overloads the core @code{std} function for duration arrays. ## The functionality is identical to core @code{std} function. ## Type @code{help std} for more information. ## ## @end deftypefn function varargout = std (this, varargin) if (nargout > 1) M = this; [this.Days, M.Days] = std (this.Days, varargin{:}); varargout{1} = this; varargout{2} = M; else this.Days = std (this.Days, varargin{:}); varargout{1} = this; endif endfunction endmethods ################################################################################ ## ** Sort, Filter, and Set Operations ** ## ################################################################################ ## Available Methods ## ## ## ## 'sort' 'sortrows' 'unique' 'interp1' ## ## 'intersect' 'setdiff' 'setxor' 'union' ## ## ## ################################################################################ methods (Access = public) ## -*- texinfo -*- ## @deftypefn {duration} {@var{B} =} sort (@var{A}) ## @deftypefnx {duration} {@var{B} =} sort (@var{A}, @var{dim}) ## @deftypefnx {duration} {@var{B} =} sort (@var{A}, @var{direction}) ## @deftypefnx {duration} {@var{B} =} sort (@var{A}, @var{dim}, @var{direction}) ## @deftypefnx {duration} {@var{B} =} sort (@dots{}, @qcode{'MissingPlacement'}, @var{MP}) ## @deftypefnx {duration} {@var{B} =} sort (@dots{}, @qcode{'ComparisonMethod'}, @var{CM}) ## @deftypefnx {duration} {[@var{B}, @var{index}] =} sort (@var{A}, @dots{}) ## ## Sort elements in a duration array. ## ## @code{@var{B} = sort (@var{A})} sorts the duration array @var{A} in ## ascending order. If @var{A} is a matrix, @code{sort (@var{A})} sorts ## each column of @var{A} in ascending order. For multidimensional arrays, ## @code{mode (@var{A})} sorts along the first non-singleton dimension. ## ## @code{@var{B} = sort (@var{A}, @var{dim})} sorts along the dimension ## specified by @var{dim}. ## ## @code{@var{B} = sort (@var{A}, @var{direction})} also specifies the ## sorting direction, which can be either @qcode{'ascend'} (default) or ## @qcode{'descend'}. ## ## @code{@var{B} = sort (@dots{}, @qcode{'MissingPlacement'}, @var{MP})} ## specifies where to place the missing elements (@qcode{}) ## returned in @var{B} with one of the following options specified in ## @var{MP}: ## ## @itemize ## @item @qcode{'auto'}, which is the default, places missing elements last ## for ascending sort and first for descending sort. ## @item @qcode{'first'} places missing elements first. ## @item @qcode{'last'} places missing elements last. ## @end itemize ## ## @code{@var{B} = sort (@dots{}, @qcode{'ComparisonMethod'}, @var{CM})} ## specifies the comparison method for determining the order of elements ## returned in @var{B} with one of following options: ## ## @itemize ## @item @qcode{'auto'}, which is the default, sorts by @code{real (A)}. ## @item @qcode{'real'} sorts by @code{real (A)}. ## @item @qcode{'abs'} sorts by @code{abs (A)}. ## @end itemize ## ## @code{[@var{B}, @var{index}] = sort (@var{A}, @dots{})} also returns a ## sorting index containing the original indices of the elements in the ## sorted array. @var{index} is the same size as @var{A} and it comprises ## indexing vectors oriented along the operating dimensions. ## ## @itemize ## @item If @var{A} is a vector, then @var{index} contains the original ## linear indices of the elements in the sorted vector @var{B} such that ## @code{@var{B} = @var{A}(@var{index})}. ## @item If @var{A} is an @math{M*N} matrix and @qcode{@var{dim} = 1}, then ## @var{index} contains the original row indices of the elements in the ## sorted vector @var{B} such that for @qcode{j = 1:N}, ## @code{@var{B}(:,j) = @var{A}(@var{index}(:,j),j)}. ## @end itemize ## ## @end deftypefn function [B, index] = sort (A, varargin) ## Parse and validate optional paired arguments optNames = {'MissingPlacement', 'ComparisonMethod'}; dfValues = {'auto', 'auto'}; [MP, CM, args] = parsePairedArguments (optNames, dfValues, varargin(:)); if (! ismember (MP, {'auto', 'first', 'last'})) error ("duration.sort: invalid value for 'MissingPlacement'."); endif if (! ismember (CM, {'auto', 'real', 'abs'})) error ("duration.sort: invalid value for 'ComparisonMethod'."); endif ## Force strings to character vectors [args{:}] = convertStringsToChars (args{:}); ## Get direction cid = cellfun (@ischar, args); if (any (cid)) dir = args{cid}; else dir = 'ascend'; endif ## Get operating dimension szA = size (A); cid = cellfun (@isnumeric, args); if (any (cid)) dim = args{cid}; else dim = find (szA != 1, 1); if (isempty (dim)) # scalar dim = 1; endif endif ## Apply comparison method (Octave specific) data = A.Days; if (strcmp (CM, 'abs')) data = abs (data); endif ## Special handling for missing elements when missing placement overrides ## default behavior (only if missing data actually exist). is_nan = isnan (data); if (any (is_nan, 'all')) ## FIX ME: this workaround will be removed once the 'sort' function ## in core Octave supports 'MissingPlacement' optional argument. ## This implementation fails the edge case where -Inf and -realmax ## elements are present along the operating dimension. if ((strcmp (dir, 'ascend') && strcmp (MP, {'first'})) || (strcmp (dir, 'descend') && strcmp (MP, {'last'}))) ## Convert missing values to -Inf so that they are placed ## appropriately according to 'MissingPlacement' specification. ## If -Inf values already exist in data, then convert them to the ## next smallest possible value is_m_inf = data == -Inf; if (any (is_m_inf, 'all')) m_inf_rep = - realmax; data(is_m_inf) = m_inf_rep; endif data(is_nan) = -Inf; endif endif ## Sort values [~, index] = sort (data, args{:}); ## Calculate linear index n_dims = ndims (A); dimarg = cell (1, n_dims); for i = 1:n_dims if (i == dim) dimarg{i} = index; else dim_sz = szA(i); tmpvec = ones (1, n_dims); tmpvec(i) = dim_sz; tmp_sz = szA; tmp_sz(i) = 1; dimarg{i} = repmat (reshape ([1:dim_sz], tmpvec), tmp_sz); endif endfor ## Return sorted duration array B = subset (A, sub2ind (szA, dimarg{:})); endfunction ## -*- texinfo -*- ## @deftypefn {duration} {@var{B} =} sortrows (@var{A}) ## @deftypefnx {duration} {@var{B} =} sortrows (@var{A}, @var{col}) ## @deftypefnx {duration} {@var{B} =} sortrows (@var{A}, @var{direction}) ## @deftypefnx {duration} {@var{B} =} sortrows (@var{A}, @var{col}, @var{direction}) ## @deftypefnx {duration} {@var{B} =} sortrows (@dots{}, @qcode{'MissingPlacement'}, @var{MP}) ## @deftypefnx {duration} {[@var{B}, @var{index}] =} sortrows (@var{A}, @dots{}) ## ## Sort rows in a duration array. ## ## @code{@var{B} = sortrows (@var{A})} sorts the rows of the 2-D duration ## array @var{A} in ascending order. The sorted array @var{B} has the same ## size as @var{A}. ## ## @code{@var{B} = sortrows (@var{A}, @var{col})} sorts @var{A} according to ## to the columns specified by the numeric vector @var{col}, which must ## explicitly contain non-zero integers whose absolute values index existing ## columns in @var{A}. Positive elements sort the corresponding columns in ## ascending order, while negative elements sort the corresponding columns ## in descending order. ## ## @code{@var{B} = sortrows (@var{A}, @var{direction})} also specifies the ## sorting direction, which can be either @qcode{'ascend'} (default) or ## @qcode{'descend'} applying to all columns in @var{A}. Alternatively, ## @var{direction} can be either a string array or a cell array of character ## vectors specifying the sorting direction for each individual column of ## @var{A}, in which case the number of elements in @var{direction} must ## equal the number of columns in @var{A}. ## ## @code{@var{B} = sortrows (@var{A}, @var{col}, @var{direction})} sorts the ## categorical array @var{A} according to the columns specified in @var{col} ## using the corresponding sorting direction specified in @var{direction}. ## In this case, the sign of the values in @var{col} is ignored. @var{col} ## and @var{direction} must have the same number of elements, but not ## necessarily equal to the columns of @var{A}. ## ## @code{@var{B} = sortrows (@dots{}, @qcode{'MissingPlacement'}, @var{MP})} ## specifies where to place the missing elements (@qcode{NaN}) returned in ## @var{B} with any of the following options specified in @var{MP}: ## ## @itemize ## @item @qcode{'auto'}, which is the default, places missing elements last ## for ascending sort and first for descending sort. ## @item @qcode{'first'} places missing elements first. ## @item @qcode{'last'} places missing elements last. ## @end itemize ## ## @code{[@var{B}, @var{index}] = sortrows (@var{A}, @dots{})} also returns ## an index vector containing the original row indices of @var{A} in the ## sorted matrix @var{B} such that @code{@var{B} = @var{A}(@var{index},:)}. ## ## @end deftypefn function [B, index] = sortrows (A, varargin) ## Input array must be a matrix if (ndims (A) != 2) error ("duration.sortrows: A must be a 2-D matrix."); endif ## Parse and validate optional paired arguments optNames = {'MissingPlacement', 'ComparisonMethod'}; dfValues = {'auto', 'auto'}; [MP, CM, args] = parsePairedArguments (optNames, dfValues, varargin(:)); if (! any (strcmp (MP, {'auto', 'first', 'last'}))) error ("duration.sortrows: invalid value for 'MissingPlacement'."); endif if (! any (strcmp (CM, {'auto', 'real', 'abs'}))) error ("duration.sortrows: invalid value for 'ComparisonMethod'."); endif ## Parse COL / DIRECTION input nc = size (A, 2); col = [1:nc]; # default ascending direction dir_flag = false; if (numel (args) > 2) error ("duration.sortrows: too many input arguments."); endif if (numel (args) > 0) col = args{1}; if (isnumeric (col)) if (! isvector (col) || any (fix (col) != col) || any (col == 0)) error (strcat ("duration.sortrows: COL must be a vector", ... " of nonzero integers indexing columns in A.")); endif if (max (abs (col)) > nc) error ("duration.sortrows: COL indexes non-existing column."); endif elseif (isvector (col) && (ischar (col) || iscellstr (col) || isa (col, 'string'))) direction = cellstr (col); if (! all (ismember (direction, {'ascend', 'descend'}))) error (strcat ("duration.sortrows: DIRECTION input must", ... " contain either 'ascend' or 'descend' values.")); endif ## Apply scalar expansion if (isscalar (direction)) direction = repmat (direction, 1, nc); endif if (numel (direction) != nc) error (strcat ("duration.sortrows: DIRECTION", ... " does not match the columns in A.")); endif ## Assign DIRECTION to COL col = [1:nc]; idx = strcmp (direction, 'descend'); col(idx) = - col(idx); dir_flag = true; else error ("duration.sortrows: invalid type for COL argument."); endif endif if (numel (args) > 1) if (dir_flag) error ("duration.sortrows: invalid third input argument."); endif if ((isvector (args{2}) && ischar (args{2})) || isa (args{2}, 'string')) direction = cellstr (args{2}); elseif (isvector (args{2}) && iscellstr (args{2})) direction = args{2}; else error ("duration.sortrows: invalid type for DIRECTION argument."); endif if (! all (ismember (direction, {'ascend', 'descend'}))) error (strcat ("duration.sortrows: DIRECTION input must", ... " contain either 'ascend' or 'descend' values.")); endif ## Assign DIRECTION to COL if (isscalar (direction) && strcmp (direction, 'ascend')) col = abs (col); elseif (isscalar (direction) && strcmp (direction, 'descend')) col = - abs (col); else if (numel (direction) != numel (col)) error (strcat ("duration.sortrows: DIRECTION does", ... " not match the elements in COL.")); endif col = abs (col); idx = strcmp (direction, 'descend'); col(idx) = - col(idx); endif endif ## Apply comparison method (Octave specific) data = A.Days; if (strcmp (CM, 'abs')) data = abs (data); endif ## FIX ME: this workaround will be removed once the 'sortrows' function ## in core Octave supports 'MissingPlacement' optional argument. ## This implementation fails the edge case where -Inf and -realmax ## elements are present along the operating dimension. pos_dir = col > 0; neg_dir = col < 0; fix_pos_dir = any (pos_dir) && strcmp (MP, {'first'}); fix_neg_dir = any (neg_dir) && strcmp (MP, {'last'}); if (fix_pos_dir || fix_neg_dir) ## Apply on selected columns if (fix_pos_dir) col_idx = col(pos_dir); else # must be fix_neg_dir col_idx = - col(neg_dir); endif cdata = data(:,col_idx); ## Only if missing data exist in operating columns. is_nan = isnan (cdata); if (any (is_nan, 'all')) ## Convert missing values to -Inf so that they are placed ## appropriately according to 'MissingPlacement' specification. ## If -Inf values already exist in data, then convert them to the ## next smallest possible value is_m_inf = cdata == -Inf; if (any (is_m_inf, 'all')) m_inf_rep = - realmax; cdata(is_m_inf) = m_inf_rep; endif cdata(is_nan) = -Inf; data(:,col_idx) = cdata; ## Sort values [~, index] = sortrows (data, col); else ## Sort values without special handling [~, index] = sortrows (data, col); endif else ## Sort values with no undefined elements [~, index] = sortrows (data, col); endif ## Return sorted duration array B = subset (A, index, ':'); endfunction ## -*- texinfo -*- ## @deftypefn {duration} {@var{B} =} unique (@var{A}) ## @deftypefnx {duration} {@var{B} =} unique (@var{A}, @var{setOrder}) ## @deftypefnx {duration} {@var{B} =} unique (@var{A}, @var{occurrence}) ## @deftypefnx {duration} {@var{B} =} unique (@var{A}, @var{setOrder}, @var{occurrence}) ## @deftypefnx {duration} {@var{B} =} unique (@var{A}, @var{occurrence}, @var{setOrder}) ## @deftypefnx {duration} {@var{B} =} unique (@var{A}, @dots{}, @qcode{'rows'}) ## @deftypefnx {duration} {[@var{B}, @var{ixA}, @var{ixB}] =} unique (@dots{}) ## ## Unique values in a duration array. ## ## @code{@var{B} = unique (@var{A})} returns the unique values of the ## duration array @var{A} in sorted order. ## ## @code{@var{B} = unique (@var{A}, @var{setOrder})} returns the unique ## values of the duration array @var{A} in an order as specified by ## @var{setOrder}, which can be either of the following values: ## ## @itemize ## @item @qcode{'sorted'} (default) returns the unique values sorted in ## ascending order. ## @item @qcode{'stable'} returns the unique values according to their order ## of occurrence. ## @end itemize ## ## @code{@var{B} = unique (@var{A}, @var{occurrence})} returns the unique ## values of the duration array @var{A} according to their order of ## occurrence. @var{occurrence} can be either of the following values: ## ## @itemize ## @item @qcode{'first'} (default) returns the first occurrence of each ## unique value, i.e. the lowest possible indices are returned. ## @item @qcode{'last'} returns the last occurrence of each unique value, ## i.e. the highest possible indices are returned. ## @end itemize ## ## You can specify @var{setOrder} and @var{occurrence} arguments together. ## ## @code{@var{B} = unique (@var{A}, @dots{}, @qcode{'rows'})} returns the ## unique rows of @var{A} by treating each row as a single entity. The ## @qcode{'rows'} option can be used alone or in any combination with the ## @var{setOrder} and @var{occurrence} arguments. @qcode{'rows'} can be ## placed at any position in the function's argument list after the input ## array @var{A}. However, this syntax is only valid for 2-dimensional ## duration arrays. ## ## @code{[@var{B}, @var{ixA}, @var{ixB}] = unique (@dots{})} also returns ## index vectors @var{ixA} and @var{ixB} using any of the previous syntaxes. ## @var{ixA} and @var{ixB} map the arrays @var{A} and @var{B} to one another ## such that @qcode{@var{B} = @var{A}(@var{ixA})} and ## @qcode{@var{A} = @var{B}(@var{ixB})}. When the @qcode{'rows'} optional ## argument is specified, then @qcode{@var{B} = @var{A}(@var{ixA},:)} and ## @qcode{@var{A} = @var{B}(@var{ixB},:)}. ## ## @end deftypefn function [this, ixA, ixB] = unique (this, varargin) ## 'legacy' option is not supported if (any (strcmp ('legacy', varargin))) error ("duration.unique: 'legacy' option is not supported."); endif [this.Days, ixA, ixB] = __unique__ (this.Days, varargin{:}); endfunction ## -*- texinfo -*- ## @deftypefn {duration} {@var{YI} =} interp1 (@var{X}, @var{Y}, @var{XI}) ## @deftypefnx {duration} {@var{YI} =} interp1 (@var{Y}, @var{XI}) ## @deftypefnx {duration} {@var{YI} =} interp1 (@dots{}, @var{method}) ## @deftypefnx {duration} {@var{YI} =} interp1 (@dots{}, @var{method}, @var{extrapolation}) ## @deftypefnx {duration} {@var{pp} =} interp1 (@var{X}, @var{Y}, @qcode{'pp'}) ## @deftypefnx {duration} {@var{pp} =} interp1 (@var{X}, @var{Y}, @var{method}, @qcode{'pp'}) ## ## One-dimensional interpolation for duration arrays. ## ## @code{@var{YI} = interp1 (@var{X}, @var{Y}, @var{XI})} computes the ## linearly interpolated values of a one-dimensional function, which is ## represented by sample points @var{X} and corresponding values @var{Y}, ## at specific query points @var{XI}. @var{X} must be a vector. If @var{Y} ## is vector, then it must have the same length as @var{X}. If @var{Y} is ## matrix, then each column is treated as a different set of one-dimensional ## sample values and the number of rows must equal the length of @var{X}. ## @var{XI} must be a vector and the same data type as @var{X}. The output ## @var{XI} is the same data type as @var{Y} and its size depends on @var{Y} ## and @var{XI}. @var{X} and @var{Y} can both be duration arrays or one of ## them can be a numeric array. ## ## @code{@var{YI} = interp1 (@var{Y}, @var{XI})} computes the linearly ## interpolated values of a one-dimensional function assuming a default set ## of sampling points determined by the shape of @var{Y}: ## ## @itemize ## @item If @var{Y} is a vector, then the default sampling points are ## @code{[1:length(@var{Y})}. ## ## @item If @var{Y} is an array, then the default sampling points are ## @code{[1:size(@var{Y})}. ## @end itemize ## ## @code{@var{YI} = interp1 (@dots{}, @var{method})} specifies one of the ## following interpolation methods to be used: ## ## @itemize ## @item @qcode{'linear'} (default) computes the linear interpolation from ## nearest neighbors. ## ## @item @qcode{'nearest'} returns the nearest neighbor. ## ## @item @qcode{'next'} returns the next neighbor. ## ## @item @qcode{'previous'} returns the previous neighbor. ## ## @item @qcode{'pchip'} computes the piecewise cubic Hermite interpolating ## polynomial, which corresponds to shape-preserving interpolation with ## smooth first derivative. ## ## @item @qcode{'cubic'} computes the cubic interpolation. ## ## @item @qcode{'spline'} computes the cubic spline interpolation, which ## corresponds to smooth first and second derivatives throughout the curve. ## @end itemize ## ## @code{@var{YI} = interp1 (@dots{}, @var{method}, @var{extrapolation})} ## further specifies a strategy for evaluating points that lie outside the ## range of the sample points in @var{X}. Set @var{extrapolation} to ## @qcode{'extrap'} to use the current @var{method} to extrapolate values. ## Set @var{extrapolation} to a scalar value of the same data type as ## @var{Y} to return a constant value outside the range of @var{X}. When ## unspecified, @var{extrapolation} defaults to @qcode{NaN}. ## ## @code{@var{pp} = interp1 (@var{X}, @var{Y}, @qcode{'pp'}} returns a ## piecewise polynomial object, using the default linear interpolation ## algorithm, which can be later used with @code{ppval} to evaluate the ## interpolation at new query points. ## ## @code{@var{pp} = interp1 (@var{X}, @var{Y}, @var{method}, @qcode{'pp'}} ## return a piecewise polynomial object, using the interpolation algorithm ## specified by @var{method}, which can be later used with @code{ppval} to ## evaluate the interpolation at new query points. ## ## @end deftypefn function YI = interp1 (X, Y, varargin) if (isempty (varargin)) ## YI = interp1 (Y, XI) if (isduration (Y)) Y = Y.days; endif if (isduration (X)) YI = duration ('Format', X.Format); YI.Days = interp1 (X.Days, Y); else YI = interp1 (X, Y); endif else [varargin{:}] = convertStringsToChars (varargin{:}); method = varargin{1}; if (ischar (method)) ## YI = interp1 (Y, XI, method) ## YI = interp1 (Y, XI, method, extrap) ## PP = interp1 (X, Y, pp) ## PP = interp1 (X, Y, method, pp) X_isDur = isa (X, 'duration'); Y_isDur = isa (Y, 'duration'); if (X_isDur) F = X.Format; X = X.days; endif if (Y_isDur) Y = Y.days; endif ## Handle numeric extrapolation input extrap = cellfun (@(x) isduration (x) || isnumeric (x), varargin); if (any (extrap)) tmp = varargin{extrap}; ExtDur = isduration (tmp); if (xor (ExtDur, X_isDur)) error (strcat ("duration.interp1: EXTRAPOLATION scalar", ... " value must match Y.")); elseif (ExtDur) varargin{extrap} = days (tmp); endif endif YI = interp1 (X, Y, varargin{:}); if (isnumeric (YI) && X_isDur) YI = days (YI); YI.Format = F; endif else XI = method; varargin(1) = []; ## YI = interp1 (X, Y, XI) ## YI = interp1 (X, Y, XI, method) ## YI = interp1 (X, Y, XI, method, extrap) X_isDur = isa (X, 'duration'); Y_isDur = isa (Y, 'duration'); XIisDur = isa (XI, 'duration'); if (xor (X_isDur, XIisDur)) error (strcat ("duration.interp1: if X is a duration array,", ... " XI must be also.")); endif ## Handle numeric extrapolation input extrap = cellfun (@(x) isduration (x) || isnumeric (x), varargin); if (any (extrap)) tmp = varargin{extrap}; ExtDur = isduration (tmp); if (xor (ExtDur, Y_isDur)) error (strcat ("duration.interp1: EXTRAPOLATION scalar", ... " value must match Y.")); elseif (ExtDur) varargin{extrap} = days (tmp); endif endif if (Y_isDur) YI = duration ('Format', Y.Format); if (X_isDur) YI.Days = interp1 (X.Days, Y.Days, XI.Days, varargin{:}); else YI.Days = interp1 (X, Y.Days, XI, varargin{:}); endif YI = fix_zero_precision (YI); elseif (isnumeric (Y)) YI = interp1 (X.Days, Y, XI.Days, varargin{:}); else error ("duration.interp1: Y must be a duration or numeric array."); endif endif endif endfunction ## -*- texinfo -*- ## @deftypefn {duration} {@var{C} =} intersect (@var{A}, @var{B}) ## @deftypefnx {duration} {@var{C} =} intersect (@var{A}, @var{B}, @qcode{'rows'}) ## @deftypefnx {duration} {@var{C} =} intersect (@var{A}, @var{B}, @dots{}, @var{order}) ## @deftypefnx {duration} {[@var{C}, @var{ixA}, @var{ixB}] =} intersect (@dots{}) ## ## Set intersection of two duration arrays. ## ## @code{@var{C} = intersect (@var{A}, @var{B})} returns the unique common ## values of the duration arrays @var{A} and @var{B}. Either @var{A} or ## @var{B} input arguments can also be a duration string specified as a ## character vector, a string array, or a cell array of character vectors, ## or a numeric array representing 24-hour days. In such case, the input is ## promoted to a duration array prior to calculating the intersection. If ## both @var{A} and @var{B} are row vectors, then @var{C} is also a row ## vector, otherwise @code{intersect} returns a column vector. ## ## @code{@var{C} = intersect (@var{A}, @var{B}, @qcode{'rows'}} returns the ## unique common rows of the duration matrices @var{A} and @var{B}, which ## must have the same number of columns. By default, the rows in duration ## matrix @var{C} are in sorted order. ## ## @code{@dots{} = intersect (@var{A}, @var{B}, @dots{}, @var{order})} also ## specifies the order of the returned unique values. @var{order} can be ## @qcode{'sorted'}, which is the default behavior, or @qcode{'stable'}, in ## which case the unique values are returned in order of appearance. ## ## @code{[@var{C}, @var{ixA}, @var{ixB}] = intersect (@dots{})} also returns ## index vectors @var{ixA} and @var{ixB} such that ## @code{@var{C} = @var{A}(@var{ixA})} and ## @code{@var{C} = @var{B}(@var{ixB})}, unless the @qcode{'rows'} optional ## argument is given, in which case @code{@var{C} = @var{A}(@var{ixA},:)} ## and @code{@var{C} = @var{B}(@var{ixB},:)}. ## ## @end deftypefn function [C, ixA, ixB] = intersect (A, B, varargin) ## 'legacy' option is not supported if (any (strcmp ("legacy", varargin))) error ("duration.intersect: 'legacy' option is not supported."); endif if (ischar (A) || isstring (A) || iscellstr (A) || isnumeric (A)) C = duration ('Format', B.Format); A = promote (A); elseif (ischar (B) || isstring (B) || iscellstr (B) || isnumeric (B)) C = duration ('Format', A.Format); B = promote (B); elseif (isduration (A) && isduration (B)) C = duration ('Format', A.Format); else error (strcat ("duration.intersect: set operation not defined", ... " between '%s' and '%s' arrays."), class (A), class (B)); endif [C.Days, ixA, ixB] = intersect (A.Days, B.Days, varargin{:}); endfunction ## -*- texinfo -*- ## @deftypefn {duration} {@var{C} =} setdiff (@var{A}, @var{B}) ## @deftypefnx {duration} {@var{C} =} setdiff (@var{A}, @var{B}, @qcode{'rows'}) ## @deftypefnx {duration} {@var{C} =} setdiff (@var{A}, @var{B}, @dots{}, @var{order}) ## @deftypefnx {duration} {[@var{C}, @var{ixA}] =} setdiff (@dots{}) ## ## Set difference of two duration arrays. ## ## @code{@var{C} = setdiff (@var{A}, @var{B})} returns the unique common ## values of the duration arrays @var{A} and @var{B}. Either @var{A} or ## @var{B} input arguments can also be a duration string specified as a ## character vector, a string array, or a cell array of character vectors, ## or a numeric array representing 24-hour days. In such case, the input is ## promoted to a duration array prior to calculating the intersection. If ## both @var{A} and @var{B} are row vectors, then @var{C} is also a row ## vector, otherwise @code{intersect} returns a column vector. ## ## @code{@var{C} = setdiff (@var{A}, @var{B}, @qcode{'rows'}} returns the ## unique common rows of the duration matrices @var{A} and @var{B}, which ## must have the same number of columns. By default, the rows in duration ## matrix @var{C} are in sorted order. ## ## @code{@dots{} = setdiff (@var{A}, @var{B}, @dots{}, @var{order})} also ## specifies the order of the returned unique values. @var{order} can be ## @qcode{'sorted'}, which is the default behavior, or @qcode{'stable'}, in ## which case the unique values are returned in order of appearance. ## ## @code{[@var{C}, @var{ixA}] = setdiff (@dots{})} also returns the index ## vector @var{ixA} such that @code{@var{C} = @var{A}(@var{ixA})}, unless ## the @qcode{'rows'} optional argument is given, in which case ## @code{@var{C} = @var{A}(@var{ixA},:)}. ## ## @end deftypefn function [C, index] = setdiff (A, B, varargin) ## 'legacy' option is not supported if (any (strcmp ("legacy", varargin))) error ("duration.setdiff: 'legacy' option is not supported."); endif if (ischar (A) || isstring (A) || iscellstr (A) || isnumeric (A)) C = duration ('Format', B.Format); A = promote (A); elseif (ischar (B) || isstring (B) || iscellstr (B) || isnumeric (B)) C = duration ('Format', A.Format); B = promote (B); elseif (isduration (A) && isduration (B)) C = duration ('Format', A.Format); else error (strcat ("duration.setdiff: set operation not defined", ... " between '%s' and '%s' arrays."), class (A), class (B)); endif [C.Days, index] = setdiff (A.Days, B.Days, varargin{:}); endfunction ## -*- texinfo -*- ## @deftypefn {duration} {@var{C} =} setxor (@var{A}, @var{B}) ## @deftypefnx {duration} {@var{C} =} setxor (@var{A}, @var{B}, @qcode{'rows'}) ## @deftypefnx {duration} {@var{C} =} setxor (@var{A}, @var{B}, @dots{}, @var{order}) ## @deftypefnx {duration} {[@var{C}, @var{ixA}, @var{ixB}] =} setxor (@dots{}) ## ## Set exclusive-or of two duration arrays. ## ## @code{@var{C} = setxor (@var{A}, @var{B})} returns the unique common ## values of the duration arrays @var{A} and @var{B}. Either @var{A} or ## @var{B} input arguments can also be a duration string specified as a ## character vector, a string array, or a cell array of character vectors, ## or a numeric array representing 24-hour days. In such case, the input is ## promoted to a duration array prior to calculating the intersection. If ## both @var{A} and @var{B} are row vectors, then @var{C} is also a row ## vector, otherwise @code{intersect} returns a column vector. ## ## @code{@var{C} = setxor (@var{A}, @var{B}, @qcode{'rows'}} returns the ## unique common rows of the duration matrices @var{A} and @var{B}, which ## must have the same number of columns. By default, the rows in ## duration matrix @var{C} are in sorted order. ## ## @code{@dots{} = setxor (@var{A}, @var{B}, @dots{}, @var{order})} also ## specifies the order of the returned unique values. @var{order} can be ## @qcode{'sorted'}, which is the default behavior, or @qcode{'stable'}, in ## which case the unique values are returned in order of appearance. ## ## @code{[@var{C}, @var{ixA}, @var{ixB}] = setxor (@dots{})} also returns ## index vectors @var{ixA} and @var{ixB} such that ## @code{@var{C} = @var{A}(@var{ixA})} and ## @code{@var{C} = @var{B}(@var{ixB})}, unless the @qcode{'rows'} optional ## argument is given, in which case @code{@var{C} = @var{A}(@var{ixA},:)} ## and @code{@var{C} = @var{B}(@var{ixB},:)}. ## ## @end deftypefn function [C, ixA, ixB] = setxor (A, B, varargin) ## 'legacy' option is not supported if (any (strcmp ("legacy", varargin))) error ("duration.setxor: 'legacy' option is not supported."); endif if (ischar (A) || isstring (A) || iscellstr (A) || isnumeric (A)) C = duration ('Format', B.Format); A = promote (A); elseif (ischar (B) || isstring (B) || iscellstr (B) || isnumeric (B)) C = duration ('Format', A.Format); B = promote (B); elseif (isduration (A) && isduration (B)) C = duration ('Format', A.Format); else error (strcat ("duration.setxor: set operation not defined", ... " between '%s' and '%s' arrays."), class (A), class (B)); endif [C.Days, ixA, ixB] = setxor (A.Days, B.Days, varargin{:}); endfunction ## -*- texinfo -*- ## @deftypefn {duration} {@var{C} =} union (@var{A}, @var{B}) ## @deftypefnx {duration} {@var{C} =} union (@var{A}, @var{B}, @qcode{'rows'}) ## @deftypefnx {duration} {@var{C} =} union (@var{A}, @var{B}, @dots{}, @var{order}) ## @deftypefnx {duration} {[@var{C}, @var{ixA}, @var{ixB}] =} union (@dots{}) ## ## Set union of two duration arrays. ## ## @code{@var{C} = union (@var{A}, @var{B})} returns the unique common ## values of the duration arrays @var{A} and @var{B}. Either @var{A} or ## @var{B} input arguments can also be a duration string specified as a ## character vector, a string array, or a cell array of character vectors, ## or a numeric array representing 24-hour days. In such case, the input is ## promoted to a duration array prior to calculating the intersection. If ## both @var{A} and @var{B} are row vectors, then @var{C} is also a row ## vector, otherwise @code{intersect} returns a column vector. ## ## @code{@var{C} = union (@var{A}, @var{B}, @qcode{'rows'}} returns the ## unique common rows of the duration matrices @var{A} and @var{B}, which ## must have the same number of columns. By default, the rows in ## duration matrix @var{C} are in sorted order. ## ## @code{@dots{} = union (@var{A}, @var{B}, @dots{}, @var{order})} also ## specifies the order of the returned unique values. @var{order} can be ## @qcode{'sorted'}, which is the default behavior, or @qcode{'stable'}, in ## which case the unique values are returned in order of appearance. ## ## @code{[@var{C}, @var{ixA}, @var{ixB}] = union (@dots{})} also returns ## index vectors @var{ixA} and @var{ixB} such that ## @code{@var{C} = @var{A}(@var{ixA})} and ## @code{@var{C} = @var{B}(@var{ixB})}, unless the @qcode{'rows'} optional ## argument is given, in which case @code{@var{C} = @var{A}(@var{ixA},:)} ## and @code{@var{C} = @var{B}(@var{ixB},:)}. ## ## @end deftypefn function [C, ixA, ixB] = union (A, B, varargin) ## 'legacy' option is not supported if (any (strcmp ("legacy", varargin))) error ("duration.union: 'legacy' option is not supported."); endif if (ischar (A) || isstring (A) || iscellstr (A) || isnumeric (A)) C = duration ('Format', B.Format); A = promote (A); elseif (ischar (B) || isstring (B) || iscellstr (B) || isnumeric (B)) C = duration ('Format', A.Format); B = promote (B); elseif (isduration (A) && isduration (B)) C = duration ('Format', A.Format); else error (strcat ("duration.union: set operation not defined", ... " between '%s' and '%s' arrays."), class (A), class (B)); endif [C.Days, ixA, ixB] = union (A.Days, B.Days, varargin{:}); endfunction endmethods ################################################################################ ## ** Array Operations ** ## ################################################################################ ## Available Methods ## ## ## ## 'cat' 'horzcat' 'vertcat' 'repmat' ## ## 'repelem' 'repelems' 'reshape' 'circshift' ## ## 'permute' 'ipermute' 'transpose' 'ctranspose' ## ## ## ################################################################################ methods (Access = public) ## -*- texinfo -*- ## @deftypefn {duration} {@var{C} =} cat (@var{dim}, @var{A}, @var{B}, @dots{}) ## ## Concatenate duration arrays. ## ## @code{@var{C} = cat (@var{dim}, @var{A}, @var{B}, @dots{})} concatenates ## duration arrays @var{A}, @var{B}, @dots{} along dimension @var{dim}. All ## input arrays must have the same size except along the operating dimension ## @var{dim}. Any of the input arrays may also be string arrays or cell ## arrays of character vectors of compatible size. Additionally, an input ## can be a numeric matrix, which when parsed to the constructor will return ## a duration array of compatible size. ## ## @end deftypefn function out = cat (dim, varargin) ## If any of the input arrays is a calendarDuration array, then convert ## the first input to calendarDuration array and call the corresponding ## method. if (any (cellfun ('iscalendarduration', varargin))) if (isduration (varargin{1})) varargin{1} = calendarDuration (0, 0, 0, varargin{1}); elseif (isnumeric (varargin{1})) if (isempty (varargin{1})) varargout{1} = calendarDuration ([], [], []); else varargout{1} = calendarDuration (0, 0, 0, 24 * varargin{i}, 0, 0); endif else error ("calendarDuration: invalid input to constructor."); endif out = cat (dim, varargin{:}); else args = varargin; [args{:}] = promote (varargin{:}); ## Get format from first duration array argument idx = find (cellfun ('isduration', varargin), 1); fmt = varargin{idx}.Format; out = duration ('Format', fmt); days = cellfun (@(obj) obj.Days, args, 'UniformOutput', false); out.Days = cat (dim, days{:}); endif endfunction ## -*- texinfo -*- ## @deftypefn {duration} {@var{C} =} horzcat (@var{A}, @var{B}, @dots{}) ## ## Horizontal concatenation of duration arrays. ## ## @code{@var{C} = horzcat (@var{A}, @var{B}, @dots{}} is the equivalent of ## the syntax @code{@var{B} = [@var{A}, @var{B}, @dots{}]} and horizontally ## concatenates the duration arrays @var{A}, @var{B}, @dots{}. All input ## arrays must have the same size except along the second dimension. Any of ## the input arrays may also be string arrays or cell arrays of character ## vectors of compatible size. Additionally, an input can be a numeric ## matrix, which when parsed to the constructor will return a duration array ## of compatible size. ## ## @end deftypefn function out = horzcat (varargin) out = cat (2, varargin{:}); endfunction ## -*- texinfo -*- ## @deftypefn {duration} {@var{C} =} vertcat (@var{A}, @var{B}, @dots{}) ## ## Vertical concatenation of duration arrays. ## ## @code{@var{C} = vertcat (@var{A}, @var{B}, @dots{}} is the equivalent of ## the syntax @code{@var{B} = [@var{A}; @var{B}; @dots{}]} and vertically ## concatenates the duration arrays @var{A}, @var{B}, @dots{}. All input ## arrays must have the same size except along the first dimension. All of ## the input arrays may also be string arrays or cell arrays of character ## vectors of compatible size. Additionally, an input can be a numeric ## matrix, which when parsed to the constructor will return a duration array ## of compatible size. ## ## @end deftypefn function out = vertcat (varargin) out = cat (1, varargin{:}); endfunction ## -*- texinfo -*- ## @deftypefn {duration} {@var{B} =} repmat (@var{A}, @var{n}) ## @deftypefnx {duration} {@var{B} =} repmat (@var{A}, @var{d1}, @dots{}, @var{dN}) ## @deftypefnx {duration} {@var{B} =} repmat (@var{A}, @var{dimvec}) ## ## Repeat copies of a duration array. ## ## @code{@var{B} = repmat (@var{A}, @var{n})} returns a duration array ## @var{B} containing @var{n} copies of the input duration array @var{A} ## along every dimension of @var{A}. ## ## @code{@var{B} = repmat (@var{A}, @var{d1}, @dots{}, @var{dN})} returns an ## array @var{B} containing copies of @var{A} along the dimensions specified ## by the list of scalar integer values @var{d1}, @dots{}, @var{dN}, which ## specify how many copies of @var{A} are made in each dimension. ## ## @code{@var{B} = repmat (@var{A}, @var{dimvec})} is equivalent to the ## previous syntax with @code{@var{dimvec} = [@var{d1}, @dots{}, @var{dN}]}. ## ## @end deftypefn function this = repmat (this, varargin) this.Days = repmat (this.Days, varargin{:}); endfunction ## -*- texinfo -*- ## @deftypefn {duration} {@var{B} =} repelem (@var{A}, @var{n}) ## @deftypefnx {duration} {@var{B} =} repelem (@var{A}, @var{d1}, @dots{}, @var{dN}) ## ## Repeat copies of duration array elements. ## ## @code{@var{B} = repelem (@var{A}, @var{n})} returns a duration vector ## @var{B} containing repeated elements of the input @var{A}, which must be ## a duration vector. If @var{n} is a scalar, each element of @var{A} is ## repeated @var{n} times along the non-singleton dimension of @var{A}. If ## @var{n} is a vector, it must have the same elements as @var{A}, in which ## case it specifies the number of times to repeat each corresponding ## element of @var{A}. ## ## @code{@var{B} = repelem (@var{A}, @var{d1}, @dots{}, @var{dN})} returns ## an array @var{B} with each element of @var{A} repeated according to the ## the list of input arguments @code{@var{d1}, @dots{}, @var{dN}} each ## corresponding to a different dimension @code{1:ndims (@var{A})} of the ## input array @var{A}. @var{d1}, @dots{}, @var{dN} must be either scalars ## or vectors with the same length as the corresponding dimension of ## @var{A} containing non-negative integer values specifying the number of ## repetitions of each element along the corresponding dimension. ## ## @end deftypefn function this = repelem (this, varargin) this.Days = repelem (this.Days, varargin{:}); endfunction ## -*- texinfo -*- ## @deftypefn {duration} {@var{B} =} repelems (@var{A}, @var{R}) ## ## Construct a vector of repeated duration array. ## ## @code{@var{B} = repelems (@var{A}, @var{R})} returns a duration vector ## @var{B} containing repeated elements of the input @var{A}, which must be ## a duration vector. @var{R} must be a @math{2*N} matrix of integers. ## Entries in the first row of @var{R} correspond to the linear indexing of ## the elements in @var{A} to be repeated. The corresponding entries in the ## second row of @var{R} specify the repeat count of each element. ## ## @end deftypefn function this = repelems (this, R) this.Days = repelems (this.Days, R); endfunction ## -*- texinfo -*- ## @deftypefn {duration} {@var{B} =} reshape (@var{A}, @var{d1}, @dots{}, @var{dN}) ## @deftypefnx {duration} {@var{B} =} reshape (@var{A}, @dots{}, @qcode{[]}, @dots{}) ## @deftypefnx {duration} {@var{B} =} reshape (@var{A}, @var{dimvec}) ## ## Reshape duration array. ## ## @code{@var{B} = reshape (@var{A}, @var{d1}, @dots{}, @var{dN})} returns a ## duration array @var{B} with specified dimensions @var{d1}, @dots{}, ## @var{dN}, whose elements are taken columnwise from the duration array ## @var{A}. The product of @var{d1}, @dots{}, @var{dN} must equal the total ## number of elements in @var{A}. ## ## @code{@var{B} = reshape (@var{A}, @dots{}, @qcode{[]}, @dots{})} returns ## a duration array @var{B} with one dimension unspecified which is ## calculated automatically so that the product of dimensions in @var{B} ## matches the total elements in @var{A}, which must be divisible the ## product of specified dimensions. An empty matrix @qcode{([])} is used to ## flag the unspecified dimension. ## ## @end deftypefn function this = reshape (this, varargin) this.Days = reshape (this.Days, varargin{:}); endfunction ## -*- texinfo -*- ## @deftypefn {duration} {@var{B} =} circshift (@var{A}, @var{n}) ## @deftypefnx {duration} {@var{B} =} circshift (@var{A}, @var{n}, @var{dim}) ## ## Circularly shift the elements in a duration array. ## ## @code{@var{B} = circshift (@var{A}, @var{n})} circularly shifts the ## elements of the duration array @var{A} according to @var{n}. If @var{n} ## is a nonzero integer scalar, then the elements of @var{A} are shifted by ## @var{n} elements along the first non-singleton dimension of @var{A}. If ## @var{n} is a vector, it must not be longer that the number of dimensions ## of @var{A} with each value of @var{n} corresponding to a dimension in ## @var{A}. The sign of the value(s) in @var{n} specify the direction in ## the elements of @var{A} are shifted. ## ## @code{@var{B} = circshift (@var{A}, @var{n}, @var{dim})} circularly ## shifts the elements of the duration array @var{A} along the dimension ## specified by @var{dim}. In this case, @var{n} must be a scalar integer ## value. ## ## @end deftypefn function this = circshift (this, varargin) this.Days = circshift (this.Days, varargin{:}); endfunction ## -*- texinfo -*- ## @deftypefn {duration} {@var{B} =} permute (@var{A}, @var{dims}) ## ## Generalized transpose for a duration N-D array. ## ## @code{@var{B} = permute (@var{A}, @var{dims})} returns the generalized ## transpose of the duration array @var{A} by rearranging its dimensions ## according to the permutation vector specified in @var{dims}. ## ## @var{dims} must index all the dimensions @code{1:ndims (@var{A})} of the ## input array @var{A}, in any order, but only once. The @var{N}th ## dimension of @var{A} gets remapped to the dimension in @var{B} specified ## by @code{@var{dims}(@var{N})}. ## ## @end deftypefn function this = permute (this, order) this.Days = permute (this.Days, order); endfunction ## -*- texinfo -*- ## @deftypefn {duration} {@var{A} =} ipermute (@var{B}, @var{dims}) ## ## Inverse of the generalized transpose for a duration N-D array. ## ## @code{@var{A} = ipermute (@var{B}, @var{dims})} returns the inverse of ## the generalized transpose performed by the @code{permute} function. The ## expression @code{ipermute (permute (@var{A}, @var{dims}), @var{dims})} ## returns the original array @var{A}. ## ## @var{dims} must index all the dimensions @code{1:ndims (@var{B})} of the ## input array @var{B}, in any order, but only once. The dimension of ## @var{B} specified in @code{@var{dims}(@var{N})} gets remapped to the ## @var{N}th dimension of @var{A}. ## ## @end deftypefn function this = ipermute (this, order) this.Days = ipermute (this.Days, order); endfunction ## -*- texinfo -*- ## @deftypefn {duration} {@var{B} =} transpose (@var{A}) ## ## Transpose a duration matrix. ## ## @code{@var{B} = transpose (@var{A})} is the equivalent of the syntax ## @code{@var{B} = @var{A}.'} and returns the transpose of the duration ## matrix @var{A}. ## ## @end deftypefn function this = transpose (this) this.Days = transpose (this.Days); endfunction ## -*- texinfo -*- ## @deftypefn {duration} {@var{B} =} ctranspose (@var{A}) ## ## Transpose a duration matrix. ## ## @code{@var{B} = ctranspose (@var{A})} is the equivalent of the syntax ## @code{@var{B} = @var{A}'} and returns the transpose of the duration ## matrix @var{A}. For duration arrays, @code{ctranspose} is identical to ## @code{transpose}. ## ## @end deftypefn function this = ctranspose (this) this.Days = ctranspose (this.Days); endfunction endmethods ################################################################################ ## ** Reference and Assignment Operations ** ## ################################################################################ ## Available Methods ## ## ## ## 'end' 'subsref' 'subsasgn' ## ## ## ################################################################################ methods (Hidden) ## Overload 'end' keyword function last_index = end (this, end_dim, ndim_obj) lastdim = ndims (this); if (end_dim == ndim_obj && ndim_obj == 1) last_index = prod (size (this)); elseif (end_dim == ndim_obj && end_dim < lastdim) last_index = prod (size (this)(end_dim:lastdim)); else last_index = size (this, end_dim); endif endfunction ## Class specific subscripted reference function varargout = subsref (this, s) chain_s = s(2:end); s = s(1); switch (s.type) case '()' out = this; out.Days = this.Days(s.subs{:}); case '{}' error (strcat ("duration.subsref: '{}' invalid indexing", ... " for referencing values. Use '()' instead.")); case '.' switch (s.subs) case 'Format' out = this.Format; otherwise error ("duration.subsref: unrecognized property: '%s'", s.subs); endswitch endswitch ## Chained references if (! isempty (chain_s)) out = subsref (out, chain_s); endif varargout{1} = out; endfunction ## Class specific subscripted assignment function this = subsasgn (this, s, val) if (numel (s) > 1) error ("duration.subsasgn: chained subscripts not allowed."); endif switch (s.type) case '()' if (isempty (val)) this.Days(s.subs{:}) = []; elseif (isa (val, 'missing')) this.Days(s.subs{:}) = NaN; elseif (iscellstr (val) || ischar (val) || isstring (val)) if (! iscellstr (val)) val = cellstr (val); endif this.Days(s.subs{:}) = timestrings2days (val, []); elseif (isnumeric (val)) this.Days(s.subs{:}) = double (val); elseif (isa (val, "duration")) this.Days(s.subs{:}) = val.Days; else error (strcat ("duration.subsasgn: assignment value must", ... " be a duration array, a numeric array of", ... " days, or a text represention of durations.")); endif case '{}' error (strcat ("duration.subsasgn: '{}' invalid indexing", ... " for assigning values. Use '()' instead.")); case '.' switch (s.subs) case 'Format' ## Convert string to character vector if necessary if (isstring (val)) if (! isscalar (val)) error (strcat ("duration.subsasgn: 'Format' must be a", ... " character vector or a string scalar.")); endif val = char (val); elseif (! (ischar (val) && isrow (val))) error (strcat ("duration.subsasgn: 'Format' must be a", ... " character vector or a string scalar.")); endif errmsg = checkFormatString (val); if (! isempty (errmsg)) error ("duration.subsasgn: %s", errmsg); endif this.Format = val; otherwise error ("duration.subsasgn: unrecognized property: '%s'", s.subs); endswitch endswitch endfunction endmethods methods (Access = private) ## Return a subset of a duration array function out = subset (this, varargin) out = this; out.Days = this.Days(varargin{:}); endfunction ## Fix floating point precision near zero function this = fix_zero_precision (this) this.Days(this.Days > -1e-15 & this.Days < 1e-15) = 0; endfunction endmethods endclassdef ## Promote numeric and string arrays to duration objects function varargout = promote (varargin) for i = 1:numel (varargin) x = varargin{i}; if (isa (x, "duration")) varargout{i} = x; elseif (isnumeric (x)) varargout{i} = days (x); elseif (iscellstr (x) || ischar (x) || isa (x, "string")) varargout{i} = duration (x); else error ("duration: invalid input to constructor."); endif endfor endfunction ## Parse H, M, S, and MS numeric inputs into days function [err, days] = hms2days (H, MI, S, MS = 0) err = 0; if (! isscalar (H) || ! isscalar (MI) || ! isscalar (S) || ! isscalar (MS)) [err, H, MI, S, MS] = common_size (H, MI, S, MS); if (err > 0) days = NaN; return endif endif H = double (H); MI = double (MI); S = double (S); MS = double (MS); days = (H / 24) + (MI / 1440) + (S / 86400) + (MS / 86400000); endfunction ## Parse TimeString inputs into days function days = timestrings2days (TS, inputFormat) days = NaN (size (TS)); ## Find default format (either 'dd:hh:mm:ss' or 'hh:mm:ss') from 1st element if (isempty (inputFormat)) str1 = TS{1}; nCols = numel (find (str1 == ':')); nDots = numel (find (str1 == '.')); if (nDots > 1) error ("duration: could not recognize time string format of '%s'.", str1); endif if (nCols > 3 || nCols < 1) error ("duration: could not recognize time string format of '%s'.", str1); endif if (nCols == 1) error ("duration: time string format is ambiguous."); endif if (nDots) cstr = strsplit (str1, '.'); if (! isempty (cstr{2}) && isnan (str2double (cstr{2}))) error (strcat ("duration: could not recognize time string", ... " format of '%s'."), str1); endif cstr = strsplit (cstr{1}, ':'); if (numel (cstr) != nCols + 1) error (strcat ("duration: could not recognize time string", ... " format of '%s'."), str1); endif for i = 1:nCols + 1 if (isnan (str2double (cstr{i}))) error (strcat ("duration: could not recognize time string", ... " format of '%s'."), str1); endif endfor endif else nCols = numel (find (inputFormat == ':')); nDots = numel (find (inputFormat == '.')); endif ## Process all elements according to inputFormat (unrecognized return NaN) for i = 1:numel (TS) str1 = TS{i}; pnd_sign = 1; if (numel (find (str1 == '.'))) cstr = strsplit (str1, '.'); if (isempty (cstr{2})) MS = 0; else MSnumber = str2double (cstr{2}); MSdigits = 10 ^ (numel (cstr{2}) - 3); MS = MSnumber / MSdigits; endif cstr = strsplit (cstr{1}, ':'); if (numel (cstr) != nCols + 1) D = H = MI = S = NaN; elseif (nCols == 1) # 'mm:ss' only D = 0; H = 0; MI = str2double (cstr{1}); if (MI < 0) pnd_sign = -1; MI = -MI; endif S = str2double (cstr{2}); if (S >= 60) S = NaN; endif elseif (nCols == 2) # 'hh:mm:ss' D = 0; H = str2double (cstr{1}); if (H < 0) pnd_sign = -1; H = -H; endif MI = str2double (cstr{2}); S = str2double (cstr{3}); if (MI >= 60 || S >= 60) MI = S = NaN; endif else # 'dd:hh:mm:ss' D = str2double (cstr{1}); if (D < 0) pnd_sign = -1; D = -D; endif H = str2double (cstr{2}); MI = str2double (cstr{3}); S = str2double (cstr{4}); if (H >= 24 || MI >= 60 || S >= 60) H = MI = S = NaN; endif endif else cstr = strsplit (str1, ':'); if (numel (cstr) != nCols + 1) D = H = MI = S = MS = NaN; elseif (nCols == 1) # either 'mm:ss' or 'hh:mm' if (strcmp (inputFormat, 'mm:ss')) D = 0; H = 0; MI = str2double (cstr{1}); if (MI < 0) pnd_sign = -1; MI = -MI; endif S = str2double (cstr{2}); MS = 0; else # 'hh:mm' D = 0; H = str2double (cstr{1}); if (H < 0) pnd_sign = -1; H = -H; endif MI = str2double (cstr{2}); if (MI >= 60) MI = NaN; endif S = 0; MS = 0; endif elseif (nCols == 2) # 'hh:mm:ss' D = 0; H = str2double (cstr{1}); if (H < 0) pnd_sign = -1; H = -H; endif MI = str2double (cstr{2}); S = str2double (cstr{3}); if (MI >= 60 || S >= 60) MI = S = NaN; endif MS = 0; else # 'dd:hh:mm:ss' D = str2double (cstr{1}); if (D < 0) pnd_sign = -1; D = -D; endif H = str2double (cstr{2}); MI = str2double (cstr{3}); S = str2double (cstr{4}); if (H >= 24 || MI >= 60 || S >= 60) H = MI = S = NaN; endif MS = 0; endif endif days(i) = D + (H / 24) + (MI / 1440) + (S / 86400) + (MS / 86400000); days(i) *= pnd_sign; endfor endfunction ## Check 'Format' string function errmsg = checkFormatString (Format) errmsg = ""; Format = strsplit (Format, '.')'; validFmt = {'y', 'd', 'h', 'm', 's', 'dd:hh:mm:ss', 'hh:mm:ss', ... 'mm:ss', 'hh:mm'}; foundFmt = ismember (validFmt, Format(1)); if (! any (foundFmt) || numel (Format) > 2) errmsg = "invalid display format."; endif if (any (foundFmt([1:5])) && numel (Format) > 1) errmsg = "invalid display format."; endif if (foundFmt(9) && numel (Format) > 1) errmsg = "'hh:mm' display format cannot indicate fractional second digits."; endif if (numel (Format) == 2) if (any (char (Format(2)) != 'S')) errmsg = "invalid display format for fractional second digits."; endif if (numel (Format{2}) > 9) errmsg = "more than nine fractional second digits in display format."; endif endif endfunction ## Check 'InputFormat' string function errmsg = checkInputFormatString (inputFormat) errmsg = ''; inputFormat = strsplit (inputFormat, '.'); validInFmt = {'dd:hh:mm:ss','hh:mm:ss','mm:ss','hh:mm'}; foundInFmt = ismember (validInFmt, inputFormat(1)); if (! any (foundInFmt) || numel (inputFormat) > 2) errmsg = "invalid 'InputFormat'."; endif if (foundInFmt(4) && numel (inputFormat) > 1) errmsg = "'hh:mm' input format cannot indicate fractional second digits."; endif if (numel (inputFormat) == 2) if (any (char (inputFormat(2)) != 'S')) errmsg = "invalid 'InputFormat' for fractional second digits."; endif if (numel (inputFormat{2}) > 9) errmsg = "more than nine fractional second digits in 'InputFormat'."; endif endif endfunction pr0m1th3as-datatypes-9c9a8d3/inst/findgroups.m000066400000000000000000000163541522766574100214010ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the datatypes package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {datatypes} {@var{G} =} findgroups (@var{A}) ## @deftypefnx {datatypes} {@var{G} =} findgroups (@var{A1}, @dots{}, @var{AN}) ## @deftypefnx {datatypes} {[@var{G}, @var{ID}] =} findgroups (@var{A}) ## @deftypefnx {datatypes} {[@var{G}, @var{ID1}, @dots{}, @var{IDN}] =} findgroups (@var{A1}, @dots{}, @var{AN}) ## ## Find groups defined by one or more grouping variables. ## ## @code{@var{G} = findgroups (@var{A})} returns @var{G}, a column vector of ## positive integer group numbers with one element per element of the grouping ## variable @var{A}. The groups are the sorted unique values of @var{A}: if ## @var{N} groups are found, every integer between 1 and @var{N} labels a group. ## Elements of @var{A} that are missing (@code{NaN}, @code{NaT}, ## @code{}, @code{''}, or @code{}) are labelled @code{NaN} ## in @var{G}. @var{A} can be a numeric, logical, @code{string}, @code{cellstr}, ## @code{char}, @code{datetime}, @code{duration}, @code{calendarDuration}, or ## @code{categorical} vector. ## ## @code{@var{G} = findgroups (@var{A1}, @dots{}, @var{AN})} defines groups as ## the sorted unique combinations of values across the grouping variables ## @var{A1}, @dots{}, @var{AN}, which must all have the same number of elements. ## ## @code{[@var{G}, @var{ID}] = findgroups (@var{A})} also returns @var{ID}, the ## sorted unique values of @var{A} that identify each group. With multiple ## grouping variables, @code{[@var{G}, @var{ID1}, @dots{}, @var{IDN}] = ## findgroups (@var{A1}, @dots{}, @var{AN})} returns one identifier vector per ## grouping variable. ## ## To group the variables of a @code{table}, call @code{findgroups (@var{T})}, ## which dispatches to the @code{table} method and returns the group identifiers ## as a table. ## ## @seealso{splitapply, table} ## @end deftypefn function [G, varargout] = findgroups (varargin) if (nargin < 1) print_usage (); endif ## Force each grouping variable to a column (char matrices keep their rows as ## observations); reject anything that is not a vector or a char matrix. vars = varargin; for k = 1:numel (vars) v = vars{k}; if (ischar (v)) if (ndims (v) > 2) error ("findgroups: grouping variables must be vectors."); endif elseif (isvector (v) || isempty (v)) vars{k} = v(:); else error ("findgroups: grouping variables must be vectors."); endif endfor ## Build the combined proxy matrix and the overall missing-row mask. nvar = numel (vars); n = size (vars{1}, 1); P = []; miss = false (n, 1); for k = 1:nvar if (size (vars{k}, 1) != n) error ("findgroups: grouping variables must have the same number of elements."); endif [p, m, errmsg] = group_col_proxy (vars{k}); if (! isempty (errmsg)) error ("findgroups: %s", errmsg); endif P = [P, p]; miss = miss | m; endfor ## Label the non-missing rows by sorted unique combination. G = NaN (n, 1); keep = find (! miss); repRows = []; if (! isempty (keep)) [~, ia, ic] = unique (P(keep,:), "rows"); G(keep) = ic; repRows = keep(ia); endif if (nargout > 1) if (nargout - 1 > nvar) print_usage (); endif varargout = cell (1, nargout - 1); for k = 1:(nargout - 1) varargout{k} = vars{k}(repRows,:); endfor endif endfunction ## Build a single-column grouping proxy for one grouping variable COL: a numeric ## matrix P (one row per element) whose sort order matches COL's value order, so ## that 'unique (P, "rows")' recovers the sorted unique groups, together with a ## logical MISS mask flagging the elements that findgroups treats as missing ## (NaN/NaT//''/). Returns an errmsg body (empty on success) ## emitted by the caller under its own name. Self-contained: no table dependency. function [p, miss, errmsg] = group_col_proxy (col) p = []; miss = []; errmsg = ''; if (isa (col, 'categorical')) ## Categorical groups follow category order (ordinal or reordered), which the ## underlying category codes encode; maps to NaN. p = double (col)(:); miss = isnan (p); elseif (isa (col, 'string') || iscellstr (col) || ischar (col)) c = cellstr (col); c = c(:); miss = cellfun (@isempty, c); [~, ~, ic] = unique (c); p = ic(:); elseif (isa (col, 'datetime')) DV = datevec (col); # (numel)-by-6 in column-major order nat = any (isnan (DV), 2); DV(nat,:) = 0; DV(nat,2:3) = 1; # valid month/day placeholder p = datenum (DV); p(nat) = NaN; p = p(:); miss = isnan (p); elseif (isa (col, 'duration')) p = days (col)(:); miss = isnan (p); elseif (isa (col, 'calendarDuration')) p = proxyArray (col); miss = any (isnan (p), 2); elseif (isnumeric (col) || islogical (col)) p = double (col)(:); miss = isnan (p); else errmsg = sprintf ("unsupported grouping variable type '%s'.", class (col)); endif endfunction %!assert_equal (findgroups ([1; 3; 1; 2]), [1; 3; 1; 2]) %!assert_equal (findgroups ([30, 10, 10, 20]), [3; 1; 1; 2]) %!assert_equal (findgroups ({'b'; 'a'; 'b'; 'c'}), [2; 1; 2; 3]) %!assert_equal (findgroups (logical ([1; 0; 1; 0])), [2; 1; 2; 1]) %!test %! ## Missing values map to NaN in G %! assert_equal (findgroups ([1; NaN; 2; 1]), [1; NaN; 2; 1]); %! assert_equal (findgroups ({'b'; ''; 'a'}), [2; NaN; 1]); %!test %! ## Categorical groups follow category order, not alphabetical order %! c = categorical ({'medium'; 'low'; 'high'; 'low'}, ... %! {'low', 'medium', 'high'}, 'Ordinal', true); %! assert_equal (findgroups (c), [2; 1; 3; 1]); %! [g, id] = findgroups (c); %! assert_equal (cellstr (id), {'low'; 'medium'; 'high'}); %!test %! ## Reordered nominal categoricals also group by category order %! c = reordercats (categorical ({'b'; 'a'; 'c'; 'a'}), {'c', 'a', 'b'}); %! assert_equal (findgroups (c), [3; 2; 1; 2]); %!test %! ## Multiple grouping variables group by sorted unique combinations %! assert_equal (findgroups ([1; 1; 2; 1], {'b'; 'a'; 'a'; 'b'}), [2; 1; 3; 2]); %!test %! ## Second output holds the sorted unique identifiers of each input %! [G, ID] = findgroups ([30; 10; 10; 20]); %! assert_equal (G, [3; 1; 1; 2]); %! assert_equal (ID, [10; 20; 30]); %!test %! [G, ID1, ID2] = findgroups ([1; 1; 2], {'b'; 'a'; 'a'}); %! assert_equal (G, [2; 1; 3]); %! assert_equal (ID1, [1; 1; 2]); %! assert_equal (ID2, {'a'; 'b'; 'a'}); %!error findgroups () %!error findgroups ([1, 2; 3, 4]) pr0m1th3as-datatypes-9c9a8d3/inst/groupcounts.m000066400000000000000000000372271522766574100216130ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the datatypes package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {datatypes} {@var{B} =} groupcounts (@var{A}) ## @deftypefnx {datatypes} {@var{B} =} groupcounts (@var{A}, @var{groupbins}) ## @deftypefnx {datatypes} {[@var{B}, @var{BG}, @var{BP}] =} groupcounts (@dots{}) ## @deftypefnx {datatypes} {[@dots{}] =} groupcounts (@dots{}, @var{Name}, @var{Value}) ## ## Count the number of elements in each group of an array. ## ## @code{@var{B} = groupcounts (@var{A})} groups the rows of @var{A} by their ## values and returns @var{B}, a column vector with the number of elements in ## each group. @var{A} is a grouping vector, a matrix whose columns are grouping ## variables, or a cell array of grouping vectors. Groups are the sorted unique ## combinations of grouping values; rows holding a missing value in a grouping ## variable form their own groups, sorted after the non-missing groups. ## ## @code{[@var{B}, @var{BG}, @var{BP}] = groupcounts (@dots{})} also returns ## @var{BG}, the grouping values that identify each group, and @var{BP}, a column ## vector giving each group's count as a percentage of the total. When @var{A} ## is a single grouping vector, @var{BG} holds its representative value for each ## group; when several grouping variables are given, @var{BG} is a cell array ## with one element per grouping variable. ## ## The optional @var{groupbins} argument bins the grouping variables before ## grouping (a vector of bin edges or a positive integer number of bins, applied ## to a numeric, datetime, or duration grouping variable, or a cell array with ## one scheme per grouping variable); each binned variable becomes a categorical ## of bin interval labels. ## ## The behaviour can be modified with the @qcode{'IncludeMissingGroups'} (default ## @code{true}), @qcode{'IncludeEmptyGroups'} (default @code{false}), and ## @qcode{'IncludedEdge'} (default @qcode{'left'}, the inclusive bin edge) ## @var{Name}/@var{Value} pairs, as for the @code{table} method. When ## @qcode{'IncludeEmptyGroups'} is @code{true}, the unused categories of a ## categorical or binned grouping variable contribute empty groups. ## ## To count the rows in each group of a @code{table}, call @code{groupcounts ## (@var{T}, @var{groupvars}, @dots{})}, which dispatches to the @code{table} ## method and returns the result as a table. ## ## @seealso{groupsummary, findgroups, splitapply, table} ## @end deftypefn function [B, varargout] = groupcounts (A, varargin) if (nargin < 1) print_usage (); endif ## An optional GROUPBINS positional argument may precede the Name-Value ## options; anything else after A must be a recognised option. optNames = {'IncludeMissingGroups', 'IncludeEmptyGroups', 'IncludedEdge'}; hasGB = false; groupbins = []; if (! isempty (varargin)) a = varargin{1}; isOpt = ((ischar (a) && isrow (a)) || (isa (a, 'string') && isscalar (a))) ... && any (strcmpi (char (a), optNames)); if (! isOpt) if (__groupbins__ ('is_spec', a)) hasGB = true; groupbins = a; varargin = varargin(2:end); else error (strcat ("groupcounts: invalid argument; expected a GROUPBINS", ... " binning scheme or a Name-Value option.")); endif endif endif ## Parse Name-Value options. dfValues = {true, false, 'left'}; [incMiss, incEmpty, incEdge] = ... parsePairedArguments (optNames, dfValues, varargin(:)); if (! (isscalar (incMiss) && (islogical (incMiss) || isnumeric (incMiss)))) error ("groupcounts: 'IncludeMissingGroups' must be a logical scalar."); endif incMiss = logical (incMiss); if (! (isscalar (incEmpty) && (islogical (incEmpty) || isnumeric (incEmpty)))) error ("groupcounts: 'IncludeEmptyGroups' must be a logical scalar."); endif incEmpty = logical (incEmpty); incEdge = check_included_edge ('groupcounts', incEdge); ## Resolve A into a cell array of grouping column vectors: a cell array holds ## one grouping variable per element, a numeric/logical matrix one per column, ## and anything else is a single grouping variable. if (iscell (A) && ! (iscellstr (A) && isvector (A))) gvs = A(:)'; elseif (! ischar (A) && ismatrix (A) && size (A, 2) > 1) gvs = cell (1, size (A, 2)); for j = 1:size (A, 2) gvs{j} = A(:,j); endfor else gvs = {A}; endif for j = 1:numel (gvs) g = gvs{j}; if (ischar (g)) if (ndims (g) > 2) error ("groupcounts: grouping variables must be vectors."); endif elseif (isvector (g) || isempty (g)) gvs{j} = g(:); else error ("groupcounts: grouping variables must be vectors."); endif endfor n = size (gvs{1}, 1); for j = 2:numel (gvs) if (size (gvs{j}, 1) != n) error (strcat ("groupcounts: each grouping variable must have the", ... " same number of elements.")); endif endfor ## Bin the grouping variables when a GROUPBINS argument was given. if (hasGB) names = cell (1, numel (gvs)); for j = 1:numel (gvs) names{j} = sprintf ("%d", j); endfor [gvs, ~, errmsg] = __groupbins__ ('bin', gvs, names, groupbins, incEdge, ... 'groupcounts'); if (! isempty (errmsg)) error ("groupcounts: %s", errmsg); endif endif ## Group the rows, treating missing grouping values as their own groups; ## IncludeEmptyGroups adds the unused categories of a categorical or binned ## grouping variable as empty groups. [Grp, ng, gvals, errmsg] = gb_grouping (gvs, incMiss, incEmpty); if (! isempty (errmsg)) error ("groupcounts: %s", errmsg); endif B = accumarray (Grp(! isnan (Grp)), 1, [ng, 1]); ## Optional outputs: the grouping identifiers and the group percentages. if (nargout > 1) if (numel (gvs) == 1) BG = gvals{1}; else BG = gvals; endif varargout{1} = BG; endif if (nargout > 2) varargout{2} = 100 * B / sum (B); endif endfunction ## Group rows by the grouping-variable values GVS (a cell array of column ## vectors), treating each variable's missing values as a single group value. ## Returns G (1..NGROUPS), NGROUPS, REPROWS (a representative row per group), and ## an errmsg body. Groups are sorted by value with missing groups last; when ## INCMISS is false the rows with a missing grouping value are dropped. ## Self-contained: no table dependency. function [G, ngroups, repRows, errmsg] = gc_group_rows (gvs, incMiss) errmsg = ''; G = []; ngroups = 0; repRows = []; n = size (gvs{1}, 1); KEY = []; SORT = []; anyMiss = false (n, 1); for j = 1:numel (gvs) [p, m, e] = gc_col_proxy (gvs{j}); if (! isempty (e)) errmsg = e; return; endif pc = p; pc(m,:) = 0; KEY = [KEY, pc, double(m)]; sp = p; sp(m,:) = Inf; SORT = [SORT, sp]; anyMiss = anyMiss | m; endfor [~, ia, ic] = unique (KEY, "rows"); ng = numel (ia); grpMiss = anyMiss(ia); [~, ord] = sortrows (SORT(ia,:)); reps = ia(ord); grpMiss = grpMiss(ord); pos = zeros (ng, 1); pos(ord) = 1:ng; G = pos(ic); if (! incMiss && any (grpMiss)) keep = find (! grpMiss); newId = NaN (ng, 1); newId(keep) = 1:numel (keep); G = newId(G); reps = reps(keep); ng = numel (keep); endif ngroups = ng; repRows = reps; endfunction ## Build a single-column grouping proxy for one grouping variable COL: a numeric ## matrix P whose sort order matches COL's value order, together with a logical ## MISS mask flagging the missing elements. Returns an errmsg body emitted by ## the caller. Self-contained: no table dependency. function [p, miss, errmsg] = gc_col_proxy (col) p = []; miss = []; errmsg = ''; if (isa (col, 'categorical')) ## Categorical groups follow category order (ordinal or reordered), which the ## underlying category codes encode; maps to NaN. p = double (col)(:); miss = isnan (p); elseif (isa (col, 'string') || iscellstr (col) || ischar (col)) c = cellstr (col); c = c(:); miss = cellfun (@isempty, c); [~, ~, ic] = unique (c); p = ic(:); elseif (isa (col, 'datetime')) DV = datevec (col); nat = any (isnan (DV), 2); DV(nat,:) = 0; DV(nat,2:3) = 1; p = datenum (DV); p(nat) = NaN; p = p(:); miss = isnan (p); elseif (isa (col, 'duration')) p = days (col)(:); miss = isnan (p); elseif (isa (col, 'calendarDuration')) p = proxyArray (col); miss = any (isnan (p), 2); elseif (isnumeric (col) || islogical (col)) p = double (col)(:); miss = isnan (p); else errmsg = sprintf ("unsupported grouping variable type '%s'.", class (col)); endif endfunction ## Validate an 'IncludedEdge' binning option VAL for CALLER, returning it ## lowercased as 'left' or 'right'. Self-contained: no table dependency. function e = check_included_edge (caller, val) if (isa (val, 'string') && isscalar (val)) val = char (val); endif if (! (ischar (val) && isrow (val) ... && any (strcmpi (val, {'left', 'right'})))) error ("%s: 'IncludedEdge' must be 'left' or 'right'.", caller); endif e = lower (val); endfunction ## Group rows by the grouping-variable values GVS (already binned when a ## GROUPBINS argument was given). Returns G (1..NG, NaN excluded), NG, GVALS (a ## 1-by-K cell of typed level-value columns, one per group), and an errmsg body. ## When INCEMPTY is true the unused categories of a categorical or binned grouping ## variable contribute empty groups, from the full Cartesian product of levels. function [G, ng, gvals, errmsg] = gb_grouping (gvs, incMiss, incEmpty) errmsg = ''; G = []; ng = 0; gvals = {}; K = numel (gvs); n = size (gvs{1}, 1); if (! incEmpty) [G, ng, repRows, errmsg] = gc_group_rows (gvs, incMiss); if (! isempty (errmsg)) return; endif gvals = cell (1, K); for j = 1:K gvals{j} = gvs{j}(repRows, :); endfor return; endif idxAll = NaN (n, K); levVals = cell (1, K); sizes = zeros (1, K); for j = 1:K [idx, lv, errmsg] = gb_levels (gvs{j}, incMiss); if (! isempty (errmsg)) return; endif idxAll(:,j) = idx; levVals{j} = lv; sizes(j) = size (lv, 1); endfor ng = prod (sizes); lvlOf = ones (ng, K); period = 1; for j = K:-1:1 lvlOf(:,j) = mod (floor ((0:ng-1)' / period), sizes(j)) + 1; period = period * sizes(j); endfor valid = all (! isnan (idxAll), 2); lin = zeros (n, 1); period = 1; for j = K:-1:1 col = idxAll(:,j); col(isnan (col)) = 1; lin = lin + (col - 1) * period; period = period * sizes(j); endfor G = NaN (n, 1); G(valid) = lin(valid) + 1; gvals = cell (1, K); for j = 1:K gvals{j} = levVals{j}(lvlOf(:,j), :); endfor endfunction ## Build the level structure of one grouping variable COL for empty-group-aware ## grouping: IDX (n-by-1 level index, NaN excluded), LEVVALS (one representative ## value per level), and an errmsg body. A categorical uses its full category ## order; a missing value forms one extra level, sorted last, when INCMISS. function [idx, levVals, errmsg] = gb_levels (col, incMiss) idx = []; levVals = []; errmsg = ''; n = size (col, 1); [p, miss, errmsg] = gc_col_proxy (col); if (! isempty (errmsg)) return; endif if (isa (col, 'categorical')) cats = categories (col); L = numel (cats); idx = double (col)(:); levVals = categorical (cats(:), cats, 'Ordinal', isordinal (col)); else idx = NaN (n, 1); keep = find (! miss); if (isempty (keep)) levVals = col([], :); L = 0; else [~, ia, ic] = unique (p(keep,:), "rows"); idx(keep) = ic; levVals = col(keep(ia), :); L = numel (ia); endif endif if (any (miss)) if (incMiss) L = L + 1; idx(miss) = L; mrow = find (miss, 1); levVals = [levVals; col(mrow, :)]; else idx(miss) = NaN; endif endif endfunction %!assert_equal (groupcounts ([1; 1; 2; 2; 1]), [3; 2]) %!assert_equal (groupcounts ([30; 10; 10; 20]), [2; 1; 1]) %!test %! ## The second and third outputs are the group values and the percentages %! [B, BG, BP] = groupcounts ([1; 1; 2; 2; 1]); %! assert_equal (B, [3; 2]); %! assert_equal (BG, [1; 2]); %! assert_equal (BP, [60; 40]); %!test %! ## Groups follow the sorted unique order of the values %! [B, BG, BP] = groupcounts ([1; 1; 2; 2; 3; 5; 3; 3; 1; 4]); %! assert_equal (B, [3; 2; 3; 1; 1]); %! assert_equal (BG, [1; 2; 3; 4; 5]); %! assert_equal (BP, [30; 20; 30; 10; 10]); %!test %! ## A matrix groups by the unique combinations of its columns %! [B, BG, BP] = groupcounts ([1 0; 1 0; 1 1; 2 1]); %! assert_equal (B, [2; 1; 1]); %! assert_equal (BG, {[1; 1; 2], [0; 1; 1]}); %! assert_equal (BP, [50; 25; 25]); %!test %! ## A cell array of vectors groups by the unique combinations, BG a cell array %! [B, BG, BP] = groupcounts ({[1; 1; 2; 2; 1], {'b'; 'a'; 'a'; 'b'; 'b'}}); %! assert_equal (B, [1; 2; 1; 1]); %! assert_equal (BG, {[1; 1; 2; 2], {'a'; 'b'; 'a'; 'b'}}); %! assert_equal (BP, [20; 40; 20; 20]); %!test %! ## A missing value forms its own group, sorted last %! [B, BG, BP] = groupcounts ([1; 1; 2; NaN; 2]); %! assert_equal (B, [2; 2; 1]); %! assert_equal (BG, [1; 2; NaN]); %! assert_equal (BP, [40; 40; 20]); %!test %! ## 'IncludeMissingGroups' false drops the rows with a missing grouping value %! [B, BG] = groupcounts ([1; 1; 2; NaN; 2], 'IncludeMissingGroups', false); %! assert_equal (B, [2; 2]); %! assert_equal (BG, [1; 2]); %!test %! ## A categorical vector groups by category order, not alphabetically %! c = categorical ({'medium'; 'low'; 'high'; 'low'}, ... %! {'low', 'medium', 'high'}, 'Ordinal', true); %! [B, BG] = groupcounts (c); %! assert_equal (B, [2; 1; 1]); %! assert_equal (cellstr (BG), {'low'; 'medium'; 'high'}); %!test %! ## A GROUPBINS edge vector groups a numeric variable by bin interval %! [B, BG, BP] = groupcounts ([1; 3; 5; 7; 9; 11], [0 6 12]); %! assert_equal (B, [3; 3]); %! assert_equal (iscategorical (BG), true); %! assert_equal (cellstr (BG), {'[0, 6)'; '[6, 12]'}); %! assert_equal (BP, [50; 50]); %!test %! ## A GROUPBINS number of bins makes equal-width bins over the data range %! [B, BG] = groupcounts ([1; 2; 3; 4], 3); %! assert_equal (cellstr (BG), {'[1, 2)'; '[2, 3)'; '[3, 4]'}); %! assert_equal (B, [1; 1; 2]); %!test %! ## 'IncludeEmptyGroups' adds unused categorical categories with zero count %! c = categorical ({'a'; 'a'; 'b'}, {'a', 'b', 'c'}); %! [B, BG] = groupcounts (c, 'IncludeEmptyGroups', true); %! assert_equal (cellstr (BG), {'a'; 'b'; 'c'}); %! assert_equal (B, [2; 1; 0]); %!error groupcounts () %!error ... %! groupcounts ([1; 2], 'bogus') %!error ... %! groupcounts ([1; 2], 2, 'IncludedEdge', 'mid') %!error ... %! groupcounts ([1; 2], 'month') %!error ... %! groupcounts ({[1; 2; 3], [1; 2]}) pr0m1th3as-datatypes-9c9a8d3/inst/groupfilter.m000066400000000000000000000314511522766574100215560ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the datatypes package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {datatypes} {@var{B} =} groupfilter (@var{A}, @var{groupvars}, @var{method}) ## @deftypefnx {datatypes} {@var{B} =} groupfilter (@var{A}, @var{groupvars}, @var{groupbins}, @var{method}) ## @deftypefnx {datatypes} {[@var{B}, @var{BG}] =} groupfilter (@dots{}) ## ## Filter the rows of an array by a per-group condition. ## ## @code{@var{B} = groupfilter (@var{A}, @var{groupvars}, @var{method})} groups ## the rows of the array @var{A} by the grouping variables @var{groupvars}, ## applies the filter function @var{method} to each group, and returns @var{B}, ## the rows of @var{A} that satisfy the condition, in their original order. ## @var{groupvars} is a grouping vector with one element per row of @var{A}, or a ## cell array of such vectors. Rows holding a missing value in a grouping ## variable form their own groups, to which @var{method} is applied like any ## other group. ## ## @var{method} is a function handle applied to each group's slice of every ## column of @var{A}. It must return either a logical scalar, which keeps or ## drops the whole group, or a logical vector with one element per row of the ## group, which keeps or drops the individual rows. A row is kept only when the ## condition holds for it across all columns of @var{A}. ## ## @code{[@var{B}, @var{BG}] = groupfilter (@dots{})} also returns @var{BG}, the ## grouping values of the kept rows. When @var{groupvars} is a single grouping ## vector, @var{BG} is a column vector with one element per kept row; when several ## grouping variables are given, @var{BG} is a cell array with one element per ## grouping variable. ## ## The optional @var{groupbins} argument bins the grouping variables before ## grouping (a vector of bin edges or a positive integer number of bins, or a ## cell array with one scheme per grouping variable); see @code{groupsummary} for ## details. The @qcode{'IncludedEdge'} Name-Value pair (@qcode{'left'} by ## default, or @qcode{'right'}) selects which bin edge is inclusive. ## ## To filter the rows of a @code{table}, call @code{groupfilter (@var{T}, ## @var{groupvars}, @dots{})}, which dispatches to the @code{table} method and ## returns the result as a table. ## ## @seealso{groupsummary, groupcounts, findgroups, splitapply, table} ## @end deftypefn function [B, varargout] = groupfilter (A, groupvars, varargin) if (nargin < 3) print_usage (); endif ## Split off a trailing 'IncludedEdge' Name-Value option, then an optional ## GROUPBINS positional argument that precedes the filter function METHOD. args = varargin; optNames = {'IncludedEdge'}; nvStart = numel (args) + 1; for k = 1:numel (args) a = args{k}; if (((ischar (a) && isrow (a)) || (isa (a, 'string') && isscalar (a))) && any (strcmpi (char (a), optNames))) nvStart = k; break; endif endfor nvArgs = args(nvStart:end); args = args(1:nvStart-1); incEdge = parsePairedArguments (optNames, {'left'}, nvArgs(:)); incEdge = check_included_edge ('groupfilter', incEdge); hasGB = false; groupbins = []; if (! isempty (args) && __groupbins__ ('is_spec', args{1})) hasGB = true; groupbins = args{1}; args = args(2:end); endif if (isempty (args)) print_usage (); endif method = args{1}; if (! is_function_handle (method)) error ("groupfilter: METHOD must be a function handle."); endif if (numel (args) > 1) error ("groupfilter: too many positional arguments."); endif ## Force the grouping variables to a cell array of column vectors. if (iscell (groupvars) && ! (iscellstr (groupvars) && isvector (groupvars))) gvs = groupvars(:)'; else gvs = {groupvars}; endif n = size (A, 1); for j = 1:numel (gvs) g = gvs{j}; if (ischar (g)) if (ndims (g) > 2) error ("groupfilter: grouping variables must be vectors."); endif elseif (isvector (g) || isempty (g)) gvs{j} = g(:); else error ("groupfilter: grouping variables must be vectors."); endif if (size (gvs{j}, 1) != n) error (strcat ("groupfilter: each grouping variable must have one", ... " element per row of A.")); endif endfor ## Bin the grouping variables when a GROUPBINS argument was given. if (hasGB) names = cell (1, numel (gvs)); for j = 1:numel (gvs) names{j} = sprintf ("%d", j); endfor [gvs, ~, errmsg] = __groupbins__ ('bin', gvs, names, groupbins, incEdge, ... 'groupfilter'); if (! isempty (errmsg)) error ("groupfilter: %s", errmsg); endif endif ## Group the rows, treating missing grouping values as their own groups so ## that every row belongs to exactly one group. [Grp, ng, errmsg] = gf_group_rows (gvs); if (! isempty (errmsg)) error ("groupfilter: %s", errmsg); endif ## Build the row keep-mask by applying METHOD to each column of A. dataCols = cell (1, size (A, 2)); for c = 1:size (A, 2) dataCols{c} = A(:,c); endfor [keep, errmsg] = gf_keep_mask (method, dataCols, Grp, ng); if (! isempty (errmsg)) error ("groupfilter: %s", errmsg); endif B = A(keep,:); ## Optional output: the grouping values of the kept rows. if (nargout > 1) if (numel (gvs) == 1) BG = gvs{1}(keep,:); else BG = cell (1, numel (gvs)); for j = 1:numel (gvs) BG{j} = gvs{j}(keep,:); endfor endif varargout{1} = BG; endif endfunction ## Build the row keep-mask by applying the filter function METHOD to each data ## column's per-group slice. DATACOLS is a cell array of column values; G the ## n-by-1 group numbers (1..NG), every row assigned to a group. For each group ## METHOD receives the slice and must return a logical scalar (keep/drop the ## whole group) or a logical vector with one element per group row. The ## per-column masks are combined with logical AND. Returns KEEP (n-by-1 ## logical) and an errmsg body emitted by the caller. Self-contained. function [keep, errmsg] = gf_keep_mask (method, dataCols, G, ng) errmsg = ''; n = numel (G); keep = true (n, 1); for d = 1:numel (dataCols) col = dataCols{d}; for g = 1:ng rows = find (G == g); if (isempty (rows)) continue; endif r = method (col(rows,:)); if (! (islogical (r) || isnumeric (r))) errmsg = "the filter function must return a logical result."; return; endif r = logical (r(:)); if (isscalar (r)) m = repmat (r, numel (rows), 1); elseif (numel (r) == numel (rows)) m = r; else errmsg = strcat ("the filter function must return a logical scalar", ... " or a logical vector with one element per group", ... " row."); return; endif keep(rows) = keep(rows) & m; endfor endfor endfunction ## Group rows by the grouping-variable values GVS (a cell array of column ## vectors), treating each variable's missing values as a single group value so ## that every row is assigned to a group. Returns G (1..NGROUPS), NGROUPS, and ## an errmsg body emitted by the caller. Self-contained: no table dependency. function [G, ngroups, errmsg] = gf_group_rows (gvs) errmsg = ''; G = []; ngroups = 0; n = size (gvs{1}, 1); KEY = []; for j = 1:numel (gvs) [p, m, e] = gf_col_proxy (gvs{j}); if (! isempty (e)) errmsg = e; return; endif pc = p; pc(m,:) = 0; KEY = [KEY, pc, double(m)]; endfor [~, ~, ic] = unique (KEY, "rows"); G = ic; ngroups = max (ic); endfunction ## Build a single-column grouping proxy for one grouping variable COL: a numeric ## matrix P whose value order matches COL's, together with a logical MISS mask ## flagging the missing elements. Returns an errmsg body emitted by the caller. ## Self-contained: no table dependency. function [p, miss, errmsg] = gf_col_proxy (col) p = []; miss = []; errmsg = ''; if (isa (col, 'categorical')) p = double (col)(:); miss = isnan (p); elseif (isa (col, 'string') || iscellstr (col) || ischar (col)) c = cellstr (col); c = c(:); miss = cellfun (@isempty, c); [~, ~, ic] = unique (c); p = ic(:); elseif (isa (col, 'datetime')) DV = datevec (col); nat = any (isnan (DV), 2); DV(nat,:) = 0; DV(nat,2:3) = 1; p = datenum (DV); p(nat) = NaN; p = p(:); miss = isnan (p); elseif (isa (col, 'duration')) p = days (col)(:); miss = isnan (p); elseif (isa (col, 'calendarDuration')) p = proxyArray (col); miss = any (isnan (p), 2); elseif (isnumeric (col) || islogical (col)) p = double (col)(:); miss = isnan (p); else errmsg = sprintf ("unsupported grouping variable type '%s'.", class (col)); endif endfunction ## Validate an 'IncludedEdge' binning option VAL for CALLER, returning it ## lowercased as 'left' or 'right'. Self-contained: no table dependency. function e = check_included_edge (caller, val) if (isa (val, 'string') && isscalar (val)) val = char (val); endif if (! (ischar (val) && isrow (val) ... && any (strcmpi (val, {'left', 'right'})))) error ("%s: 'IncludedEdge' must be 'left' or 'right'.", caller); endif e = lower (val); endfunction %!test %! ## A scalar-returning method keeps or drops whole groups %! g = [1; 1; 1; 2; 2; 3]; %! x = [3; 1; 2; 9; 8; 5]; %! assert_equal (groupfilter (x, g, @(v) numel (v) > 2), [3; 1; 2]); %!test %! ## A vector-returning method keeps individual rows within each group %! d = [1; 1; 1; 1; 2; 2; 2; 2]; %! t = [67; 65; 71; 55; 61; 79; 58; 78]; %! assert_equal (groupfilter (t, d, @(x) x == max (x)), [71; 79]); %!test %! ## Output rows keep their original order, not group order %! g = [2; 1; 2; 1]; %! x = [1; 2; 3; 4]; %! assert_equal (groupfilter (x, g, @(v) numel (v) >= 1), [1; 2; 3; 4]); %!test %! ## The second output returns the grouping values of the kept rows %! d = [1; 1; 1; 1; 2; 2; 2; 2]; %! t = [67; 65; 71; 55; 61; 79; 58; 78]; %! [B, BG] = groupfilter (t, d, @(x) x == max (x)); %! assert_equal (B, [71; 79]); %! assert_equal (BG, [1; 2]); %!test %! ## A matrix groups by its grouping vector; a row is kept only when the %! ## condition holds across all columns of A %! A = [5 1; 1 9; 9 1; 2 9]; %! g = [1; 1; 2; 2]; %! assert_equal (groupfilter (A, g, @(v) v > 3), zeros (0, 2)); %!test %! ## Rows with a missing grouping value form their own group and are filtered %! g = [1; 1; NaN; 2; 2]; %! x = [10; 20; 30; 40; 50]; %! [B, BG] = groupfilter (x, g, @(v) mean (v) > 25); %! assert_equal (B, [30; 40; 50]); %! assert_equal (BG, [NaN; 2; 2]); %!test %! ## Several grouping vectors return BG as a cell array, one per variable %! x = [3; 1; 9; 2; 8]; %! g1 = [1; 1; 2; 2; 1]; %! g2 = {'a'; 'a'; 'b'; 'b'; 'a'}; %! [B, BG] = groupfilter (x, {g1, g2}, @(v) v == max (v)); %! assert_equal (B, [9; 8]); %! assert_equal (BG, {[2; 1], {'b'; 'a'}}); %!test %! ## A categorical grouping vector groups by category %! c = categorical ({'a'; 'b'; 'a'; 'b'; 'a'}); %! x = [1; 5; 2; 6; 9]; %! [B, BG] = groupfilter (x, c, @(v) v == max (v)); %! assert_equal (B, [6; 9]); %! assert_equal (cellstr (BG), {'b'; 'a'}); %!test %! ## A GROUPBINS edge vector filters within bins of a numeric grouping variable %! x = (1:6)'; %! g = [1; 3; 5; 7; 9; 11]; %! B = groupfilter (x, g, [0 6 12], @(v) numel (v) > 2); %! assert_equal (B, (1:6)'); %! B2 = groupfilter (x, g, [0 6 12], @(v) mean (v) > 4, 'IncludedEdge', 'right'); %! assert_equal (B2, [4; 5; 6]); %!error groupfilter ([1; 2], [1; 2]) %!error ... %! groupfilter ([1; 2], [1; 2], 'sum') %!error ... %! groupfilter ([1; 2], [1; 2], 'month', @(x) x > 0) %!error ... %! groupfilter ([1; 2], [1; 2], @(x) x > 0, 5) %!error ... %! groupfilter ([1; 2; 3], [1; 2], @(x) x > 0) %!error ... %! groupfilter ([1; 1], [1; 1], @(x) [true; true; true]) pr0m1th3as-datatypes-9c9a8d3/inst/groupsummary.m000066400000000000000000000544101522766574100217660ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the datatypes package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {datatypes} {@var{B} =} groupsummary (@var{A}, @var{groupvars}, @var{method}) ## @deftypefnx {datatypes} {@var{B} =} groupsummary (@var{A}, @var{groupvars}, @var{groupbins}, @var{method}) ## @deftypefnx {datatypes} {[@var{B}, @var{BG}, @var{BC}] =} groupsummary (@dots{}) ## ## Compute summary statistics by group for an array. ## ## @code{@var{B} = groupsummary (@var{A}, @var{groupvars}, @var{method})} groups ## the rows of the array @var{A} by the grouping variables @var{groupvars} and ## applies @var{method} to each column of @var{A} within each group, returning ## the results in @var{B}, which has one row per group. @var{groupvars} is a ## grouping vector with one element per row of @var{A}, or a cell array of such ## vectors. Groups are the sorted unique combinations of grouping values; rows ## holding a missing value in a grouping variable form their own groups, sorted ## after the non-missing groups. ## ## @var{method} is one of the names @qcode{'sum'}, @qcode{'mean'}, ## @qcode{'median'}, @qcode{'mode'}, @qcode{'var'}, @qcode{'std'}, @qcode{'min'}, ## @qcode{'max'}, @qcode{'range'}, @qcode{'nnz'}, @qcode{'nummissing'}, or ## @qcode{'numunique'}, a function handle, or a cell ## array of method names and@/or function handles. @code{NaN} values are omitted ## for every named method except @qcode{'nummissing'}; a function handle receives ## the values with @code{NaN} included and must return a single row. When ## several methods are requested the columns of @var{B} are ordered by column of ## @var{A} first, then by method. ## ## @code{[@var{B}, @var{BG}, @var{BC}] = groupsummary (@dots{})} also returns ## @var{BG}, the grouping values that identify each group, and @var{BC}, a column ## vector with the number of rows in each group. When @var{groupvars} is a ## single grouping vector, @var{BG} holds its representative value for each group; ## when several grouping variables are given, @var{BG} is a cell array with one ## element per grouping variable. ## ## The optional @var{groupbins} argument bins the grouping variables before ## grouping: a vector of bin edges or a positive integer number of equal-width ## bins, applied to a numeric, datetime, or duration grouping variable, or a cell ## array with one scheme per grouping variable. Each binned variable becomes a ## categorical of bin interval labels. ## ## The behaviour can be modified with the @qcode{'IncludeMissingGroups'} (default ## @code{true}), @qcode{'IncludeEmptyGroups'} (default @code{false}), and ## @qcode{'IncludedEdge'} (default @qcode{'left'}, the inclusive bin edge) ## @var{Name}/@var{Value} pairs, as for the @code{table} method. When ## @qcode{'IncludeEmptyGroups'} is @code{true}, the unused categories of a ## categorical or binned grouping variable contribute empty groups. ## ## To summarise the variables of a @code{table}, call @code{groupsummary ## (@var{T}, @var{groupvars}, @dots{})}, which dispatches to the @code{table} ## method and returns the result as a table. ## ## @seealso{findgroups, splitapply, table} ## @end deftypefn function [B, varargout] = groupsummary (A, groupvars, varargin) if (nargin < 3) print_usage (); endif ## An optional GROUPBINS positional argument precedes the method; the method is ## the next trailing argument, and the rest are Name-Value pairs. args = varargin; hasGB = false; groupbins = []; if (! isempty (args) && __groupbins__ ('is_spec', args{1})) hasGB = true; groupbins = args{1}; args = args(2:end); endif if (isempty (args)) print_usage (); endif method = args{1}; nvArgs = args(2:end); optNames = {'IncludeMissingGroups', 'IncludeEmptyGroups', 'IncludedEdge'}; dfValues = {true, false, 'left'}; [incMiss, incEmpty, incEdge] = ... parsePairedArguments (optNames, dfValues, nvArgs(:)); if (! (isscalar (incMiss) && (islogical (incMiss) || isnumeric (incMiss)))) error ("groupsummary: 'IncludeMissingGroups' must be a logical scalar."); endif incMiss = logical (incMiss); if (! (isscalar (incEmpty) && (islogical (incEmpty) || isnumeric (incEmpty)))) error ("groupsummary: 'IncludeEmptyGroups' must be a logical scalar."); endif incEmpty = logical (incEmpty); incEdge = check_included_edge ('groupsummary', incEdge); [methods, methNames, errmsg] = gs_normalise_methods (method); if (! isempty (errmsg)) error ("groupsummary: %s", errmsg); endif if (isempty (methods)) error ("groupsummary: a method is required for array input."); endif ## Force the grouping variables to a cell array of column vectors. if (iscell (groupvars) && ! (iscellstr (groupvars) && isvector (groupvars))) gvs = groupvars(:)'; else gvs = {groupvars}; endif n = size (A, 1); for j = 1:numel (gvs) g = gvs{j}; if (ischar (g)) if (ndims (g) > 2) error ("groupsummary: grouping variables must be vectors."); endif elseif (isvector (g) || isempty (g)) gvs{j} = g(:); else error ("groupsummary: grouping variables must be vectors."); endif if (size (gvs{j}, 1) != n) error (strcat ("groupsummary: each grouping variable must have one", ... " element per row of A.")); endif endfor ## Bin the grouping variables when a GROUPBINS argument was given. if (hasGB) names = cell (1, numel (gvs)); for j = 1:numel (gvs) names{j} = sprintf ("%d", j); endfor [gvs, ~, errmsg] = __groupbins__ ('bin', gvs, names, groupbins, incEdge, ... 'groupsummary'); if (! isempty (errmsg)) error ("groupsummary: %s", errmsg); endif endif ## Group the rows, treating missing grouping values as their own groups; ## IncludeEmptyGroups adds the unused categories of a categorical or binned ## grouping variable as empty groups. [Grp, ng, gvals, errmsg] = gb_grouping (gvs, incMiss, incEmpty); if (! isempty (errmsg)) error ("groupsummary: %s", errmsg); endif ## Compute each method over each column of A (column first, then method). ncol = size (A, 2); B = []; for c = 1:ncol for mi = 1:numel (methods) vals = cell (ng, 1); for g = 1:ng rows = (Grp == g); [v, errmsg] = gs_apply_method (methods{mi}, A(rows,c)); if (! isempty (errmsg)) error ("groupsummary: %s for column %d of A.", errmsg, c); endif vals{g} = v; endfor B = [B, vertcat(vals{:})]; endfor endfor ## Optional outputs: the grouping identifiers and the group counts. if (nargout > 1) if (numel (gvs) == 1) BG = gvals{1}; else BG = gvals; endif varargout{1} = BG; endif if (nargout > 2) varargout{2} = accumarray (Grp(! isnan (Grp)), 1, [ng, 1]); endif endfunction ## Normalise the METHOD argument into a cell array of method specs METHODS (each ## a method-name char vector or a function handle) and a parallel cell array of ## display names METHNAMES. Returns an errmsg body emitted by the caller. ## Self-contained: no table dependency. function [methods, methNames, errmsg] = gs_normalise_methods (method) methods = {}; methNames = {}; errmsg = ''; if (isempty (method) && ! iscell (method) && ! ischar (method) && ! is_function_handle (method)) return; endif if (is_function_handle (method) || (ischar (method) && isrow (method)) || isa (method, 'string')) items = {method}; elseif (iscell (method)) items = method(:)'; else errmsg = strcat ("METHOD must be a method name, a function handle, or a", ... " cell array of method names and function handles."); return; endif known = {'sum', 'mean', 'median', 'mode', 'var', 'std', 'min', 'max', ... 'range', 'nnz', 'nummissing', 'numunique'}; nfun = 0; for k = 1:numel (items) it = items{k}; if (is_function_handle (it)) nfun++; methods{end+1} = it; methNames{end+1} = sprintf ("fun%d", nfun); elseif ((ischar (it) && isrow (it)) || (isa (it, 'string') && isscalar (it))) nm = lower (char (it)); if (! any (strcmp (nm, known))) errmsg = sprintf ("'%s' is not a supported method name.", char (it)); return; endif methods{end+1} = nm; methNames{end+1} = nm; else errmsg = strcat ("each method must be a method name or a function", ... " handle."); return; endif endfor endfunction ## Group rows by the grouping-variable values GVS (a cell array of column ## vectors), treating each variable's missing values as a single group value. ## Returns G (1..NGROUPS), NGROUPS, REPROWS (a representative row per group), and ## an errmsg body. Groups are sorted by value with missing groups last; when ## INCMISS is false the rows with a missing grouping value are dropped. function [G, ngroups, repRows, errmsg] = gs_group_rows (gvs, incMiss) errmsg = ''; G = []; ngroups = 0; repRows = []; n = size (gvs{1}, 1); KEY = []; SORT = []; anyMiss = false (n, 1); for j = 1:numel (gvs) [p, m, e] = group_col_proxy (gvs{j}); if (! isempty (e)) errmsg = e; return; endif pc = p; pc(m,:) = 0; KEY = [KEY, pc, double(m)]; sp = p; sp(m,:) = Inf; SORT = [SORT, sp]; anyMiss = anyMiss | m; endfor [~, ia, ic] = unique (KEY, "rows"); ng = numel (ia); grpMiss = anyMiss(ia); [~, ord] = sortrows (SORT(ia,:)); reps = ia(ord); grpMiss = grpMiss(ord); pos = zeros (ng, 1); pos(ord) = 1:ng; G = pos(ic); if (! incMiss && any (grpMiss)) keep = find (! grpMiss); newId = NaN (ng, 1); newId(keep) = 1:numel (keep); G = newId(G); reps = reps(keep); ng = numel (keep); endif ngroups = ng; repRows = reps; endfunction ## Build a single-column grouping proxy for one grouping variable COL: a numeric ## matrix P whose sort order matches COL's value order, together with a logical ## MISS mask flagging the missing elements. Returns an errmsg body emitted by ## the caller. Self-contained: no table dependency. function [p, miss, errmsg] = group_col_proxy (col) p = []; miss = []; errmsg = ''; if (isa (col, 'categorical')) ## Categorical groups follow category order (ordinal or reordered), which the ## underlying category codes encode; maps to NaN. p = double (col)(:); miss = isnan (p); elseif (isa (col, 'string') || iscellstr (col) || ischar (col)) c = cellstr (col); c = c(:); miss = cellfun (@isempty, c); [~, ~, ic] = unique (c); p = ic(:); elseif (isa (col, 'datetime')) DV = datevec (col); nat = any (isnan (DV), 2); DV(nat,:) = 0; DV(nat,2:3) = 1; p = datenum (DV); p(nat) = NaN; p = p(:); miss = isnan (p); elseif (isa (col, 'duration')) p = days (col)(:); miss = isnan (p); elseif (isa (col, 'calendarDuration')) p = proxyArray (col); miss = any (isnan (p), 2); elseif (isnumeric (col) || islogical (col)) p = double (col)(:); miss = isnan (p); else errmsg = sprintf ("unsupported grouping variable type '%s'.", class (col)); endif endfunction ## Apply a single method M (a method-name char vector or a function handle) to ## the column slice X of one group, returning a row result V. Named methods omit ## missing values (except 'nummissing'); a function handle receives X unchanged ## and must return a single row. Returns an errmsg body emitted by the caller. function [v, errmsg] = gs_apply_method (m, x) v = []; errmsg = ''; if (is_function_handle (m)) v = m (x); if (size (v, 1) != 1) errmsg = "a function handle method must return a single row"; endif return; endif if (strcmp (m, 'nummissing')) v = sum (gs_missing_mask (x), 1); return; endif if (strcmp (m, 'numunique')) miss = gs_missing_mask (x); if (size (x, 2) == 1) v = numel (unique (x(! miss,:))); else v = zeros (1, size (x, 2)); for c = 1:size (x, 2) col = x(:,c); v(c) = numel (unique (col(! miss(:,c)))); endfor endif return; endif if (! (isnumeric (x) || islogical (x))) errmsg = sprintf (strcat ("named method '%s' is not supported for data", ... " of type '%s'; use a function handle"), m, ... class (x)); return; endif x = double (x); ## An empty group (e.g. an unused IncludeEmptyGroups category) takes the ## method's empty value: 0 for the additive 'sum'/'nnz', NaN otherwise. if (rows (x) == 0) if (any (strcmp (m, {'sum', 'nnz'}))) v = zeros (1, columns (x)); else v = NaN (1, columns (x)); endif return; endif nan = isnan (x); cnt = sum (! nan, 1); z = x; z(nan) = 0; switch (m) case 'sum' v = sum (z, 1); case 'mean' v = sum (z, 1) ./ cnt; case 'min' v = min (x, [], 1); case 'max' v = max (x, [], 1); case 'range' v = max (x, [], 1) - min (x, [], 1); case 'nnz' v = sum (x != 0 & ! nan, 1); case {'median', 'mode', 'var', 'std'} v = NaN (1, size (x, 2)); for c = 1:size (x, 2) col = x(! nan(:,c), c); if (! isempty (col)) switch (m) case 'median' v(c) = median (col); case 'mode' v(c) = mode (col); case 'var' v(c) = var (col); case 'std' v(c) = std (col); endswitch endif endfor endswitch endfunction ## Return a logical mask the size of X flagging its missing elements. Supports ## the numeric, logical, text, datetime, duration, calendarDuration, and ## categorical types. Self-contained: no table dependency. function mask = gs_missing_mask (x) if (isa (x, 'datetime')) DV = datevec (x); mask = any (isnan (DV), 2); mask = reshape (mask, size (x, 1), size (x, 2)); elseif (isa (x, 'duration')) mask = isnan (days (x)); elseif (isa (x, 'calendarDuration')) mask = any (isnan (proxyArray (x)), 2); elseif (isa (x, 'categorical') || isa (x, 'string')) mask = ismissing (x); elseif (iscellstr (x)) mask = cellfun (@isempty, x); elseif (islogical (x)) mask = false (size (x)); elseif (isnumeric (x)) mask = isnan (x); else mask = false (size (x)); endif endfunction ## Validate an 'IncludedEdge' binning option VAL for CALLER, returning it ## lowercased as 'left' or 'right'. Self-contained: no table dependency. function e = check_included_edge (caller, val) if (isa (val, 'string') && isscalar (val)) val = char (val); endif if (! (ischar (val) && isrow (val) ... && any (strcmpi (val, {'left', 'right'})))) error ("%s: 'IncludedEdge' must be 'left' or 'right'.", caller); endif e = lower (val); endfunction ## Group rows by the grouping-variable values GVS (already binned when a ## GROUPBINS argument was given). Returns G (1..NG, NaN for an excluded row), ## NG, GVALS (a 1-by-K cell of the typed level-value columns, one per group), and ## an errmsg body. When INCEMPTY is true the unused categories of a categorical ## or binned grouping variable contribute empty groups, built from the full ## Cartesian product of the per-variable levels; otherwise only the observed ## groups are returned. function [G, ng, gvals, errmsg] = gb_grouping (gvs, incMiss, incEmpty) errmsg = ''; G = []; ng = 0; gvals = {}; K = numel (gvs); n = size (gvs{1}, 1); if (! incEmpty) [G, ng, repRows, errmsg] = gs_group_rows (gvs, incMiss); if (! isempty (errmsg)) return; endif gvals = cell (1, K); for j = 1:K gvals{j} = gvs{j}(repRows, :); endfor return; endif idxAll = NaN (n, K); levVals = cell (1, K); sizes = zeros (1, K); for j = 1:K [idx, lv, errmsg] = gb_levels (gvs{j}, incMiss); if (! isempty (errmsg)) return; endif idxAll(:,j) = idx; levVals{j} = lv; sizes(j) = size (lv, 1); endfor ng = prod (sizes); lvlOf = ones (ng, K); period = 1; for j = K:-1:1 lvlOf(:,j) = mod (floor ((0:ng-1)' / period), sizes(j)) + 1; period = period * sizes(j); endfor valid = all (! isnan (idxAll), 2); lin = zeros (n, 1); period = 1; for j = K:-1:1 col = idxAll(:,j); col(isnan (col)) = 1; lin = lin + (col - 1) * period; period = period * sizes(j); endfor G = NaN (n, 1); G(valid) = lin(valid) + 1; gvals = cell (1, K); for j = 1:K gvals{j} = levVals{j}(lvlOf(:,j), :); endfor endfunction ## Build the level structure of one grouping variable COL for empty-group-aware ## grouping: IDX (n-by-1 level index, NaN for an excluded row), LEVVALS (a typed ## column of one representative value per level), and an errmsg body. A ## categorical uses its full category order (so unused categories are levels); a ## missing value forms one extra level, sorted last, when INCMISS is true. function [idx, levVals, errmsg] = gb_levels (col, incMiss) idx = []; levVals = []; errmsg = ''; n = size (col, 1); [p, miss, errmsg] = group_col_proxy (col); if (! isempty (errmsg)) return; endif if (isa (col, 'categorical')) cats = categories (col); L = numel (cats); idx = double (col)(:); levVals = categorical (cats(:), cats, 'Ordinal', isordinal (col)); else idx = NaN (n, 1); keep = find (! miss); if (isempty (keep)) levVals = col([], :); L = 0; else [~, ia, ic] = unique (p(keep,:), "rows"); idx(keep) = ic; levVals = col(keep(ia), :); L = numel (ia); endif endif if (any (miss)) if (incMiss) L = L + 1; idx(miss) = L; mrow = find (miss, 1); levVals = [levVals; col(mrow, :)]; else idx(miss) = NaN; endif endif endfunction %!assert_equal (groupsummary ([10; 20; 30; 40], [1; 1; 2; 2], 'mean'), [15; 35]) %!assert_equal (groupsummary ([10; 20; 30; 40], [1; 1; 2; 2], 'sum'), [30; 70]) %!test %! ## Multiple outputs return the group identifiers and the group counts %! [B, BG, BC] = groupsummary ([10; 20; 30; 40], [1; 1; 2; 2], 'mean'); %! assert_equal (B, [15; 35]); %! assert_equal (BG, [1; 2]); %! assert_equal (BC, [2; 2]); %!test %! ## A matrix is summarised column by column %! assert_equal (groupsummary ([10 1; 20 2; 30 3; 40 4], [1; 1; 2; 2], 'sum'), ... %! [30, 3; 70, 7]); %!test %! ## Several methods order the columns of B by data column first, then method %! B = groupsummary ([10; 20; 30; 40], [1; 1; 2; 2], {'mean', 'sum'}); %! assert_equal (B, [15, 30; 35, 70]); %!test %! ## Named methods omit NaN in the data %! assert_equal (groupsummary ([10; 20; NaN; 40], [1; 1; 2; 2], 'mean'), [15; 40]); %!test %! ## A missing value in a grouping variable forms its own group, sorted last %! [B, BG, BC] = groupsummary ([10; 20; 30; 40], [1; 1; NaN; 2], 'mean'); %! assert_equal (B, [15; 40; 30]); %! assert_equal (BG, [1; 2; NaN]); %! assert_equal (BC, [2; 1; 1]); %!test %! ## 'IncludeMissingGroups' false drops the rows with a missing grouping value %! B = groupsummary ([10; 20; 30; 40], [1; 1; NaN; 2], 'mean', ... %! 'IncludeMissingGroups', false); %! assert_equal (B, [15; 40]); %!test %! ## A function handle is applied to each group's slice %! B = groupsummary ([10; 20; 30; 40], [1; 1; 2; 2], @(v) max (v) - min (v)); %! assert_equal (B, [10; 10]); %!test %! ## Several grouping vectors return BG as a cell array, one per variable %! [B, BG, BC] = groupsummary ([10; 20; 30; 40; 50], ... %! {[1; 1; 2; 2; 1], {'b'; 'a'; 'a'; 'b'; 'b'}}, 'sum'); %! assert_equal (B, [20; 60; 30; 40]); %! assert_equal (BG, {[1; 1; 2; 2], {'a'; 'b'; 'a'; 'b'}}); %! assert_equal (BC, [1; 2; 1; 1]); %!test %! ## A categorical grouping vector groups by category order, not alphabetically %! c = categorical ({'medium'; 'low'; 'high'; 'low'}, ... %! {'low', 'medium', 'high'}, 'Ordinal', true); %! [B, BG, BC] = groupsummary ((1:4)', c, 'sum'); %! assert_equal (B, [6; 1; 3]); %! assert_equal (cellstr (BG), {'low'; 'medium'; 'high'}); %! assert_equal (BC, [2; 1; 1]); %!test %! ## A GROUPBINS edge vector groups a numeric variable by bin interval %! [B, BG, BC] = groupsummary ((1:6)', [1; 3; 5; 7; 9; 11], [0 6 12], 'sum'); %! assert_equal (B, [6; 15]); %! assert_equal (iscategorical (BG), true); %! assert_equal (cellstr (BG), {'[0, 6)'; '[6, 12]'}); %! assert_equal (BC, [3; 3]); %!test %! ## A GROUPBINS number of bins makes equal-width bins over the data range %! [B, BG] = groupsummary ((1:4)', [1; 2; 3; 4], 3, 'sum'); %! assert_equal (cellstr (BG), {'[1, 2)'; '[2, 3)'; '[3, 4]'}); %! assert_equal (B, [1; 2; 7]); %!test %! ## 'IncludedEdge' 'right' makes the right bin edge the inclusive one %! [B, BG, BC] = groupsummary ((1:3)', [0; 6; 12], [0 6 12], 'sum', ... %! 'IncludedEdge', 'right'); %! assert_equal (cellstr (BG), {'[0, 6]'; '(6, 12]'}); %! assert_equal (BC, [2; 1]); %!test %! ## 'IncludeEmptyGroups' adds unused categorical categories as empty groups %! c = categorical ({'a'; 'a'; 'b'}, {'a', 'b', 'c'}); %! [B, BG, BC] = groupsummary ([10; 20; 30], c, 'sum', ... %! 'IncludeEmptyGroups', true); %! assert_equal (cellstr (BG), {'a'; 'b'; 'c'}); %! assert_equal (B, [30; 30; 0]); %! assert_equal (BC, [2; 1; 0]); %!error groupsummary ([1; 2], [1; 2]) %!error ... %! groupsummary ([1; 2], [1; 2], 'bogus') %!error ... %! groupsummary ([1; 2], [1; 2], 2, 'sum', 'IncludedEdge', 'mid') %!error ... %! groupsummary ([1; 2], [1; 2], 'month', 'sum') %!error ... %! groupsummary ([1; 2; 3], [1; 2], 'sum') pr0m1th3as-datatypes-9c9a8d3/inst/grouptransform.m000066400000000000000000000414661522766574100223130ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the datatypes package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {datatypes} {@var{B} =} grouptransform (@var{A}, @var{groupvars}, @var{method}) ## @deftypefnx {datatypes} {@var{B} =} grouptransform (@var{A}, @var{groupvars}, @var{groupbins}, @var{method}) ## @deftypefnx {datatypes} {[@var{B}, @var{BG}] =} grouptransform (@dots{}) ## @deftypefnx {datatypes} {[@dots{}] =} grouptransform (@dots{}, @var{Name}, @var{Value}) ## ## Transform the columns of an array group by group. ## ## @code{@var{B} = grouptransform (@var{A}, @var{groupvars}, @var{method})} ## groups the rows of the array @var{A} by the grouping variables ## @var{groupvars}, applies @var{method} to each column of @var{A} within each ## group, and returns @var{B}, the transformed values, one row per row of @var{A} ## and in the original order. @var{groupvars} is a grouping vector with one ## element per row of @var{A}, or a cell array of such vectors. Rows holding a ## missing value in a grouping variable form their own groups, which are ## transformed like any other group. ## ## @var{method} is one of the names @qcode{'zscore'}, @qcode{'norm'}, ## @qcode{'meancenter'}, @qcode{'rescale'}, @qcode{'meanfill'}, ## @qcode{'linearfill'}, or a function handle. @qcode{'zscore'} centers and ## scales each group to zero mean and unit standard deviation; @qcode{'norm'} ## divides by the group 2-norm; @qcode{'meancenter'} subtracts the group mean; ## @qcode{'rescale'} rescales to the range @code{[0, 1]}; @qcode{'meanfill'} ## replaces missing values with the group mean; and @qcode{'linearfill'} fills ## missing values by linear interpolation within the group (leaving leading and ## trailing missing values unchanged). For the named methods @code{NaN} values ## are omitted when computing the group statistics. A function handle is applied ## to each group's column slice and must return either a single row (broadcast) ## or one row per row of the group. ## ## @code{[@var{B}, @var{BG}] = grouptransform (@dots{})} also returns @var{BG}, ## the grouping values of each row. When @var{groupvars} is a single grouping ## vector, @var{BG} is that vector; when several grouping variables are given, ## @var{BG} is a cell array with one element per grouping variable. ## ## The @qcode{'ReplaceValues'} @var{Name}/@var{Value} pair (default @code{true}) ## controls whether the transformed values replace the columns of @var{A} or, when ## @code{false}, are appended to them. The optional @var{groupbins} argument bins ## the grouping variables before grouping (a vector of bin edges or a positive ## integer number of bins, or a cell array with one scheme per grouping variable); ## see @code{groupsummary} for details. The @qcode{'IncludedEdge'} pair ## (@qcode{'left'} by default, or @qcode{'right'}) selects the inclusive bin edge. ## ## To transform the variables of a @code{table}, call @code{grouptransform ## (@var{T}, @var{groupvars}, @dots{})}, which dispatches to the @code{table} ## method and returns the result as a table. ## ## @seealso{groupsummary, groupfilter, findgroups, splitapply, table} ## @end deftypefn function [B, varargout] = grouptransform (A, groupvars, varargin) if (nargin < 3) print_usage (); endif ## An optional GROUPBINS positional argument precedes the transform METHOD (a ## known method name or a function handle). args = varargin; hasGB = false; groupbins = []; if (! isempty (args) && __groupbins__ ('is_spec', args{1})) hasGB = true; groupbins = args{1}; args = args(2:end); endif if (isempty (args)) print_usage (); endif method = args{1}; knownMethods = {'zscore', 'norm', 'meancenter', 'rescale', ... 'meanfill', 'linearfill'}; if (is_function_handle (method)) ## ok elseif (((ischar (method) && isrow (method)) || (isa (method, 'string') && isscalar (method))) && any (strcmpi (char (method), knownMethods))) method = lower (char (method)); else error (strcat ("grouptransform: METHOD must be one of 'zscore', 'norm',", ... " 'meancenter', 'rescale', 'meanfill', 'linearfill', or a", ... " function handle.")); endif ## Anything after METHOD must be the 'ReplaceValues' or 'IncludedEdge' ## Name-Value pair (the array form has no DATAVARS argument; A's columns are ## the data). rest = args(2:end); optNames = {'ReplaceValues', 'IncludedEdge'}; nvStart = numel (rest) + 1; for k = 1:numel (rest) a = rest{k}; if (((ischar (a) && isrow (a)) || (isa (a, 'string') && isscalar (a))) && any (strcmpi (char (a), optNames))) nvStart = k; break; endif endfor if (nvStart > 1) error ("grouptransform: too many positional arguments."); endif dfValues = {true, 'left'}; [replaceVals, incEdge] = parsePairedArguments (optNames, dfValues, rest(:)); if (! (isscalar (replaceVals) && (islogical (replaceVals) || isnumeric (replaceVals)))) error ("grouptransform: 'ReplaceValues' must be a logical scalar."); endif replaceVals = logical (replaceVals); incEdge = check_included_edge ('grouptransform', incEdge); ## Force the grouping variables to a cell array of column vectors. if (iscell (groupvars) && ! (iscellstr (groupvars) && isvector (groupvars))) gvs = groupvars(:)'; else gvs = {groupvars}; endif n = size (A, 1); for j = 1:numel (gvs) g = gvs{j}; if (ischar (g)) if (ndims (g) > 2) error ("grouptransform: grouping variables must be vectors."); endif elseif (isvector (g) || isempty (g)) gvs{j} = g(:); else error ("grouptransform: grouping variables must be vectors."); endif if (size (gvs{j}, 1) != n) error (strcat ("grouptransform: each grouping variable must have one", ... " element per row of A.")); endif endfor ## Bin the grouping variables when a GROUPBINS argument was given. if (hasGB) names = cell (1, numel (gvs)); for j = 1:numel (gvs) names{j} = sprintf ("%d", j); endfor [gvs, ~, errmsg] = __groupbins__ ('bin', gvs, names, groupbins, incEdge, ... 'grouptransform'); if (! isempty (errmsg)) error ("grouptransform: %s", errmsg); endif endif ## Group the rows, treating missing grouping values as their own groups so ## that every row belongs to exactly one group. [Grp, ng, errmsg] = gt_group_rows (gvs); if (! isempty (errmsg)) error ("grouptransform: %s", errmsg); endif ## Transform the columns of A, group by group. [trans, errmsg] = gt_transform_col (method, A, Grp, ng); if (! isempty (errmsg)) error ("grouptransform: %s.", errmsg); endif if (replaceVals) B = trans; else B = [A, trans]; endif ## Optional output: the grouping values of each row. if (nargout > 1) if (numel (gvs) == 1) BG = gvs{1}; else BG = gvs; endif varargout{1} = BG; endif endfunction ## Transform one data column COL (n-by-k) group by group, applying METHOD (a ## transform-name char vector or a function handle) to each group's slice and ## returning OUT the same size as COL. G is the n-by-1 group-number vector ## (1..NG), every row assigned to a group. A function handle must return a ## single row (broadcast) or one row per group row. Returns an errmsg body ## emitted by the caller. Self-contained: no table dependency. function [out, errmsg] = gt_transform_col (method, col, G, ng) out = []; errmsg = ''; if (! (isnumeric (col) || islogical (col))) errmsg = sprintf (strcat ("grouptransform requires numeric or logical", ... " data; got '%s'"), class (col)); return; endif x = double (col); out = x; for g = 1:ng rows = find (G == g); if (isempty (rows)) continue; endif slice = x(rows,:); if (is_function_handle (method)) r = method (slice); if (! (isnumeric (r) || islogical (r))) errmsg = "the transform function must return a numeric result"; out = []; return; endif if (size (r, 1) == 1) r = repmat (r, numel (rows), 1); endif if (! isequal (size (r), size (slice))) errmsg = strcat ("the transform function must return a result the", ... " same size as the group, or a single row"); out = []; return; endif out(rows,:) = r; else for c = 1:columns (slice) out(rows,c) = gt_apply_named (method, slice(:,c)); endfor endif endfor endfunction ## Apply a single named transform METHOD to the column vector X, returning the ## transformed values V the same size as X. NaN values are omitted when ## computing the group statistics; the centring and scaling methods leave NaN in ## place, while 'meanfill'/'linearfill' fill them. Self-contained. function v = gt_apply_named (method, x) nan = isnan (x); xo = x(! nan); switch (method) case 'meancenter' v = x - mean (xo); case 'zscore' v = (x - mean (xo)) / std (xo); case 'norm' v = x / norm (xo); case 'rescale' mn = min (xo); mx = max (xo); v = (x - mn) / (mx - mn); case 'meanfill' v = x; v(nan) = mean (xo); case 'linearfill' v = gt_linearfill (x); endswitch endfunction ## Fill the missing values of the column vector X by linear interpolation over ## the non-missing positions, leaving leading and trailing missing values (and ## any group with fewer than two non-missing values) unchanged. Self-contained. function v = gt_linearfill (x) v = x; idx = find (! isnan (x)); if (numel (idx) >= 2) pos = (1:numel (x))'; vi = interp1 (idx, x(idx), pos, "linear"); fill = isnan (x) & pos > idx(1) & pos < idx(end); v(fill) = vi(fill); endif endfunction ## Group rows by the grouping-variable values GVS (a cell array of column ## vectors), treating each variable's missing values as a single group value so ## that every row is assigned to a group. Returns G (1..NGROUPS), NGROUPS, and ## an errmsg body emitted by the caller. Self-contained: no table dependency. function [G, ngroups, errmsg] = gt_group_rows (gvs) errmsg = ''; G = []; ngroups = 0; KEY = []; for j = 1:numel (gvs) [p, m, e] = gt_col_proxy (gvs{j}); if (! isempty (e)) errmsg = e; return; endif pc = p; pc(m,:) = 0; KEY = [KEY, pc, double(m)]; endfor [~, ~, ic] = unique (KEY, "rows"); G = ic; ngroups = max (ic); endfunction ## Build a single-column grouping proxy for one grouping variable COL: a numeric ## matrix P whose value order matches COL's, together with a logical MISS mask ## flagging the missing elements. Returns an errmsg body emitted by the caller. ## Self-contained: no table dependency. function [p, miss, errmsg] = gt_col_proxy (col) p = []; miss = []; errmsg = ''; if (isa (col, 'categorical')) p = double (col)(:); miss = isnan (p); elseif (isa (col, 'string') || iscellstr (col) || ischar (col)) c = cellstr (col); c = c(:); miss = cellfun (@isempty, c); [~, ~, ic] = unique (c); p = ic(:); elseif (isa (col, 'datetime')) DV = datevec (col); nat = any (isnan (DV), 2); DV(nat,:) = 0; DV(nat,2:3) = 1; p = datenum (DV); p(nat) = NaN; p = p(:); miss = isnan (p); elseif (isa (col, 'duration')) p = days (col)(:); miss = isnan (p); elseif (isa (col, 'calendarDuration')) p = proxyArray (col); miss = any (isnan (p), 2); elseif (isnumeric (col) || islogical (col)) p = double (col)(:); miss = isnan (p); else errmsg = sprintf ("unsupported grouping variable type '%s'.", class (col)); endif endfunction ## Validate an 'IncludedEdge' binning option VAL for CALLER, returning it ## lowercased as 'left' or 'right'. Self-contained: no table dependency. function e = check_included_edge (caller, val) if (isa (val, 'string') && isscalar (val)) val = char (val); endif if (! (ischar (val) && isrow (val) ... && any (strcmpi (val, {'left', 'right'})))) error ("%s: 'IncludedEdge' must be 'left' or 'right'.", caller); endif e = lower (val); endfunction %!test %! ## 'meancenter' subtracts the group mean %! g = [1; 1; 1; 2; 2]; %! x = [10; 20; 30; 40; 60]; %! assert_equal (grouptransform (x, g, 'meancenter'), [-10; 0; 10; -10; 10]); %!test %! ## 'zscore', 'norm', and 'rescale' spot checks %! g = [1; 1; 1; 2; 2]; %! x = [10; 20; 30; 40; 60]; %! assert_equal (grouptransform (x, g, 'zscore'), ... %! [-1; 0; 1; -1/sqrt(2); 1/sqrt(2)], 8 * eps); %! assert_equal (grouptransform (x, g, 'norm'), ... %! [[10; 20; 30] ./ norm([10; 20; 30]); ... %! [40; 60] ./ norm([40; 60])], 8 * eps); %! assert_equal (grouptransform (x, g, 'rescale'), [0; 0.5; 1; 0; 1]); %!test %! ## 'meanfill' replaces missing values with the group mean %! g = [1; 1; 2; 2]; %! x = [10; NaN; 30; 40]; %! assert_equal (grouptransform (x, g, 'meanfill'), [10; 10; 30; 40]); %!test %! ## 'linearfill' interpolates interior missing values, leaving the edges NaN %! g = [1; 1; 1; 1; 2; 2; 2]; %! x = [1; NaN; NaN; 4; NaN; 10; NaN]; %! assert_equal (grouptransform (x, g, 'linearfill'), [1; 2; 3; 4; NaN; 10; NaN]); %!test %! ## A function handle is applied to each group's slice %! g = [1; 1; 1; 2; 2]; %! x = [10; 20; 30; 40; 60]; %! assert_equal (grouptransform (x, g, @(v) v - mean (v)), [-10; 0; 10; -10; 10]); %!test %! ## The second output returns the grouping values of each row %! g = [1; 1; 2; 2]; %! x = [10; 20; 30; 40]; %! [B, BG] = grouptransform (x, g, 'norm'); %! assert_equal (B, [[10; 20] ./ norm([10; 20]); [30; 40] ./ norm([30; 40])], ... %! 8 * eps); %! assert_equal (BG, [1; 1; 2; 2]); %!test %! ## A matrix transforms each column independently per group %! A = [10 1; 20 2; 30 3; 40 4]; %! g = [1; 1; 2; 2]; %! assert_equal (grouptransform (A, g, 'meancenter'), ... %! [-5 -0.5; 5 0.5; -5 -0.5; 5 0.5]); %!test %! ## 'ReplaceValues' false appends the transformed columns to A %! g = [1; 1; 1; 2; 2]; %! x = [10; 20; 30; 40; 60]; %! B = grouptransform (x, g, 'meancenter', 'ReplaceValues', false); %! assert_equal (B, [10 -10; 20 0; 30 10; 40 -10; 60 10]); %!test %! ## Rows with a missing grouping value form their own group, in original order %! g = [1; 1; NaN; 2; 2]; %! x = [10; 20; 30; 40; 60]; %! [B, BG] = grouptransform (x, g, 'meancenter'); %! assert_equal (B, [-5; 5; 0; -10; 10]); %! assert_equal (BG, [1; 1; NaN; 2; 2]); %!test %! ## Several grouping vectors return BG as a cell array, one per variable %! x = [10; 20; 30; 40; 60]; %! g1 = [1; 1; 2; 2; 1]; %! g2 = {'a'; 'a'; 'b'; 'b'; 'a'}; %! [B, BG] = grouptransform (x, {g1, g2}, 'meancenter'); %! assert_equal (B, [-20; -10; -5; 5; 30]); %! assert_equal (BG, {[1; 1; 2; 2; 1], {'a'; 'a'; 'b'; 'b'; 'a'}}); %!test %! ## A GROUPBINS edge vector bins the grouping variable before transforming %! x = [10; 20; 30; 40]; %! v = [1; 2; 3; 4]; %! [B, BG] = grouptransform (x, v, [0 2.5 5], 'meancenter'); %! assert_equal (B, [-5; 5; -5; 5]); %! assert_equal (iscategorical (BG), true); %! assert_equal (cellstr (BG), {'[0, 2.5)'; '[0, 2.5)'; '[2.5, 5]'; '[2.5, 5]'}); %!test %! ## A GROUPBINS number of bins makes equal-width bins over the data range %! x = [10; 20; 30; 40]; %! v = [1; 2; 3; 4]; %! B = grouptransform (x, v, 2, 'meancenter'); %! assert_equal (B, [-5; 5; -5; 5]); %!error grouptransform ([1; 2], [1; 2]) %!error ... %! grouptransform ([1; 2], [1; 2], 'bogus') %!error ... %! grouptransform ([1; 2], [1; 2], 2, 'zscore', 'IncludedEdge', 'mid') %!error ... %! grouptransform ([1; 2], [1; 2], 'month', 'meancenter') %!error ... %! grouptransform ([1; 2], [1; 2], 'zscore', 'ReplaceValues', 'yes') %!error ... %! grouptransform ([1; 2], [1; 2], 'zscore', 5) %!error ... %! grouptransform ([1; 2; 3], [1; 2], 'zscore') pr0m1th3as-datatypes-9c9a8d3/inst/hours.m000066400000000000000000000041061522766574100203510ustar00rootroot00000000000000## Copyright (C) 2024-2026 Andreas Bertsatos ## ## This file is part of the datatypes package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {datatypes} {@var{D} =} hours (@var{X}) ## ## Fixed-time duration in hours. ## ## @code{@var{D} = hours (@var{X})} returns a @qcode{duration} array ## representing fixed-time duration hours equivalent to the values in @var{X}, ## which must be a numeric array. ## ## @code{hours} is also available as a method for @qcode{duration} arrays, in ## which case it performs the opposite conversion. ## ## @seealso{duration, years, days, minutes, seconds, milliseconds, ## duration.hours} ## @end deftypefn function out = hours (x) if (nargin == 0) x = 1; elseif (! isnumeric (x)) error ("hours: input array must be numeric."); elseif (! isreal (x)) error ("hours: input array must be real."); endif out = duration (double (x), 0, 0, 'Format', 'h'); endfunction %!demo %! ## `hours` builds a duration from a number of hours; fractional values are fine. %! %! hours (1.5) %! hours ([1, 2.5, 8]) %!test %! X = magic (3); %! D = hours (X); %! assert_equal (size (D), size (X)); %!test %! D = hours ([1, 2, 3]); %! assert_equal (hours (D), [1, 2, 3]); %!test %! D = hours (int16 (1)); %! assert_equal (hours (D), 1); %!test %! D = hours (); %! assert_equal (hours (D), 1); %!error hours ("asd"); %!error hours (1+i); pr0m1th3as-datatypes-9c9a8d3/inst/iscalendarduration.m000066400000000000000000000027471522766574100230750ustar00rootroot00000000000000## Copyright (C) 2024-2026 Andreas Bertsatos ## ## This file is part of the datatypes package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {datatypes} {@var{TF} =} iscalendarduration (@var{X}) ## ## True if input is a @code{calendarDuration} array, false otherwise. ## ## @code{@var{TF} = iscalendarduration (@var{X})} always returns a logical ## scalar, irrespective of the size of @var{X}. ## ## @end deftypefn function TF = iscalendarduration (x) TF = isa (x, 'calendarDuration'); endfunction %!assert_equal (iscalendarduration (calendarDuration (0, 0, 0)), true); %!assert_equal (iscalendarduration (calendarDuration ([1, 2], 0, 0)), true); %!assert_equal (iscalendarduration ([0, 0, 0]), false); %!assert_equal (iscalendarduration ({0, 0, 0}), false); %!assert_equal (iscalendarduration ({calendarDuration(0, 0, 0)}), false); pr0m1th3as-datatypes-9c9a8d3/inst/iscategorical.m000066400000000000000000000026561522766574100220320ustar00rootroot00000000000000## Copyright (C) 2024-2026 Andreas Bertsatos ## ## This file is part of the datatypes package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {datatypes} {@var{TF} =} iscategorical (@var{X}) ## ## True if input is a @code{categorical} array, false otherwise. ## ## @code{@var{TF} = iscategorical (@var{X})} always returns a logical scalar, ## irrespective of the size of @var{X}. ## ## @end deftypefn function TF = iscategorical (x) TF = isa (x, 'categorical'); endfunction %!assert_equal (iscategorical (categorical ([1, 2, 3])), true); %!assert_equal (iscategorical (categorical ([1, 2; 1, 4])), true); %!assert_equal (iscategorical ([0, 1, 2]), false); %!assert_equal (iscategorical ({true, false}), false); %!assert_equal (iscategorical ({categorical([1, 2, 2])}), false); pr0m1th3as-datatypes-9c9a8d3/inst/isdatetime.m000066400000000000000000000026151522766574100213440ustar00rootroot00000000000000## Copyright (C) 2024-2026 Andreas Bertsatos ## ## This file is part of the datatypes package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {datatypes} {@var{TF} =} isdatetime (@var{X}) ## ## True if input is a @code{datetime} array, false otherwise. ## ## @code{@var{TF} = isdatetime (@var{X})} always returns a logical scalar, ## irrespective of the size of @var{X}. ## ## @end deftypefn function TF = isdatetime (x) TF = isa (x, 'datetime'); endfunction %!assert_equal (isdatetime (datetime ([1, 2, 3])), true); %!assert_equal (isdatetime (datetime ([1, 2, 0; 1, 4, 0])), true); %!assert_equal (isdatetime ([0, 1, 2]), false); %!assert_equal (isdatetime ({true, false}), false); %!assert_equal (isdatetime ({datetime([1, 2, 3])}), false); pr0m1th3as-datatypes-9c9a8d3/inst/isduration.m000066400000000000000000000026151522766574100213750ustar00rootroot00000000000000## Copyright (C) 2024-2026 Andreas Bertsatos ## ## This file is part of the datatypes package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {datatypes} {@var{TF} =} isduration (@var{X}) ## ## True if input is a @code{duration} array, false otherwise. ## ## @code{@var{TF} = isduration (@var{X})} always returns a logical scalar, ## irrespective of the size of @var{X}. ## ## @end deftypefn function TF = isduration (x) TF = isa (x, 'duration'); endfunction %!assert_equal (isduration (duration ([1, 2, 3])), true); %!assert_equal (isduration (duration ([1, 2, 0; 1, 4, 0])), true); %!assert_equal (isduration ([0, 1, 2]), false); %!assert_equal (isduration ({true, false}), false); %!assert_equal (isduration ({duration([1, 2, 3])}), false); pr0m1th3as-datatypes-9c9a8d3/inst/isnat.m000066400000000000000000000033571522766574100203360ustar00rootroot00000000000000## Copyright (C) 2025-2026 Andreas Bertsatos ## ## This file is part of the datatypes package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {datatypes} {@var{TF} =} isnat (@var{T}) ## ## Test for Not-A-Time elements in datetime array. ## ## @code{@var{TF} = isnat (@var{T})} returns a logical array @var{TF} of the ## same size as @var{T} containing @qcode{true} for each corresponding ## element of @var{T} that is Not-A-Time (@qcode{NaT}) and @qcode{false} ## otherwise. @qcode{NaT} is the equivalent of @qcode{NaN} in numeric ## arrays. ## ## If @var{T} is not a datetime array, @code{isnat} returns an error. ## ## @end deftypefn function TF = isnat (T) error ("isnat: input argument must be a datetime array."); endfunction %!error isnat (1) %!error isnat ({'asd'}) %!error isnat ('er') %!error isnat (ones (2, 3)) %!error isnat (days (2)) pr0m1th3as-datatypes-9c9a8d3/inst/istable.m000066400000000000000000000025401522766574100206340ustar00rootroot00000000000000## Copyright (C) 2024-2026 Andreas Bertsatos ## ## This file is part of the datatypes package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {datatypes} {@var{TF} =} istable (@var{X}) ## ## True if input is a @code{table}, false otherwise. ## ## @code{@var{TF} = istable (@var{X})} always returns a logical scalar, ## irrespective of the size of @var{X}. ## ## @end deftypefn function TF = istable (x) TF = isa (x, 'table'); endfunction %!assert_equal (istable (table ([1, 2, 3])), true); %!assert_equal (istable (table ([1, 2, 0; 1, 4, 0])), true); %!assert_equal (istable ([0, 1, 2]), false); %!assert_equal (istable ({true, false}), false); %!assert_equal (istable ({table([1, 2, 3])}), false); pr0m1th3as-datatypes-9c9a8d3/inst/keyHash.m000066400000000000000000000135441522766574100206130ustar00rootroot00000000000000## Copyright (C) 2025-2026 Andreas Bertsatos ## ## This file is part of the datatypes package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {datatypes} {@var{hey} =} keyHash (@var{X}) ## @deftypefnx {datatypes} {@var{hey} =} keyHash (@var{X}, @var{base}) ## ## Generate a hash code for an array. ## ## @code{@var{h} = keyHash (@var{X})} generates a @qcode{uint64} scalar that ## represents the input @var{X}, which may be numeric, logical, or character ## array or cell array of character vectors. @code{keyHash} utilizes the 64-bit ## FNV-1a variant of the Fowler-Noll-Vo non-cryptographic hash function. ## ## @code{@var{h} = keyHash (@var{X}, @var{base})} also generates a 64-bit ## hash code using @var{base} as the offset basis for the FNV-1a hash ## algorithm. @var{base} must be a @qcode{uint64} integer type scalar. Use ## this syntax to cascade @code{keyHash} on multiple objects for which a ## single hash code is required. ## ## Note that unlike MATLAB, this implementation does not use any random seed. ## As a result, @code{keyHash} will always generate the exact same hash key ## for any particular input across different workers and Octave sessions. ## ## @end deftypefn function key = keyHash (x = [], base = []) ## Validate input if (nargin < 1) print_usage; endif ## Initialize string with size and class name size_str = sprintf ('%dx', size (x))(1:end-1); init_str = [size_str class(x)]; if (base) if (! (isscalar (base) && isa (base, 'uint64'))) error ("keyHash: BASE must be a UINT64 scalar."); endif key = __ckeyHash__ (init_str, base); else key = __ckeyHash__ (init_str); endif ## Select data type if (isnumeric (x) || islogical (x)) key = __nkeyHash__ (x(:), key); elseif (ischar (x)) key = __ckeyHash__ (x(:), key); elseif (iscellstr (x)) key = __ckeyHash__ ([x{:}], key); else error ("keyHash: unsupported input type."); endif endfunction %!test %! key = keyHash (1); %! assert_equal (isscalar (key), true); %! key = keyHash ([1:5]); %! assert_equal (isscalar (key), true); %!test %! key1 = keyHash (1); %! key2 = keyHash (1, 0xcbf29ce484222325); # default offset basis %! assert_equal (key1, key2); %!test %! key1 = keyHash (0); %! assert_equal (class (key1), 'uint64'); %!test %! A = [1:5]; %! B = [1:5]; %! key1 = keyHash (A); %! key2 = keyHash (B); %! assert_equal (key1, key2); %!test %! A = [1:5]; %! B = [1:5]'; %! key1 = keyHash (A); %! key2 = keyHash (B); %! assert_equal (isequal (key1, key2), false); %!test %! A = ''; %! E = uint64 (15921358368119480423); %! key = keyHash (A); %! assert (isequal (key, E), sprintf ("k: %lx e: %lx d: %lx", key, E, key - E)); %! assert_equal (key, E); %!test %! A = uint64 (128); %! E = uint64 (8038837787959150693); %! key = keyHash (A); %! assert (isequal (key, E), sprintf ("k: %lx e: %lx d: %lx", key, E, key - E)); %! assert_equal (key, E); ## Add more tests on endianness (see GitHub issue 43) %!assert_equal (keyHash ([1, 2]), uint64 (1291405323040189622)) %!assert_equal (keyHash ([1+2i]), uint64 (4391366703481493027)) %!assert_equal (keyHash (single ([1, 2])), uint64 (10607299016200876763)) %!assert_equal (keyHash (int64 ([1, 2])), uint64 (8387921810608003298)) %!assert_equal (keyHash (uint64 ([1, 2])), uint64 (15129359585364357711)) %!assert_equal (keyHash (int32 ([1, 2])), uint64 (7340059757986521701)) %!assert_equal (keyHash (uint32 ([1, 2])), uint64 (6705001899901839972)) %!assert_equal (keyHash (int16 ([1, 2])), uint64 (2451959940856797955)) %!assert_equal (keyHash (uint16 ([1, 2])), uint64 (10303904549507999050)) %!assert_equal (keyHash (int8 ([1, 2])), uint64 (3623669810820070662)) %!assert_equal (keyHash (uint8 ([1, 2])), uint64 (12877398120156278681)) %!assert_equal (keyHash (logical ([1, 2])), uint64 (16918259908176314871)) %!assert_equal (__nkeyHash__ ([1, 2]), uint64 (4062974330926783736)) %!assert_equal (__nkeyHash__ ([1+2i]), uint64 (4062974330926783736)) %!assert_equal (__nkeyHash__ (single ([1, 2])), uint64 (10375111465485032152)) %!assert_equal (__nkeyHash__ (int64 ([1, 2])), uint64 (8581494755304202342)) %!assert_equal (__nkeyHash__ (uint64 ([1, 2])), uint64 (8581494755304202342)) %!assert_equal (__nkeyHash__ (int32 ([1, 2])), uint64 (14538333428393601222)) %!assert_equal (__nkeyHash__ (uint32 ([1, 2])), uint64 (14538333428393601222)) %!assert_equal (__nkeyHash__ (int16 ([1, 2])), uint64 (12479921481467174326)) %!assert_equal (__nkeyHash__ (uint16 ([1, 2])), uint64 (12479921481467174326)) %!assert_equal (__nkeyHash__ (int8 ([1, 2])), uint64 (589729691727335466)) %!assert_equal (__nkeyHash__ (uint8 ([1, 2])), uint64 (589729691727335466)) %!assert_equal (__nkeyHash__ (logical ([1, 2])), uint64 (589728592215707255)) ## Add more tests on different architectures (see GitHub issues 38 and 40) %!assert_equal (__nkeyHash__ (uint8 (128)), uint64 (5808531584386460767)) %!assert_equal (keyHash (uint8 (128)), uint64 (10759574069356082695)) %!assert_equal (__nkeyHash__ (uint32 (0xdeadbeef)), ... %! uint64 (12840711468051582507)) %!assert_equal (keyHash (uint32 (0xdeadbeef)), uint64 (3790509136731937468)) %!error keyHash (); %!error keyHash (1, 1); %!error keyHash (@(x) x); pr0m1th3as-datatypes-9c9a8d3/inst/keyMatch.m000066400000000000000000000032351522766574100207600ustar00rootroot00000000000000## Copyright (C) 2025-2026 Andreas Bertsatos ## ## This file is part of the datatypes package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {datatypes} {@var{TF} =} keyMatch (@var{A}, @var{B}) ## ## Return true if both inputs have the same hash code. ## ## @code{@var{TF} = keyMatch (@var{A}, @var{B})} returns a logical scalar, ## which is @qcode{true}, if both inputs, @var{A} and @var{B}, have the same ## FNV-1a 64-bit hash code, and @qcode{false} otherwise. ## ## @end deftypefn function TF = keyMatch (A, B) if (nargin != 2) print_usage; endif if (! strcmp (class (A), class (B))) TF = false; elseif (! isequal (size (A), size (B))) TF = false; else A_key = keyHash (A); B_key = keyHash (B); TF = A_key == B_key; endif endfunction %!assert_equal (keyMatch (1, {'1'}), false); %!assert_equal (keyMatch (ones (2), 2), false); %!assert_equal (keyMatch (1, 2), false); %!assert_equal (keyMatch (1, 1), true); %!error keyMatch (2); pr0m1th3as-datatypes-9c9a8d3/inst/milliseconds.m000066400000000000000000000042651522766574100217040ustar00rootroot00000000000000## Copyright (C) 2024-2026 Andreas Bertsatos ## ## This file is part of the datatypes package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {datatypes} {@var{D} =} milliseconds (@var{X}) ## ## Fixed-time duration in milliseconds. ## ## @code{@var{D} = milliseconds (@var{X})} returns a @qcode{duration} array ## representing fixed-time duration milliseconds equivalent to the values in ## @var{X}, which must be a numeric array. ## ## @code{milliseconds} is also available as a method for @qcode{duration} ## arrays, in which case it performs the opposite conversion. ## ## @seealso{duration, years, days, hours, minutes, seconds, ## duration.milliseconds} ## @end deftypefn function out = milliseconds (x) if (nargin == 0) x = 1; elseif (! isnumeric (x)) error ("milliseconds: input array must be numeric."); elseif (! isreal (x)) error ("milliseconds: input array must be real."); endif out = duration (0, 0, double (x) / 1000, 'Format', 's'); endfunction %!demo %! ## `milliseconds` builds a duration from a number of milliseconds. %! %! milliseconds (1500) %!test %! X = magic (3); %! D = milliseconds (X); %! assert_equal (size (D), size (X)); %!test %! D = milliseconds ([1, 2, 3]); %! assert_equal (milliseconds (D), [1, 2, 3]); %!test %! D = milliseconds (int16 (1)); %! assert_equal (milliseconds (D), 1); %!test %! D = milliseconds (); %! assert_equal (milliseconds (D), 1); %!error milliseconds ("asd"); %!error milliseconds (1+i); pr0m1th3as-datatypes-9c9a8d3/inst/minutes.m000066400000000000000000000042041522766574100206740ustar00rootroot00000000000000## Copyright (C) 2024-2026 Andreas Bertsatos ## ## This file is part of the datatypes package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {datatypes} {@var{D} =} minutes (@var{X}) ## ## Fixed-time duration in minutes. ## ## @code{@var{D} = minutes (@var{X})} returns a @qcode{duration} array ## representing fixed-time duration minutes equivalent to the values in @var{X}, ## which must be a numeric array. ## ## @code{minutes} is also available as a method for @qcode{duration} arrays, in ## which case it performs the opposite conversion. ## ## @seealso{duration, years, days, hours, seconds, milliseconds, ## duration.minutes} ## @end deftypefn function out = minutes (x) if (nargin == 0) x = 1; elseif (! isnumeric (x)) error ("minutes: input array must be numeric."); elseif (! isreal (x)) error ("minutes: input array must be real."); endif out = duration (0, double (x), 0, 'Format', 'm'); endfunction %!demo %! ## `minutes` builds a duration from a number of minutes. %! %! minutes (90) %! %! ## Combine builders by adding them. %! hours (1) + minutes (30) %!test %! X = magic (3); %! D = minutes (X); %! assert_equal (size (D), size (X)); %!test %! D = minutes ([1, 2, 3]); %! assert_equal (minutes (D), [1, 2, 3]); %!test %! D = minutes (int16 (1)); %! assert_equal (minutes (D), 1); %!test %! D = minutes (); %! assert_equal (minutes (D), 1); %!error minutes ("asd"); %!error minutes (1+i); pr0m1th3as-datatypes-9c9a8d3/inst/missing.m000066400000000000000000000607011522766574100206650ustar00rootroot00000000000000## Copyright (C) 2024-2026 Andreas Bertsatos ## ## This file is part of the datatypes package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . classdef missing ## -*- texinfo -*- ## @deftp {datatypes} missing ## ## Array of missing values. ## ## A special class to represent missing data to other data types. ## ## @end deftp properties (SetAccess = private, Hidden) data = NaN endproperties methods (Hidden) ## Custom display function display (this) in_name = inputname (1); if (! isempty (in_name)) fprintf ('%s =\n', in_name); endif __disp__ (this, 'missing', in_name); endfunction ## Custom display function disp (this) __disp__ (this, 'missing'); endfunction endmethods ################################################################################ ## ** Create and convert 'missing' type ** ## ################################################################################ ## Available Methods ## ## ## ## 'missing' 'dispstrings' 'cellstr' 'double' ## ## 'single' 'calendarDuration' 'categorical' 'datetime' ## ## 'duration' ## ## ## ################################################################################ methods (Access = public) ## -*- texinfo -*- ## @deftypefn {missing} {@var{M} =} missing () ## ## Create missing values. ## ## @code{missing} always returns a scalar missing value. Use @code{repmat} ## to expand a scalar missing value to a missing array of desired ## dimensions. ## ## @end deftypefn function this = missing () if (nargin > 0 ) error ("missing: too many input arguments."); endif endfunction endmethods methods (Hidden) function cstr = dispstrings (this) cstr = repmat ({''}, size (this)); endfunction function cstr = cellstr (this) cstr = dispstrings (this); endfunction function out = double (this) out = this.data; endfunction function out = single (this) out = single (this.data); endfunction function out = calendarDuration (this) out = calendarDuration (NaN (size (this)), NaN, NaN); endfunction function out = categorical (this) out = categorical (NaN (size (this))); endfunction function out = datetime (this) out = NaT (size (this)); endfunction function out = duration (this) out = duration (NaN (size (this))); endfunction ## Overload isequal for categorical support function TF = isequal (varargin) ## Check for categorical input idx = find (cellfun ('iscategorical', varargin), 1); if (isempty (idx)) if (any (cellfun (@(x) ! isa (x, 'missing'), varargin))) error ("missing.isequal: unsupported input types."); endif data = cellfun (@(x) x.data, varargin, 'UniformOutput', false); TF = isequal (data{:}); else ## Convert first input (missing) to categorical varargin{1} = categorical (varargin{1}); ## Call categorical overloaded method TF = isequal (varargin{:}); endif endfunction ## Overload isequaln for categorical support function TF = isequaln (varargin) ## Check for categorical input idx = find (cellfun ('iscategorical', varargin), 1); if (isempty (idx)) if (any (cellfun (@(x) ! isa (x, 'missing'), varargin))) error ("missing.isequaln: unsupported input types."); endif data = cellfun (@(x) x.data, varargin, 'UniformOutput', false); TF = isequaln (data{:}); else ## Convert first input (missing) to categorical varargin{1} = categorical (varargin{1}); ## Call categorical overloaded method TF = isequaln (varargin{:}); endif endfunction endmethods ################################################################################ ## ** Summary Information ** ## ################################################################################ ## Available Methods ## ## ## ## 'size' 'ndims' 'numel' ## ## ## ################################################################################ methods (Access = public) ## -*- texinfo -*- ## @deftypefn {missing} {@var{sz} =} size (@var{M}) ## @deftypefnx {missing} {@var{dim_sz} =} size (@var{M}, @var{dim}) ## @deftypefnx {missing} {@var{dim_sz} =} size (@var{M}, @var{d1}, @var{d2}, @dots{}) ## @deftypefnx {missing} {[@var{rows}, @var{columns}, @dots{}, @var{dim_n_sz}] =} size (@dots{}) ## ## Return the size of a missing array. ## ## @code{@var{sz} = size (@var{M})} returns a row vector with the size ## (number of elements) of each dimension for the missing array @var{M}. ## ## @code{@var{dim_sz} = size (@var{M}, @var{dim})} returns the size of ## the corresponding dimension specified in @var{dim}. If @var{dim} is a ## vector, then @var{dim_sz} is a vector of the same length and with each ## element corresponding to a specified dimension. Multiple dimensions may ## also be specified as separate arguments. ## ## With a single output argument, @code{size} returns a row vector. When ## called with multiple output arguments, @code{size} returns the size of ## dimension N in the Nth argument. ## ## @end deftypefn function varargout = size (this, varargin) if (! isempty (varargin)) sz = size (this.data, varargin{:}); else sz = size (this.data); endif if (nargout == 0 || nargout == 1) varargout{1} = sz; elseif (numel (sz) != nargout) error (["missing.size: nargout > 1 but does not", ... " match number of requested dimensions."]); else for i = 1:nargout varargout{i} = sz(i); endfor endif endfunction ## -*- texinfo -*- ## @deftypefn {missing} {@var{out} =} ndims (@var{M}) ## ## Number of dimensions in a missing array. ## ## @code{@var{out} = ndims (@var{M})} returns the number of dimensions of ## the missing array @var{M}. ## ## @end deftypefn function out = ndims (this) out = ndims (this.data); endfunction ## -*- texinfo -*- ## @deftypefn {missing} {@var{out} =} numel (@var{M}) ## ## Total number of elements in a missing array. ## ## @code{@var{out} = numel (@var{M})} returns the number of elements in the ## missing array @var{M}. ## ## @end deftypefn function out = numel (this) out = numel (this.data); endfunction endmethods ################################################################################ ## ** Query Operations ** ## ################################################################################ ## Available Methods ## ## ## ## 'iscolumn' 'isempty' 'ismatrix' 'ismissing' ## ## 'isrow' 'isscalar' 'isvector' ## ## ## ################################################################################ methods (Access = public) ## -*- texinfo -*- ## @deftypefn {missing} {@var{TF} =} iscolumn (@var{M}) ## ## Return true if missing array is a column vector. ## ## @code{@var{TF} = iscolumn (@var{M})} returns a logical scalar @var{TF}, ## which is @qcode{true} if the missing array @var{M} is a column vector ## and @qcode{false} otherwise. A column vector is a 2-D array for which ## @code{size (@var{X})} returns @code{[@var{N}, 1]} with non-negative ## @var{N}. ## ## @end deftypefn function out = iscolumn (this) out = iscolumn (this.data); endfunction ## -*- texinfo -*- ## @deftypefn {missing} {@var{TF} =} isempty (@var{M}) ## ## Return true if missing array is empty. ## ## @code{@var{TF} = isempty (@var{M})} returns a logical scalar @var{TF}, ## which is @qcode{true} if the missing array @var{M} is empty and ## @qcode{false} otherwise. ## ## @end deftypefn function out = isempty (this) out = isempty (this.data); endfunction ## -*- texinfo -*- ## @deftypefn {missing} {@var{TF} =} ismatrix (@var{M}) ## ## Return true if missing array is a 2-D array. ## ## @code{@var{TF} = ismatrix (@var{M})} returns a logical scalar @var{TF}, ## which is @qcode{true} if the missing array @var{M} is a matrix and ## @qcode{false} otherwise. A matrix is an array of any type where ## @code{ndims (@var{X}) == 2} and for which @code{size (@var{X})} returns ## @code{[@var{H}, @var{W}]} with non-negative @var{H} and @var{W}. ## ## @end deftypefn function out = ismatrix (this) out = ismatrix (this.data); endfunction ## -*- texinfo -*- ## @deftypefn {missing} {@var{out} =} ismissing (@var{M}) ## ## Return true for each element in missing array. ## ## @code{@var{TF} = ismissing (@var{M})} returns a logical array @var{TF} ## of the same size as @var{M} containing @qcode{true} in every element. ## ## @end deftypefn function out = ismissing (this) out = true (size (this)); endfunction ## -*- texinfo -*- ## @deftypefn {missing} {@var{TF} =} isrow (@var{M}) ## ## Return true if missing array is a row vector. ## ## @code{@var{TF} = isrow (@var{M})} returns a logical scalar @var{TF}, ## which is @qcode{true} if the missing array @var{M} is a row vector ## and @qcode{false} otherwise. A row vector is a 2-D array for which ## @code{size (@var{X})} returns @code{[1, @var{N}]} with non-negative ## @var{N}. ## ## @end deftypefn function out = isrow (this) out = isrow (this.data); endfunction ## -*- texinfo -*- ## @deftypefn {missing} {@var{TF} =} isscalar (@var{M}) ## ## Return true if missing array is a scalar. ## ## @code{@var{TF} = isscalar (@var{M})} returns a logical scalar @var{TF}, ## which is @qcode{true} if the missing array @var{M} is also a scalar ## and @qcode{false} otherwise. A scalar is a single element object for ## which @code{size (@var{X})} returns @code{[1, 1]}. ## ## @end deftypefn function out = isscalar (this) out = isscalar (this.data); endfunction ## -*- texinfo -*- ## @deftypefn {missing} {@var{TF} =} isvector (@var{M}) ## ## Return true if missing array is a vector. ## ## @code{@var{TF} = isvector (@var{M})} returns a logical scalar @var{TF}, ## which is @qcode{true} if the missing array @var{M} is a vector and ## @qcode{false} otherwise. A vector is a 2-D array for which one of the ## dimensions is equal to 1 (either @math{1*N} or @math{N*1}). By ## definition, a scalar is also a vector. ## ## @end deftypefn function out = isvector (this) out = isvector (this.data); endfunction endmethods ################################################################################ ## ** Relational Operations ** ## ################################################################################ ## Available Methods ## ## ## ## 'eq' 'ge' 'gt' 'le' ## ## 'lt' 'ne' ## ## ## ################################################################################ methods (Access = public) ## -*- texinfo -*- ## @deftypefn {missing} {@var{TF} =} eq (@var{A}, @var{B}) ## ## Equality for missing arrays. ## ## @code{@var{TF} = eq (@var{A}, @var{B})} is the equivalent of the syntax ## @code{@var{TF} = @var{A} == @var{B}} and returns a logical array of the ## same size as the largest input with its elements set to @qcode{false}. ## @var{A} and @var{B} must be size compatible, which translates to they can ## be the same size, one can be scalar, or for every dimension, their ## dimension sizes must be equal or one of them must be 1. ## ## One of the input arguments may also be any type of array. Any comparison ## with missing arrays always returns @qcode{false}. ## ## @end deftypefn function TF = eq (A, B) A = nan (size (A)); B = nan (size (B)); try TF = A == B; catch error ("missing.eq: arrays have incompatible sizes."); end_try_catch endfunction ## -*- texinfo -*- ## @deftypefn {missing} {@var{TF} =} ge (@var{A}, @var{B}) ## ## Greater than or equal to for missing arrays. ## ## @code{@var{TF} = ge (@var{A}, @var{B})} is the equivalent of the syntax ## @code{@var{TF} = @var{A} >= @var{B}} and returns a logical array of the ## same size as the largest input with its elements set to @qcode{false}. ## @var{A} and @var{B} must be size compatible, which translates to they can ## be the same size, one can be scalar, or for every dimension, their ## dimension sizes must be equal or one of them must be 1. ## ## One of the input arguments may also be any type of array. Any comparison ## with missing arrays always returns @qcode{false}. ## ## @end deftypefn function TF = ge (A, B) A = nan (size (A)); B = nan (size (B)); try TF = A >= B; catch error ("missing.ge: arrays have incompatible sizes."); end_try_catch endfunction ## -*- texinfo -*- ## @deftypefn {missing} {@var{TF} =} gt (@var{A}, @var{B}) ## ## Greater than for missing arrays. ## ## @code{@var{TF} = gt (@var{A}, @var{B})} is the equivalent of the syntax ## @code{@var{TF} = @var{A} > @var{B}} and returns a logical array of the ## same size as the largest input with its elements set to @qcode{false}. ## @var{A} and @var{B} must be size compatible, which translates to they can ## be the same size, one can be scalar, or for every dimension, their ## dimension sizes must be equal or one of them must be 1. ## ## One of the input arguments may also be any type of array. Any comparison ## with missing arrays always returns @qcode{false}. ## ## @end deftypefn function TF = gt (A, B) A = nan (size (A)); B = nan (size (B)); try TF = A > B; catch error ("missing.gt: arrays have incompatible sizes."); end_try_catch endfunction ## -*- texinfo -*- ## @deftypefn {missing} {@var{TF} =} le (@var{A}, @var{B}) ## ## Less than or equal to for missing arrays. ## ## @code{@var{TF} = le (@var{A}, @var{B})} is the equivalent of the syntax ## @code{@var{TF} = @var{A} <= @var{B}} and returns a logical array of the ## same size as the largest input with its elements set to @qcode{false}. ## @var{A} and @var{B} must be size compatible, which translates to they can ## be the same size, one can be scalar, or for every dimension, their ## dimension sizes must be equal or one of them must be 1. ## ## One of the input arguments may also be any type of array. Any comparison ## with missing arrays always returns @qcode{false}. ## ## @end deftypefn function TF = le (A, B) A = nan (size (A)); B = nan (size (B)); try TF = A <= B; catch error ("missing.le: arrays have incompatible sizes."); end_try_catch endfunction ## -*- texinfo -*- ## @deftypefn {missing} {@var{TF} =} lt (@var{A}, @var{B}) ## ## Less than for missing arrays. ## ## @code{@var{TF} = lt (@var{A}, @var{B})} is the equivalent of the syntax ## @code{@var{TF} = @var{A} < @var{B}} and returns a logical array of the ## same size as the largest input with its elements set to @qcode{false}. ## @var{A} and @var{B} must be size compatible, which translates to they can ## be the same size, one can be scalar, or for every dimension, their ## dimension sizes must be equal or one of them must be 1. ## ## One of the input arguments may also be any type of array. Any comparison ## with missing arrays always returns @qcode{false}. ## ## @end deftypefn function TF = lt (A, B) A = nan (size (A)); B = nan (size (B)); try TF = A < B; catch error ("missing.lt: arrays have incompatible sizes."); end_try_catch endfunction ## -*- texinfo -*- ## @deftypefn {missing} {@var{TF} =} ne (@var{A}, @var{B}) ## ## Not equal for missing arrays. ## ## @code{@var{TF} = ne (@var{A}, @var{B})} is the equivalent of the syntax ## @code{@var{TF} = @var{A} != @var{B}} and returns a logical array of the ## same size as the largest input with its elements set to @qcode{false}. ## @var{A} and @var{B} must be size compatible, which translates to they can ## be the same size, one can be scalar, or for every dimension, their ## dimension sizes must be equal or one of them must be 1. ## ## One of the input arguments may also be any type of array. Any comparison ## with missing arrays always returns @qcode{false}. ## ## @end deftypefn function TF = ne (A, B) A = nan (size (A)); B = nan (size (B)); try TF = A != B; catch error ("missing.ne: arrays have incompatible sizes."); end_try_catch endfunction endmethods ################################################################################ ## ** Array Operations ** ## ################################################################################ ## Available Methods ## ## ## ## 'cat' 'horzcat' 'vertcat' 'repmat' ## ## 'reshape' 'squeeze' 'transpose' 'ctranspose' ## ## ## ################################################################################ methods (Hidden) function out = cat (dim, varargin) datatype = ''; for i = 1:numel (varargin) if (isa (varargin{i}, 'missing')) continue elseif (isa (varargin{i}, 'calendarDuration')) datatype = 'calendarDuration'; elseif (iscellstr (varargin{i})) datatype = 'cellstr'; elseif (isa (varargin{i}, 'categorical')) datatype = 'categorical'; elseif (isa (varargin{i}, 'datetime')) datatype = 'datetime'; elseif (isa (varargin{i}, 'double')) datatype = 'double'; elseif (isa (varargin{i}, 'duration')) datatype = 'duration'; elseif (isa (varargin{i}, 'single')) datatype = 'single'; elseif (isa (varargin{i}, 'string')) datatype = 'string'; else error ("missing.cat: cannot convert 'missing' to '%s' type.", ... class (varargin{i})); endif endfor if (isempty (datatype)) sz = cellfun (@(x) x.data, varargin, 'UniformOutput', false); out = varargin{1}; out.data = cat (dim, sz{:}); else args = varargin; for i = 1:numel (varargin) if (isa (varargin{i}, 'missing')) switch datatype case 'calendarDuration' args{i} = calendarDuration (NaN (size (varargin{i})), NaN, NaN); case 'cellstr' args{i} = repmat ({''}, size (varargin{i})); case 'categorical' args{i} = categorical (NaN (size (varargin{i}))); case 'datetime' args{i} = NaT (size (varargin{i})); case 'double' args{i} = NaN (size (varargin{i})); case 'single' args{i} = single (NaN (size (varargin{i}))); case 'string' args{i} = string (NaN (size (varargin{i}))); endswitch endif endfor out = cat (dim, args{:}); endif endfunction function out = horzcat (varargin) out = cat (2, varargin{:}); endfunction function out = vertcat (varargin) out = cat (1, varargin{:}); endfunction function this = repmat (this, varargin) this.data = repmat (this.data, varargin{:}); endfunction function this = reshape (this, varargin) this.data = reshape (this.data, varargin{:}); endfunction function this = squeeze (this, varargin) this.data = squeeze (this.data, varargin{:}); endfunction function this = ctranspose (this, varargin) this.data = ctranspose (this.data, varargin{:}); endfunction function this = transpose (this, varargin) this.data = transpose (this.data, varargin{:}); endfunction endmethods ################################################################################ ## ** Reference and Assignment Operations ** ## ################################################################################ ## Available Methods ## ## ## ## 'subsref' 'subsasgn' ## ## ## ################################################################################ methods (Hidden) ## Class specific subscripted reference function varargout = subsref (this, s) chain_s = s(2:end); s = s(1); switch (s.type) case '()' out = this; out.data = this.data(s.subs{:}); case '{}' error (["missing.subsref: '{}' invalid indexing", ... " for referencing values. Use '()' instead."]); case '.' error (["missing.subsref: '.' invalid indexing", ... " for referencing values. Use '()' instead."]); endswitch ## Chained references if (! isempty (chain_s)) out = subsref (out, chain_s); endif varargout{1} = out; endfunction ## Class specific subscripted assignment function this = subsasgn (this, s, val) if (numel (s) > 1) error ("missing.subsasgn: chained subscripts not allowed."); endif switch (s.type) case '()' if (isa (val, "missing")) this.data(s.subs{:}) = val.data; elseif (isempty (val)) this.data(s.subs{:}) = []; else error ("missing.subsasgn: unable to perform assignment."); endif case '{}' error (["missing.subsasgn: '{}' invalid indexing", ... " for assigning values. Use '()' instead."]); case '.' error (["missing.subsasgn: '.' invalid indexing", ... " for assigning values. Use '()' instead."]); endswitch endfunction endmethods endclassdef pr0m1th3as-datatypes-9c9a8d3/inst/ods2struct.m000066400000000000000000000114271522766574100213310ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the datatypes package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {datatypes} {@var{s} =} ods2struct (@var{filename}) ## ## Read every sheet of an OpenDocument spreadsheet into a scalar structure. ## ## @code{@var{s} = ods2struct (@var{filename})} reads each data sheet of the ## OpenDocument spreadsheet named by @var{filename} into a @code{table} and ## returns a scalar structure with one field per sheet, in sheet order. Both ## the compressed @qcode{.ods} and the flat @qcode{.fods} formats are read. ## Each sheet is reconstructed exactly as by @code{ods2table}; it is the inverse ## of @code{struct2ods}. ## ## A sheet name that is not a valid structure field name is canonicalised with ## @code{matlab.lang.makeValidName} (and made unique if two sheet names collide). ## Whenever the field name differs from the sheet name, the original sheet name ## is stored on that field's table as the @qcode{'ActualSheetName'} custom ## property, so a subsequent @code{struct2ods} restores the exact sheet name. ## ## @seealso{struct2ods, ods2table, table2ods, readtable} ## @end deftypefn function s = ods2struct (filename) if (nargin != 1) print_usage (); endif if (! (ischar (filename) || iscellstr (filename) || isa (filename, 'string'))) error ("ods2struct: FILENAME must be a character vector, cellstr, or string."); endif file = char (cellstr (filename)); ## Enumerate the data sheet names (the first output doubles as an error probe). [data, ~, ~, names] = __ods2table__ (file); if (ischar (data)) error ("ods2struct: %s", data); endif s = struct (); usedFields = {}; for k = 1:numel (names) sn = names{k}; R = ods2table (file, 'Sheet', sn); fn = matlab.lang.makeValidName (sn); ## Make the field name unique if canonicalisation collided with an earlier ## one; the ActualSheetName property below preserves the true sheet name. base = fn; j = 1; while (any (strcmp (fn, usedFields))) fn = sprintf ('%s_%d', base, j); j += 1; endwhile usedFields{end+1} = fn; ## Stash the original sheet name whenever the field name had to change. if (! strcmp (fn, sn)) R = addprop (R, 'ActualSheetName', 'table'); R.Properties.CustomProperties.ActualSheetName = sn; endif s.(fn) = R; endfor endfunction %!demo %! ## `ods2struct` is the inverse of `struct2ods`: it reads *every* data sheet of %! ## a workbook into one scalar struct, one field per sheet, in sheet order. %! %! wb.Patients = table ({'Li'; 'Diaz'}, [38; 40], 'VariableNames', {'Name', 'Age'}); %! wb.Visits = table ([1; 2; 3], 'VariableNames', {'Visit'}); %! filename = fullfile (tempdir (), 'clinic.ods'); %! struct2ods (filename, wb); %! %! s = ods2struct (filename); %! s.Patients %! %! delete (filename); %!test # round-trip a multi-sheet workbook written by struct2ods %! s.alpha = table ([1; 2; 3], {'a'; 'b'; 'c'}, 'VariableNames', {'x', 'y'}); %! s.beta = table ([10.5; 20.5], 'VariableNames', {'v'}); %! fn = [tempname() '.ods']; %! unwind_protect %! struct2ods (fn, s); %! r = ods2struct (fn); %! assert_equal (fieldnames (r), {'alpha'; 'beta'}); %! assert_equal (r.alpha.x, [1; 2; 3]); %! assert_equal (r.beta.v, [10.5; 20.5]); %! unwind_protect_cleanup %! delete (fn); %! end_unwind_protect %!test # an odd sheet name is canonicalised and stashed in ActualSheetName %! T = table ([7; 8], 'VariableNames', {'v'}); %! T = addprop (T, 'ActualSheetName', 'table'); %! T.Properties.CustomProperties.ActualSheetName = 'My Sheet!'; %! s.only = T; %! fn = [tempname() '.fods']; %! unwind_protect %! struct2ods (fn, s); %! r = ods2struct (fn); %! f = fieldnames (r); %! assert_equal (numel (f), 1); %! assert_equal (isvarname (f{1}), true); %! assert_equal (r.(f{1}).Properties.CustomProperties.ActualSheetName, ... %! 'My Sheet!'); %! assert_equal (r.(f{1}).v, [7; 8]); %! unwind_protect_cleanup %! delete (fn); %! end_unwind_protect %!error ... %! ods2struct (42) %!error ... %! ods2struct ([tempname() '.ods']) pr0m1th3as-datatypes-9c9a8d3/inst/ods2table.m000066400000000000000000000575231522766574100211030ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the datatypes package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {datatypes} {@var{tbl} =} ods2table (@var{filename}) ## @deftypefnx {datatypes} {@var{tbl} =} ods2table (@var{filename}, @qcode{'Sheet'}, @var{sheet}) ## ## Read an OpenDocument spreadsheet file into a table. ## ## @code{@var{tbl} = ods2table (@var{filename})} reads the OpenDocument ## spreadsheet named by @var{filename}, which may be a character vector, a ## cellstr, or a string scalar, and returns it as a @code{table}. Both the ## compressed @qcode{.ods} and the flat @qcode{.fods} formats are read; the ## format is detected from the file contents, not its extension. ## ## @code{@var{tbl} = ods2table (@dots{}, @qcode{'Sheet'}, @var{sheet})} reads a ## specific sheet, selected either by its name (a character vector or string ## scalar) or by a 1-based index over the data sheets. Without this option the ## first data sheet is read. ## ## When the file carries the hidden @qcode{__datatypes_meta__} sheet written by ## the @code{table2ods} method, the variable types, names, descriptions, and ## units are restored from it, and @code{date} and @code{time} cells are ## reconstructed as @code{datetime} and @code{duration} arrays. Integers are ## restored without loss of precision and missing cells become @code{NaN}, ## @code{NaT}, or missing strings as appropriate. ## ## When the metadata sheet is absent (a spreadsheet written by another ## application) the variable types are inferred from the cell value types and ## the variables are named @qcode{Var1}, @qcode{Var2}, and so on. ## ## The following round-trip limitations apply, mirroring @code{csv2table}: ## @code{calendarDuration} and @code{categorical} variables are returned as cell ## arrays of character vectors (their values are not reconstructed), missing ## @code{string} values are read back as empty strings, and datetime and ## duration display formats are not preserved, although the values themselves ## are exact. ## ## @end deftypefn function tbl = ods2table (filename, varargin) if (nargin < 1) print_usage (); endif if (! (ischar (filename) || iscellstr (filename) || isa (filename, 'string'))) error ("ods2table: FILENAME must be a character vector, cellstr, or string."); endif file = char (cellstr (filename)); optNames = {'Sheet'}; dfValues = {[]}; [sheet, args] = parsePairedArguments (optNames, dfValues, varargin(:)); if (! isempty (args)) error ("ods2table: unknown option '%s'.", args{1}); endif if (! (isempty (sheet) || (ischar (sheet) && isrow (sheet)) ... || (isa (sheet, 'string') && isscalar (sheet)) ... || (isnumeric (sheet) && isscalar (sheet)))) error (strcat ("ods2table: 'Sheet' must be a sheet name or a scalar", ... " index.")); endif if (isa (sheet, 'string')) sheet = char (sheet); endif ## Read the workbook into raw grids [data, vtype, meta] = __ods2table__ (file, sheet); if (ischar (data)) error ("ods2table: %s", data); endif ## No metadata sheet -> infer everything from the data cell value types if (isempty (meta)) tbl = ods_autodetect (data, vtype); return; endif ## Parse the descriptive comment on the metadata sheet hdr = sscanf (meta{1,1}, ... "# varTypes %d rows; varNames %d rows; varDescriptions %d rows; varUnits %d rows."); if (numel (hdr) != 4) error ("ods2table: malformed metadata header in '%s'.", file); endif Trows = hdr(1); Nrows = hdr(2); Drows = hdr(3); Urows = hdr(4); ## A table with no variables round-trips to an empty table if (Nrows == 0) tbl = table (); return; endif ## Split the metadata rows into type, name, description, and unit blocks body = meta(2:end,:); T = body(1:Trows,:); body(1:Trows,:) = []; N = body(1:Nrows,:); body(1:Nrows,:) = []; if (Drows) D = body(1:Drows,:); body(1:Drows,:) = []; else D = {}; endif if (Urows) U = body(1:Urows,:); else U = {}; endif ## When the table has no rows the data sheet is empty, so the data grid comes ## back without columns; restore the expected column count from the metadata. metacols = size (T, 2); if (size (data, 2) != metacols) data = cell (size (data, 1), metacols); vtype = cell (size (data, 1), metacols); endif ## A leading RowNames column is tagged in the type row with an empty name RowNames = {}; if (strcmp (T{1,1}, 'RowNames') && isempty (N{1,1})) RowNames = ods_column_strings (data(:,1), vtype(:,1)); data(:,1) = []; vtype(:,1) = []; T(:,1) = []; N(:,1) = []; if (! isempty (D)), D(:,1) = []; endif if (! isempty (U)), U(:,1) = []; endif endif ## Group consecutive columns that share a variable name (multicolumn ## variables), then reconstruct each variable from its declared type. names = N(1,:); ncol = numel (names); varNames = {}; varValues = {}; descr = {}; units = {}; c = 1; while (c <= ncol) c2 = c; while (c2 < ncol && strcmp (names{c2+1}, names{c})) c2 += 1; endwhile idx = c:c2; varNames{end+1} = names{c}; varValues{end+1} = ods_cell2var (data(:,idx), vtype(:,idx), T{1,c}); if (! isempty (D)), descr{end+1} = D{1,c}; endif if (! isempty (U)), units{end+1} = U{1,c}; endif c = c2 + 1; endwhile if (isempty (RowNames)) tbl = table (varValues{:}, 'VariableNames', varNames); else tbl = table (varValues{:}, 'VariableNames', varNames, 'RowNames', RowNames); endif if (! isempty (descr)) tbl.Properties.VariableDescriptions = descr; endif if (! isempty (units)) tbl.Properties.VariableUnits = units; endif endfunction ## Reconstruct one table variable (n-by-k) from its data and value-type columns ## and its declared type T. Missing cells (empty value-type) become NaN, NaT, ## or missing strings according to the type. function v = ods_cell2var (C, VT, T) numvartype = {'double', 'single', 'int8', 'uint8', 'int16', 'uint16', ... 'int32', 'uint32', 'int64', 'uint64'}; if (strcmp (T, 'cell')) v = C; elseif (strcmp (T, 'logical')) v = logical (cell2mat (C)); elseif (ismember (T, numvartype)) M = ods_column_numeric (C); if (any (cellfun (@(x) isinteger (x), C(:)))) v = cellfun (@(x) cast (x, T), num2cell (M)); # element-wise, mixed types else v = cast (M, T); endif elseif (strncmp (T, 'datetime', 8)) ## A zone-aware datetime carries its TimeZone after 'datetime '. tz = ''; if (numel (T) > 9) tz = T(10:end); endif v = ods_iso2datetime (C, tz); elseif (strcmp (T, 'duration')) v = ods_iso2duration (C); elseif (strcmp (T, 'string')) v = string (ods_column_strings (C, VT)); elseif (strcmp (T, 'calendarDuration')) warning ("ods2table: 'calendarDuration' strings are not converted."); v = ods_column_strings (C, VT); elseif (strcmp (T, 'categorical')) warning ("ods2table: 'categorical' strings are not converted."); v = ods_column_strings (C, VT); else v = ods_column_strings (C, VT); endif endfunction ## Build a numeric matrix from a data cell block, mapping missing cells to NaN. function M = ods_column_numeric (C) M = nan (size (C)); for i = 1:numel (C) if (! isempty (C{i})) M(i) = double (C{i}); endif endfor endfunction ## Build a cellstr block from a data/value-type cell block: missing cells and ## numeric cells are coerced to text; genuine empty-string cells stay empty. function S = ods_column_strings (C, VT) S = cell (size (C)); for i = 1:numel (C) x = C{i}; if (ischar (x)) S{i} = x; elseif (isempty (x)) S{i} = ''; else S{i} = num2str (x); endif endfor endfunction ## Parse an ISO 8601 data block into a datetime array; empty cells become NaT. ## A non-empty TZ restores the datetime's TimeZone (the ISO strings are the ## wall-clock time in that zone). function dt = ods_iso2datetime (C, tz = '') sz = size (C); Y = nan (sz); Mo = nan (sz); D = nan (sz); h = nan (sz); mi = nan (sz); s = nan (sz); for i = 1:numel (C) str = C{i}; if (ischar (str) && ! isempty (str)) val = sscanf (str, "%d-%d-%dT%d:%d:%f"); if (numel (val) == 6) Y(i) = val(1); Mo(i) = val(2); D(i) = val(3); h(i) = val(4); mi(i) = val(5); s(i) = val(6); endif endif endfor if (isempty (tz)) dt = datetime (Y, Mo, D, h, mi, s); # NaN components yield NaT else dt = datetime (Y, Mo, D, h, mi, s, 'TimeZone', tz); endif endfunction ## Parse an ISO 8601 duration block (PTnHnMnS) into a duration array; empty ## cells become NaN durations. function du = ods_iso2duration (C) tot = nan (size (C)); for i = 1:numel (C) str = C{i}; if (ischar (str) && ! isempty (str)) neg = (str(1) == '-'); if (neg) str(1) = []; endif tk = regexp (str, '^PT([\d.]+)H([\d.]+)M([\d.]+)S$', 'tokens'); if (! isempty (tk)) H = str2double (tk{1}{1}); M = str2double (tk{1}{2}); S = str2double (tk{1}{3}); val = H * 3600 + M * 60 + S; if (neg) val = -val; endif tot(i) = val; endif endif endfor du = seconds (tot); # NaN yields a missing duration endfunction ## Foreign-file fallback: no metadata sheet, so infer each column's type from ## its cell value types and name the variables Var1, Var2, ... function tbl = ods_autodetect (data, vtype) ncol = size (data, 2); varNames = arrayfun (@(x) sprintf ("Var%d", x), 1:ncol, ... 'UniformOutput', false); varValues = cell (1, ncol); for c = 1:ncol vt = vtype(:,c); seen = vt(! cellfun (@isempty, vt)); if (isempty (seen)) kind = 'string'; else kind = seen{1}; endif switch (kind) case 'float' varValues{c} = ods_column_numeric (data(:,c)); case 'boolean' varValues{c} = logical (ods_column_numeric (data(:,c))); case 'date' varValues{c} = ods_iso2datetime (data(:,c)); case 'time' varValues{c} = ods_iso2duration (data(:,c)); otherwise varValues{c} = ods_column_strings (data(:,c), vtype(:,c)); endswitch endfor tbl = table (varValues{:}, 'VariableNames', varNames); endfunction %!demo %! ## `ods2table` reads an OpenDocument spreadsheet into a table. With no options %! ## it reads the first data sheet. %! %! T = table ([38; 43], [71; 69], 'VariableNames', {'Age', 'Height'}); %! filename = fullfile (tempdir (), 'patients.ods'); %! table2ods (T, filename); %! ods2table (filename) %!demo %! ## Point `'Sheet'` at a specific sheet, by name or by 1-based index, to read %! ## one page of a multi-sheet workbook. %! %! filename = fullfile (tempdir (), 'workbook.ods'); %! table2ods (table ([38; 43], 'VariableNames', {'Age'}), filename, 'Sheet', 'Patients'); %! table2ods (table ([1; 2; 3], 'VariableNames', {'Visit'}), filename, 'Sheet', 'Visits'); %! ods2table (filename, 'Sheet', 'Visits') %! %! delete (filename); ## Round-trip: numeric double and cellstr text %!test %! fn = [tempname() '.fods']; %! T = table ([1; 2; 3], {'a'; 'b'; 'c'}, 'VariableNames', {'x', 'g'}); %! unwind_protect %! table2ods (T, fn); %! R = ods2table (fn); %! assert_equal (R.Properties.VariableNames, {'x', 'g'}); %! assert_equal (R.x, [1; 2; 3]); %! assert_equal (R.g, {'a'; 'b'; 'c'}); %! unwind_protect_cleanup %! delete (fn); %! end_unwind_protect ## Round-trip: full-range int64/uint64 preserved without precision loss %!test %! fn = [tempname() '.fods']; %! T = table (int64 ([9223372036854775807; -5; 0]), ... %! uint64 ([18446744073709551615; 1; 0]), ... %! 'VariableNames', {'i', 'u'}); %! unwind_protect %! table2ods (T, fn); %! R = ods2table (fn); %! assert_equal (class (R.i), 'int64'); %! assert_equal (class (R.u), 'uint64'); %! assert_equal (R.i, int64 ([9223372036854775807; -5; 0])); %! assert_equal (R.u, uint64 ([18446744073709551615; 1; 0])); %! unwind_protect_cleanup %! delete (fn); %! end_unwind_protect ## Round-trip: narrow integer types cast back from the declared metadata type %!test %! fn = [tempname() '.fods']; %! T = table (int8 ([1; -2]), uint16 ([3; 4]), single ([1.5; 2.5]), ... %! 'VariableNames', {'a', 'b', 'c'}); %! unwind_protect %! table2ods (T, fn); %! R = ods2table (fn); %! assert_equal (class (R.a), 'int8'); %! assert_equal (class (R.b), 'uint16'); %! assert_equal (class (R.c), 'single'); %! assert_equal (R.a, int8 ([1; -2])); %! assert_equal (R.c, single ([1.5; 2.5])); %! unwind_protect_cleanup %! delete (fn); %! end_unwind_protect ## Round-trip: logical variable %!test %! fn = [tempname() '.fods']; %! T = table ([true; false; true], 'VariableNames', {'flag'}); %! unwind_protect %! table2ods (T, fn); %! R = ods2table (fn); %! assert_equal (class (R.flag), 'logical'); %! assert_equal (R.flag, [true; false; true]); %! unwind_protect_cleanup %! delete (fn); %! end_unwind_protect ## Round-trip: datetime with a NaT, restored to native datetime %!test %! fn = [tempname() '.fods']; %! d = [datetime(2024, 1, 15, 10, 30, 15.5); NaT; datetime(1999, 12, 31, 23, 59, 59)]; %! T = table (d, 'VariableNames', {'when'}); %! unwind_protect %! table2ods (T, fn); %! R = ods2table (fn); %! assert_equal (class (R.when), 'datetime'); %! assert_equal (isequaln (datevec (R.when), datevec (d)), true); %! unwind_protect_cleanup %! delete (fn); %! end_unwind_protect ## Round-trip: duration with negative, sub-second, over-24h, and zero values %!test %! fn = [tempname() '.fods']; %! du = [duration(25, 30, 15.5); duration(-1, -30, 0); duration(0, 0, 0)]; %! T = table (du, 'VariableNames', {'elapsed'}); %! unwind_protect %! table2ods (T, fn); %! R = ods2table (fn); %! assert_equal (class (R.elapsed), 'duration'); %! assert_equal (isequaln (seconds (R.elapsed), seconds (du)), true); %! unwind_protect_cleanup %! delete (fn); %! end_unwind_protect ## Round-trip: missing duration (NaN) restored as a missing duration %!test %! fn = [tempname() '.fods']; %! du = [duration(1, 0, 0); duration(NaN, 0, 0)]; %! T = table (du, 'VariableNames', {'d'}); %! unwind_protect %! table2ods (T, fn); %! R = ods2table (fn); %! assert_equal (isnan (seconds (R.d)), [false; true]); %! unwind_protect_cleanup %! delete (fn); %! end_unwind_protect ## Round-trip: string variable %!test %! fn = [tempname() '.fods']; %! T = table (string ({'x'; 'y'; 'z'}), 'VariableNames', {'s'}); %! unwind_protect %! table2ods (T, fn); %! R = ods2table (fn); %! assert_equal (class (R.s), 'string'); %! assert_equal (R.s, string ({'x'; 'y'; 'z'})); %! unwind_protect_cleanup %! delete (fn); %! end_unwind_protect ## Round-trip: missing numeric value restored as NaN %!test %! fn = [tempname() '.fods']; %! T = table ([1; NaN; 3], 'VariableNames', {'v'}); %! unwind_protect %! table2ods (T, fn); %! R = ods2table (fn); %! assert_equal (R.v, [1; NaN; 3]); %! unwind_protect_cleanup %! delete (fn); %! end_unwind_protect ## Round-trip: row names %!test %! fn = [tempname() '.fods']; %! T = table ([1; 2; 3], 'VariableNames', {'v'}, 'RowNames', {'r1', 'r2', 'r3'}); %! unwind_protect %! table2ods (T, fn); %! R = ods2table (fn); %! assert_equal (R.Properties.RowNames, {'r1'; 'r2'; 'r3'}); %! assert_equal (R.v, [1; 2; 3]); %! unwind_protect_cleanup %! delete (fn); %! end_unwind_protect ## Round-trip: multicolumn variable kept as one matrix-valued variable %!test %! fn = [tempname() '.fods']; %! T = table ([1 2; 3 4; 5 6], {'p'; 'q'; 'r'}, 'VariableNames', {'mat', 'tag'}); %! unwind_protect %! table2ods (T, fn); %! R = ods2table (fn); %! assert_equal (size (R.mat), [3, 2]); %! assert_equal (R.mat, [1 2; 3 4; 5 6]); %! unwind_protect_cleanup %! delete (fn); %! end_unwind_protect ## Round-trip: descriptions and units restored when every variable carries one %!test %! fn = [tempname() '.fods']; %! T = table ([1; 2], {'a'; 'b'}, 'VariableNames', {'n', 'g'}); %! T.Properties.VariableDescriptions = {'count', 'group'}; %! T.Properties.VariableUnits = {'kg', '-'}; %! unwind_protect %! table2ods (T, fn); %! R = ods2table (fn); %! assert_equal (R.Properties.VariableDescriptions, {'count', 'group'}); %! assert_equal (R.Properties.VariableUnits, {'kg', '-'}); %! unwind_protect_cleanup %! delete (fn); %! end_unwind_protect ## Round-trip: descriptions/units written when only some variables carry one %!test %! fn = [tempname() '.fods']; %! T = table ([1; 2], [3; 4], 'VariableNames', {'a', 'b'}); %! T.Properties.VariableDescriptions = {'first', ''}; %! T.Properties.VariableUnits = {'', 'kg'}; %! unwind_protect %! table2ods (T, fn); %! R = ods2table (fn); %! assert_equal (R.Properties.VariableDescriptions, {'first', ''}); %! assert_equal (R.Properties.VariableUnits, {'', 'kg'}); %! unwind_protect_cleanup %! delete (fn); %! end_unwind_protect ## Round-trip: a zone-aware datetime keeps its TimeZone %!test %! fn = [tempname() '.ods']; %! dt = datetime (2024, 6, [15; 16], 10, 30, 0, 'TimeZone', 'America/New_York'); %! T = table (dt, 'VariableNames', {'t'}); %! unwind_protect %! table2ods (T, fn); %! R = ods2table (fn); %! assert_equal (R.t.TimeZone, 'America/New_York'); %! assert_equal (cellstr (char (R.t)), cellstr (char (dt))); %! unwind_protect_cleanup %! delete (fn); %! end_unwind_protect ## Foreign spreadsheet with no metadata sheet: infer types, name Var1, Var2, ... %!test %! fn = [tempname() '.fods']; %! T = table ([1; 2; 3], {'a'; 'b'; 'c'}, 'VariableNames', {'x', 'g'}); %! unwind_protect %! table2ods (T, fn); %! txt = fileread (fn); %! txt = regexprep (txt, ... %! '', ''); %! fid = fopen (fn, 'w'); fputs (fid, txt); fclose (fid); %! R = ods2table (fn); %! assert_equal (R.Properties.VariableNames, {'Var1', 'Var2'}); %! assert_equal (R.Var1, [1; 2; 3]); %! assert_equal (class (R.Var2), 'cell'); %! unwind_protect_cleanup %! delete (fn); %! end_unwind_protect ## Select a specific sheet by name and by 1-based index from a multi-sheet file %!test %! fn = [tempname() '.fods']; %! doc = ['' ... %! '' ... %! '' ... %! '' ... %! '11' ... %! '' ... %! '' ... %! '22' ... %! '']; %! fid = fopen (fn, 'w'); fputs (fid, doc); fclose (fid); %! unwind_protect %! Rdef = ods2table (fn); # default reads the first sheet %! assert_equal (Rdef.Var1, 11); %! Rname = ods2table (fn, 'Sheet', 'Two'); %! assert_equal (Rname.Var1, 22); %! Ridx = ods2table (fn, 'Sheet', 2); %! assert_equal (Ridx.Var1, 22); %! unwind_protect_cleanup %! delete (fn); %! end_unwind_protect ## Error: a requested sheet that does not exist %!error ... %! fn = [tempname() '.fods']; ... %! doc = ['' ... %! '' ... %! '']; ... %! fid = fopen (fn, 'w'); fputs (fid, doc); fclose (fid); ... %! ods2table (fn, 'Sheet', 'X'); ## Error: 'Sheet' of an invalid type %!error ... %! ods2table ([tempname() '.fods'], 'Sheet', {1, 2}); ## Error: FILENAME of the wrong type %!error ... %! ods2table (42); ## Error: a file that cannot be read as an OpenDocument spreadsheet %!error ... %! ods2table ([tempname() '.fods']); ## Round-trip: a table with no rows preserves its variable types %!test %! fn = [tempname() '.fods']; %! T = table (zeros (0, 1), int64 (zeros (0, 1)), datetime (zeros (0, 1), 1, 1), ... %! seconds (zeros (0, 1)), string (cell (0, 1)), ... %! 'VariableNames', {'d', 'i', 'dt', 'du', 's'}); %! unwind_protect %! table2ods (T, fn); %! R = ods2table (fn); %! assert_equal (size (R), [0, 5]); %! assert_equal (class (R.i), 'int64'); %! assert_equal (class (R.dt), 'datetime'); %! assert_equal (class (R.du), 'duration'); %! assert_equal (class (R.s), 'string'); %! unwind_protect_cleanup %! delete (fn); %! end_unwind_protect ## Round-trip: a table with no variables comes back as an empty table %!test %! fn = [tempname() '.fods']; %! unwind_protect %! table2ods (table (), fn); %! R = ods2table (fn); %! assert_equal (istable (R), true); %! assert_equal (size (R), [0, 0]); %! unwind_protect_cleanup %! delete (fn); %! end_unwind_protect ## Round-trip: XML metacharacters in a string are escaped and restored %!test %! fn = [tempname() '.fods']; %! T = table ({'a&"c"'; 'x&y'}, 'VariableNames', {'s'}); %! unwind_protect %! table2ods (T, fn); %! R = ods2table (fn); %! assert_equal (R.s, {'a&"c"'; 'x&y'}); %! unwind_protect_cleanup %! delete (fn); %! end_unwind_protect ## Round-trip: leading and trailing whitespace in a string is preserved %!test %! fn = [tempname() '.fods']; %! T = table ({' pad '; ''}, 'VariableNames', {'s'}); %! unwind_protect %! table2ods (T, fn); %! R = ods2table (fn); %! assert_equal (R.s, {' pad '; ''}); %! unwind_protect_cleanup %! delete (fn); %! end_unwind_protect ## Round-trip: infinite numeric values are preserved %!test %! fn = [tempname() '.fods']; %! T = table ([Inf; -Inf; 1.5], 'VariableNames', {'v'}); %! unwind_protect %! table2ods (T, fn); %! R = ods2table (fn); %! assert_equal (R.v, [Inf; -Inf; 1.5]); %! unwind_protect_cleanup %! delete (fn); %! end_unwind_protect ## Round-trip through the compressed '.ods' (ZIP) container %!test %! fn = [tempname() '.ods']; %! d = [datetime(2024, 1, 15, 10, 30, 15.5); NaT]; %! T = table ([1; 2], int64 ([9223372036854775807; -5]), d, ... %! seconds ([90; 3661]), {'a&"c"'; 'y'}, ... %! 'VariableNames', {'x', 'big', 'when', 'dur', 's'}); %! T.Properties.RowNames = {'r1', 'r2'}; %! unwind_protect %! table2ods (T, fn); %! R = ods2table (fn); %! assert_equal (R.Properties.RowNames, {'r1'; 'r2'}); %! assert_equal (R.x, [1; 2]); %! assert_equal (R.big, int64 ([9223372036854775807; -5])); %! assert_equal (class (R.when), 'datetime'); %! assert_equal (isequaln (datevec (R.when), datevec (d)), true); %! assert_equal (isequaln (seconds (R.dur), [90; 3661]), true); %! assert_equal (R.s, {'a&"c"'; 'y'}); %! unwind_protect_cleanup %! delete (fn); %! end_unwind_protect pr0m1th3as-datatypes-9c9a8d3/inst/parsePairedArguments.m000066400000000000000000000141571522766574100233450ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the datatypes package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {datatypes} {[@var{optarg_1}, @dots{}, @var{optarg_N}] =} @ ## parsePairedArguments (@var{optarg_names}, @var{default_values}, @var{arg_list}) ## @deftypefnx {datatypes} {[@var{optarg_1}, @dots{}, @var{optarg_N}, @var{rem_args}] =} @ ## parsePairedArguments (@var{optarg_names}, @var{default_values}, @var{arg_list}) ## ## Parse optional paired arguments from variable argument list. ## ## @code{parsePairedArguments} parses the optional paired arguments specified ## by @var{optarg_names} from the variable input argument list, @var{arg_list}. ## Any @var{optarg_names} that are not found in @var{arg_list} are returned with ## their default value specified by @var{default_values}, which must be a cell ## array with the same number of elements as @var{optarg_names}. ## ## @var{optarg_names} must be a cell array of character vectors or a string ## array with the same number of elements as the number of output arguments ## specified as @code{[@var{optarg_1}, @dots{}, @var{optarg_N}]}, while an extra ## output argument, @var{rem_args}, may be specified for the remaining input ## arguments in @var{arg_list} that were not specified by @var{optarg_names}. ## ## Each property name specified by @var{optarg_names} is case insensitive. ## ## The following example illustrates how to use @code{parsePairedArguments} ## inside a function to parse optional paired arguments for three properties, ## namely @qcode{'A'}, @qcode{'B'}, and @qcode{'C'}. ## ## @example ## ## Declare optional property Names and their default Values ## optNames = @{'A', 'B', 'C'@}; ## dfValues = @{1, 2, 3@}; ## ## ## Parse optional Name-Value paired arguments ## [var_A, var_B, var_C, args] = ... ## parsePairedArguments (optNames, dfValues, args); ## @end example ## ## @end deftypefn function [varargout] = parsePairedArguments (optNames, dfValues, args) ## Input validation if (nargin != 3) error ("parsePairedArguments: invalid number of input arguments."); endif if (isa (optNames, 'string')) optNames = cellstr (optNames); endif if (! iscellstr (optNames)) error (strcat ("parsePairedArguments: OPTARG_NAMES must be a cell", ... " array of character vectors or a string array.")); endif if (! iscell (dfValues)) error ("parsePairedArguments: DFVALUES must be a cell array."); endif optN = numel (optNames); if (optN != numel (dfValues)) error ("parsePairedArguments: OPTARG_NAMES mismatches DFVALUES."); endif if (nargout < optN || nargout > optN + 1) error ("parsePairedArguments: inconsistent number of output arguments."); endif if (! iscell (args)) error ("parsePairedArguments: ARG_LIST must be a cell array."); endif ## Search through all input arguments for Name/Value pairs foundNames = []; nargs = numel (args); for ii = nargs-1:-1:1 tmp_arg = args{ii}; if (isstring (tmp_arg)) tmp_arg = char (tmp_arg); endif if (ischar (tmp_arg)) idx = strcmpi (tmp_arg, optNames); if (any (idx)) idx = find (idx); varargout{idx} = args{ii+1}; foundNames = [foundNames, idx]; args(ii:ii+1) = []; endif endif endfor ## Find optNames that were not in args and add defaults allNames = 1:numel (optNames); notfound = ! ismember (allNames, foundNames); defNames = optNames(notfound); for ii = 1:numel (defNames) idx = find (strcmpi (defNames{ii}, optNames)); varargout{idx} = dfValues{idx}; endfor ## Append remaining input arguments in varargout if (nargout > optN) idx = optN + 1; varargout{idx} = args(:); endif endfunction %!shared optNames, dfValues %! optNames = {'A', 'B', 'C'}; %! dfValues = {{3}, [1, 2], 'text'}; %!test %! [a, b, c] = parsePairedArguments (optNames, dfValues, {'A', 5}); %! assert_equal (a, 5); %! assert_equal (b, [1, 2]); %! assert_equal (c, 'text'); %!test %! [a, b, c, args] = parsePairedArguments (optNames, dfValues, {4, 'D', 5}); %! assert_equal (numel (args), 3); %! assert_equal (args{2}, 'D'); %! assert_equal (a, {3}); %! assert_equal (b, [1, 2]); %! assert_equal (c, 'text'); %!test %! [a, b, c] = parsePairedArguments (optNames, dfValues, ... %! {'A', 5, 'b', 1, 'C', 'test'}); %! assert_equal (a, 5); %! assert_equal (b, 1); %! assert_equal (c, 'test'); %!test %! [a, b, c, args] = parsePairedArguments (optNames, dfValues, {1}); %! assert_equal (args, {1}); %! assert_equal (a, {3}); %! assert_equal (b, [1, 2]); %! assert_equal (c, 'text'); %!error ... %! parsePairedArguments (optNames, dfValues) %!error ... %! parsePairedArguments ([1, 2, 3], dfValues, {1}) %!error ... %! parsePairedArguments (optNames, [1, 2, 3], {1}) %!error ... %! parsePairedArguments (optNames(1:2), dfValues, {1}) %!error ... %! [a, b] = parsePairedArguments (optNames, dfValues, {1}) %!error ... %! [a, b, c, d, e] = parsePairedArguments (optNames, dfValues, {1}) %!error ... %! [a, b, c, e] = parsePairedArguments (optNames, dfValues, 1) pr0m1th3as-datatypes-9c9a8d3/inst/patients.mat000066400000000000000000000660711522766574100213760ustar00rootroot00000000000000Octave-1-LAgematrixdC@E@C@D@H@G@@@D@<@?@F@E@9@C@B@H@@@;@B@I@H@C@D@F@<@9@C@9@B@>@F@D@9@G@F@H@F@A@@@C@C@F@F@B@F@B@>@C@E@E@H@F@E@G@I@C@D@F@B@C@=@<@>@<@=@B@F@@@?@H@9@D@C@D@@@?@A@@@E@H@A@C@<@=@@@C@B@H@?@B@C@F@>@H@H@9@F@H@F@H@ Diastolicmatrixd@W@@S@T@R@T@Q@V@T@S@U@@S@Q@R@W@S@W@W@S@@S@S@R@S@V@V@X@@S@T@S@T@@V@W@T@T@U@W@T@V@@U@V@R@W@T@@V@X@@V@@S@@T@S@T@S@W@V@V@U@@V@S@R@T@S@@T@@S@@R@@U@S@T@T@S@T@S@T@R@V@R@S@@U@U@R@S@@T@S@@U@S@T@T@T@W@W@X@U@@T@V@@S@V@S@@R@X@W@R@@W@U@Gendercelld sq_stringMale sq_stringMale sq_stringFemale sq_stringFemale sq_stringFemale sq_stringFemale sq_stringFemale sq_stringMale sq_stringMale sq_stringFemale sq_stringFemale sq_stringFemale sq_stringMale sq_stringMale sq_stringFemale sq_stringMale sq_stringMale sq_stringFemale sq_stringMale sq_stringMale sq_stringFemale sq_stringFemale sq_stringFemale sq_stringFemale sq_stringFemale sq_stringMale sq_stringFemale sq_stringFemale sq_stringMale sq_stringMale sq_stringFemale sq_stringFemale sq_stringFemale sq_stringMale sq_stringMale sq_stringFemale sq_stringMale sq_stringFemale sq_stringMale sq_stringFemale sq_stringMale sq_stringMale sq_stringMale sq_stringMale sq_stringMale sq_stringFemale sq_stringMale sq_stringFemale sq_stringMale sq_stringMale sq_stringMale sq_stringFemale sq_stringFemale sq_stringFemale sq_stringMale sq_stringFemale sq_stringFemale sq_stringMale sq_stringMale sq_stringFemale sq_stringFemale sq_stringFemale sq_stringFemale sq_stringFemale sq_stringFemale sq_stringMale sq_stringFemale sq_stringFemale sq_stringFemale sq_stringFemale sq_stringMale sq_stringFemale sq_stringMale sq_stringFemale sq_stringFemale sq_stringMale sq_stringFemale sq_stringMale sq_stringMale sq_stringFemale sq_stringMale sq_stringMale sq_stringMale sq_stringFemale sq_stringFemale sq_stringMale sq_stringFemale sq_stringFemale sq_stringFemale sq_stringFemale sq_stringMale sq_stringMale sq_stringFemale sq_stringMale sq_stringFemale sq_stringMale sq_stringMale sq_stringMale sq_stringMale sq_stringMaleHeightmatrixdQ@@Q@P@P@P@Q@P@Q@Q@P@Q@P@Q@R@@P@Q@@Q@@Q@Q@Q@@P@P@O@P@@P@Q@O@O@Q@P@Q@P@P@Q@Q@P@Q@P@P@O@Q@@Q@Q@Q@P@@P@Q@O@Q@P@Q@O@P@P@R@O@P@Q@Q@Q@O@@P@P@P@Q@Q@Q@N@P@P@P@P@R@@P@P@R@P@Q@P@P@Q@@Q@@Q@P@O@Q@@P@O@P@@P@Q@Q@Q@Q@P@@Q@@Q@Q@Q@P@LastNamecelld sq_stringSmith sq_stringJohnson sq_stringWilliams sq_stringJones sq_stringBrown sq_stringDavis sq_stringMiller sq_stringWilson sq_stringMoore sq_stringTaylor sq_stringAnderson sq_stringThomas sq_stringJackson sq_stringWhite sq_stringHarris sq_stringMartin sq_stringThompson sq_stringGarcia sq_stringMartinez sq_stringRobinson sq_stringClark sq_string Rodriguez sq_stringLewis sq_stringLee sq_stringWalker sq_stringHall sq_stringAllen sq_stringYoung sq_string Hernandez sq_stringKing sq_stringWright sq_stringLopez sq_stringHill sq_stringScott sq_stringGreen sq_stringAdams sq_stringBaker sq_stringGonzalez sq_stringNelson sq_stringCarter sq_stringMitchell sq_stringPerez sq_stringRoberts sq_stringTurner sq_stringPhillips sq_stringCampbell sq_stringParker sq_stringEvans sq_stringEdwards sq_stringCollins sq_stringStewart sq_stringSanchez sq_stringMorris sq_stringRogers sq_stringReed sq_stringCook sq_stringMorgan sq_stringBell sq_stringMurphy sq_stringBailey sq_stringRivera sq_stringCooper sq_string Richardson sq_stringCox sq_stringHoward sq_stringWard sq_stringTorres sq_stringPeterson sq_stringGray sq_stringRamirez sq_stringJames sq_stringWatson sq_stringBrooks sq_stringKelly sq_stringSanders sq_stringPrice sq_stringBennett sq_stringWood sq_stringBarnes sq_stringRoss sq_string Henderson sq_stringColeman sq_stringJenkins sq_stringPerry sq_stringPowell sq_stringLong sq_string Patterson sq_stringHughes sq_stringFlores sq_string Washington sq_stringButler sq_stringSimmons sq_stringFoster sq_stringGonzales sq_stringBryant sq_string Alexander sq_stringRussell sq_stringGriffin sq_stringDiaz sq_stringHayesLocationcelld sq_stringCounty General Hospital sq_string VA Hospital sq_stringSt. Mary's Medical Center sq_string VA Hospital sq_stringCounty General Hospital sq_stringSt. Mary's Medical Center sq_string VA Hospital sq_string VA Hospital sq_stringSt. Mary's Medical Center sq_stringCounty General Hospital sq_stringCounty General Hospital sq_stringSt. Mary's Medical Center sq_string VA Hospital sq_string VA Hospital sq_stringSt. Mary's Medical Center sq_string VA Hospital sq_stringSt. Mary's Medical Center sq_string VA Hospital sq_stringCounty General Hospital sq_stringCounty General Hospital sq_string VA Hospital sq_string VA Hospital sq_string VA Hospital sq_stringCounty General Hospital sq_stringCounty General Hospital sq_string VA Hospital sq_string VA Hospital sq_stringCounty General Hospital sq_stringCounty General Hospital sq_stringCounty General Hospital sq_string VA Hospital sq_string VA Hospital sq_stringSt. Mary's Medical Center sq_stringSt. Mary's Medical Center sq_stringCounty General Hospital sq_string VA Hospital sq_string VA Hospital sq_stringSt. Mary's Medical Center sq_stringSt. Mary's Medical Center sq_stringSt. Mary's Medical Center sq_stringCounty General Hospital sq_string VA Hospital sq_string VA Hospital sq_string VA Hospital sq_string VA Hospital sq_stringCounty General Hospital sq_string VA Hospital sq_stringCounty General Hospital sq_stringCounty General Hospital sq_stringCounty General Hospital sq_stringCounty General Hospital sq_stringSt. Mary's Medical Center sq_stringCounty General Hospital sq_string VA Hospital sq_string VA Hospital sq_string VA Hospital sq_stringSt. Mary's Medical Center sq_stringSt. Mary's Medical Center sq_string VA Hospital sq_stringSt. Mary's Medical Center sq_stringCounty General Hospital sq_string VA Hospital sq_stringCounty General Hospital sq_stringCounty General Hospital sq_string VA Hospital sq_stringSt. Mary's Medical Center sq_stringCounty General Hospital sq_stringCounty General Hospital sq_string VA Hospital sq_stringCounty General Hospital sq_stringCounty General Hospital sq_string VA Hospital sq_stringSt. Mary's Medical Center sq_stringSt. Mary's Medical Center sq_stringSt. Mary's Medical Center sq_string VA Hospital sq_stringCounty General Hospital sq_stringSt. Mary's Medical Center sq_stringCounty General Hospital sq_string VA Hospital sq_stringSt. Mary's Medical Center sq_string VA Hospital sq_stringCounty General Hospital sq_stringSt. Mary's Medical Center sq_string VA Hospital sq_stringCounty General Hospital sq_stringCounty General Hospital sq_stringCounty General Hospital sq_string VA Hospital sq_stringSt. Mary's Medical Center sq_stringCounty General Hospital sq_string VA Hospital sq_stringSt. Mary's Medical Center sq_stringCounty General Hospital sq_stringCounty General Hospital sq_stringCounty General Hospital sq_string VA Hospital sq_stringCounty General Hospital sq_stringCounty General Hospital sq_stringCounty General HospitalSelfAssessedHealthStatuscelld sq_string Excellent sq_stringFair sq_stringGood sq_stringFair sq_stringGood sq_stringGood sq_stringGood sq_stringGood sq_string Excellent sq_string Excellent sq_string Excellent sq_stringPoor sq_stringPoor sq_string Excellent sq_stringGood sq_stringGood sq_string Excellent sq_stringFair sq_stringGood sq_stringGood sq_string Excellent sq_stringFair sq_stringFair sq_stringFair sq_stringGood sq_stringPoor sq_string Excellent sq_stringGood sq_stringPoor sq_string Excellent sq_string Excellent sq_stringPoor sq_string Excellent sq_string Excellent sq_stringGood sq_string Excellent sq_stringGood sq_stringFair sq_stringGood sq_stringGood sq_stringFair sq_string Excellent sq_stringGood sq_string Excellent sq_stringGood sq_stringFair sq_stringPoor sq_stringGood sq_string Excellent sq_stringGood sq_stringPoor sq_stringGood sq_stringPoor sq_string Excellent sq_string Excellent sq_string Excellent sq_stringGood sq_stringGood sq_stringGood sq_stringGood sq_string Excellent sq_stringGood sq_string Excellent sq_stringGood sq_string Excellent sq_stringGood sq_string Excellent sq_string Excellent sq_string Excellent sq_string Excellent sq_stringGood sq_stringFair sq_string Excellent sq_stringPoor sq_string Excellent sq_stringFair sq_stringFair sq_stringPoor sq_string Excellent sq_stringGood sq_stringGood sq_string Excellent sq_stringGood sq_stringGood sq_string Excellent sq_stringGood sq_stringPoor sq_stringGood sq_stringGood sq_stringGood sq_string Excellent sq_string Excellent sq_stringFair sq_stringGood sq_string Excellent sq_stringGood sq_stringGood sq_stringFair sq_stringGood sq_stringFairSmoker bool matrixdSystolicmatrixd_@@[@@_@@]@^@@^@@`@\@\@]@\@\@_@@`@\@@`@_@^@]@@_@@^@^@\@`@ `@\@@\@@_@^@_@`@@^@\@_@@^@_@a@@]@_@^@`@]@`@ a@@]@]@]@^@]@_@ `@@`@`@@]@ `@]@^@@a@@]@@\@^@\@^@@]@^@^@]@[@@^@@a@@_@^@^@@]@@_@_@@^@]@^@]@]@^@`@``@@\@@_@`@`@^@^@@a@_@@`@^@ `@`@_@]@a@\@Weightmatrixdf@`d@``@`@]@a@a@f@f@`@`@ a@e@@i@ `@f@g@``@`f@e@`@@]@ a@@b@^@g@a@\@d@@g@_@ a@@a@`g@ h@ a@h@]@f@`@d@f@ e@@h@e@`@f@@^@c@`f@@e@a@`@`b@@g@_@`@@e@f@@`@@`@_@a@[@`@g@ a@a@@`@ a@@g@_@f@_@\@@f@``@f@@h@_@@g@g@g@^@`@f@^@^@a@ `@g@f@_@e@`@`e@g@@g@e@ f@pr0m1th3as-datatypes-9c9a8d3/inst/private/000077500000000000000000000000001522766574100205045ustar00rootroot00000000000000pr0m1th3as-datatypes-9c9a8d3/inst/private/__a1ref__.m000066400000000000000000000057501522766574100224630ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the datatypes package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {datatypes} {[@var{r1}, @var{c1}, @var{r2}, @var{c2}] =} __a1ref__ (@var{spec}) ## ## Parse a spreadsheet A1-style range into 1-based row and column bounds. ## ## @var{spec} is a character vector or string scalar such as @qcode{'C5'} (a ## single-cell anchor) or @qcode{'C5:D8'} (a rectangle). The return values are ## the top-left corner (@var{r1}, @var{c1}) and bottom-right corner (@var{r2}, ## @var{c2}). A single-cell spec yields an open-ended rectangle with ## @var{r2} = @var{c2} = @code{Inf}, meaning "from this cell to the end of the ## used range". Reversed corners (e.g.@: @qcode{'D8:C5'}) are normalised. ## ## This is an internal helper shared by @code{readtable} and @code{writetable}. ## Do NOT call it directly. ## ## @end deftypefn function [r1, c1, r2, c2] = __a1ref__ (spec) if (isa (spec, 'string')) spec = char (spec); endif if (! (ischar (spec) && isrow (spec))) error ("__a1ref__: RANGE must be a character vector or string scalar."); endif parts = ostrsplit (spec, ':'); if (numel (parts) == 1) [r1, c1] = parse_cell (spec); r2 = Inf; c2 = Inf; elseif (numel (parts) == 2) [r1, c1] = parse_cell (parts{1}); [r2, c2] = parse_cell (parts{2}); ## Normalise so (r1,c1) is the top-left and (r2,c2) the bottom-right corner if (r2 < r1) [r1, r2] = deal (r2, r1); endif if (c2 < c1) [c1, c2] = deal (c2, c1); endif else error ("__a1ref__: invalid range '%s'.", spec); endif endfunction ## Parse a single A1 cell reference ('C5') into 1-based (row, column). function [r, c] = parse_cell (s) tok = regexp (s, '^([A-Za-z]+)([0-9]+)$', 'tokens'); if (isempty (tok)) error ("__a1ref__: invalid cell reference '%s'.", s); endif letters = toupper (tok{1}{1}); c = 0; for i = 1:numel (letters) c = c * 26 + (double (letters(i)) - double ('A') + 1); endfor r = str2double (tok{1}{2}); if (r < 1) error ("__a1ref__: invalid row number in cell reference '%s'.", s); endif endfunction ## __a1ref__ is a private helper; it carries no inline BIST blocks (private ## functions are not reachable by 'pkg test'). Its behaviour is covered ## indirectly through the 'Range' tests of readtable and table.writetable. pr0m1th3as-datatypes-9c9a8d3/inst/private/__disp__.m000066400000000000000000000111111522766574100224100ustar00rootroot00000000000000## Copyright (C) 2024-2026 Andreas Bertsatos ## ## This file is part of the datatypes package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {private} {} __disp__ (@var{obj}, @var{class}) ## @deftypefnx {private} {} __disp__ (@var{obj}, @var{class}, @var{varname}) ## ## Display arrays of datatype objects. ## ## @end deftypefn function __disp__ (this, datatype, name = 'ans') if (isempty (this)) str = strjoin (repmat ({'%d'}, 1, ndims (this)), 'x'); str = sprintf ('\n %s empty %s array\n\n', str, datatype); fprintf (str, size (this)); elseif (isscalar (this)) fprintf (' %s\n\n', datatype); fprintf (' %s\n', dispstrings (this){:}); elseif (ismatrix (this)) fprintf (' %dx%d %s array\n\n', size (this), datatype); dispcstrmatrix (dispstrings (this)); else str = strjoin (repmat ({'%d'}, 1, ndims (this)), 'x'); str = sprintf ('\n %s %s array\n\n', str, datatype); fprintf (str, size (this)); ## Handle each page separately sz = size (this); high_sz = sz(3:end); high_ixs = {}; for i = 1:numel (high_sz) high_ixs{i} = [1:high_sz(i)]'; endfor page_ixs = combvec (high_ixs); idx.type = '()'; for ix = 1:size (page_ixs, 1) p_ix = page_ixs(ix,:); idx.subs = {":", ":", num2cell(p_ix){:}}; pagestr = sprintf (strjoin (repmat ({'%d'}, 1, numel (p_ix)), ':'), p_ix); fprintf ('%s(:,:,%s) = \n\n', name, pagestr); page_this = subsref (this, idx); dispcstrmatrix (dispstrings (page_this)); endfor endif endfunction function dispcstrmatrix (cstr) sz = terminal_size (); cols = sz(2) - 4; colgap = " "; optLens = []; for iCol = 1:size (cstr, 2) optLen = max (cellfun ('length', cstr(:, iCol))); optLens = [optLens, optLen]; endfor if (sum (optLens + 4) <= cols) # all columns fit in terminal size rowSpat = ""; for iCol = 1:size (cstr, 2) rowSpat = [rowSpat, sprintf("%%-%ds", optLens(iCol)), colgap]; endfor for iRow = 1:size (cstr, 1) strrow = sprintf (rowSpat, cstr{iRow,:}); fprintf (" %s\n", strrow); endfor fprintf ("\n"); else # we need to split rows optLen_cs = cumsum (optLens + 6); startCol = 1; while (! isempty (find (optLen_cs > cols))) stopCol = find (optLen_cs > cols, 1) - 1; rowSpat = ""; for iCol = 1:stopCol rowSpat = [rowSpat, sprintf("%%-%ds", optLens(iCol)), colgap]; endfor optLens(1:iCol) = []; optLen_cs = cumsum (optLens + 6); stopCol = stopCol + startCol - 1; fprintf ("Columns %d through %d:\n\n", startCol, stopCol); for iRow = 1:size (cstr, 1) strrow = sprintf (rowSpat, cstr{iRow,[startCol:stopCol]}); fprintf (" %s\n", strrow); endfor fprintf ("\n"); startCol = stopCol + 1; endwhile if (! isempty (optLens)) for iCol = 1:length (optLens) rowSpat = [rowSpat, sprintf("%%-%ds", optLens(iCol)), colgap]; endfor stopCol = startCol + iCol - 1; if (startCol == stopCol) fprintf ("Column %d:\n\n", startCol); else fprintf ("Columns %d through %d:\n\n", startCol, stopCol); endif for iRow = 1:size (cstr, 1) strrow = sprintf (rowSpat, cstr{iRow,[startCol:stopCol]}); fprintf (" %s\n", strrow); endfor fprintf ("\n"); endif endif endfunction function out = combvec (vecs) switch (numel (vecs)) case 1 out = vecs{1}(:); case 2 a = vecs{1}(:); b = vecs{2}(:); out = repmat (a, numel (b), 2); i_comb = 1; for i_a = 1:numel (a) for i_b = 1:numel (b) out(i_comb,:) = [a(i_a), b(i_b)]; i_comb = i_comb + 1; endfor endfor otherwise out = []; a = vecs{1}(:); rest = vecs(2:end); rest_combs = combvec (rest); for i = 1:numel (a) out = [out; [repmat(a(i), [size(rest_combs,1), 1]), rest_combs]]; endfor endswitch endfunction pr0m1th3as-datatypes-9c9a8d3/inst/private/__groupbins__.m000066400000000000000000000420111522766574100234640ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the datatypes package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {datatypes} {@var{tf} =} __groupbins__ (@qcode{'is_spec'}, @var{x}) ## @deftypefnx {datatypes} {[@var{cols}, @var{names}, @var{errmsg}] =} __groupbins__ (@qcode{'bin'}, @var{cols}, @var{names}, @var{scheme}, @var{incedge}, @var{caller}) ## ## Shared @qcode{'groupbins'} engine for the grouping methods and functions. ## ## @code{__groupbins__ (@qcode{'is_spec'}, @var{x})} returns true when @var{x} is ## a @qcode{'groupbins'} binning specification (used to tell a positional binning ## argument apart from a method argument). ## ## @code{__groupbins__ (@qcode{'bin'}, @var{cols}, @var{names}, @var{scheme}, ## @var{incedge}, @var{caller})} bins each grouping-variable column in @var{cols} ## per @var{scheme} (a number of bins, an edge vector, a @code{duration} / ## @code{calendarDuration} bin width, or a datetime time-unit keyword), returning ## the binned columns (categoricals) and the MATLAB-style output variable names ## (@qcode{@var{unit}_@var{var}} for a time unit, @qcode{disc_@var{var}} ## otherwise; unbinned variables keep their name). @var{errmsg} is a message ## body the caller emits under its own name (empty on success). ## ## This is an internal helper; do NOT call it directly. ## ## @end deftypefn function varargout = __groupbins__ (op, varargin) switch (op) case 'is_spec' varargout{1} = gb_is_spec (varargin{1}); case 'bin' [varargout{1}, varargout{2}, varargout{3}] = gb_bin (varargin{:}); otherwise error ("__groupbins__: unknown operation '%s'.", op); endswitch endfunction ## Contiguous calendar-period time-unit keywords (datetime only). The component ## keywords ('monthofyear', 'dayofweek', ...) are recognised as specs but not ## yet implemented (they raise a clean "not yet supported" error when binning). function kw = gb_period_units () kw = {'second', 'minute', 'hour', 'day', 'week', 'month', 'quarter', ... 'year', 'decade', 'century'}; endfunction function kw = gb_keywords () kw = [{'none'}, gb_period_units(), {'dayname', 'monthname', 'dayofweek', ... 'dayofmonth', 'dayofyear', 'hourofday', 'weekofmonth', 'weekofyear', ... 'monthofyear', 'quarterofyear', 'secondofminute', 'minuteofhour'}]; endfunction function tf = gb_is_charrow (x) tf = (ischar (x) && isrow (x)) || (isa (x, 'string') && isscalar (x)); endfunction function tf = gb_is_none (x) tf = gb_is_charrow (x) && strcmpi (char (x), 'none'); endfunction ## True if X is a single binning-scheme element. function tf = gb_is_scheme_elem (x) tf = false; if ((isnumeric (x) || islogical (x)) && ! isempty (x)) tf = true; elseif (isa (x, 'datetime') || isa (x, 'duration') ... || isa (x, 'calendarDuration')) tf = true; elseif (gb_is_charrow (x)) tf = any (strcmpi (char (x), gb_keywords ())); endif endfunction ## True if X is a binning specification: a single element or a cell of elements. function tf = gb_is_spec (x) tf = gb_is_scheme_elem (x); if (! tf && iscell (x) && ! isempty (x)) tf = all (cellfun (@gb_is_scheme_elem, x(:)')); endif endfunction ## Normalise SCHEME into a 1-by-K per-variable cell. function [schemes, errmsg] = gb_normalise_schemes (scheme, K, caller) errmsg = ''; schemes = {}; if (iscell (scheme)) if (numel (scheme) != K) errmsg = sprintf (strcat ("GROUPBINS as a cell array must hold one", ... " binning scheme per grouping variable (%d)."), ... K); return; endif schemes = scheme(:)'; else schemes = repmat ({scheme}, 1, K); endif endfunction ## Bin each grouping column; return updated columns and their output names. function [cols, names, errmsg] = gb_bin (cols, names, scheme, incEdge, caller) errmsg = ''; K = numel (cols); [schemes, errmsg] = gb_normalise_schemes (scheme, K, caller); if (! isempty (errmsg)) return; endif for j = 1:K if (gb_is_none (schemes{j})) continue; # no binning for this variable endif [b, nm, errmsg] = gb_bin_col (cols{j}, schemes{j}, incEdge, names{j}); if (! isempty (errmsg)) return; endif cols{j} = b; names{j} = nm; endfor endfunction ## Bin one grouping column COL per SCHEME; return the categorical, its output ## name, and an errmsg body (empty on success). function [binned, newname, errmsg] = gb_bin_col (col, scheme, incEdge, varname) binned = []; newname = varname; errmsg = ''; n = numel (col); ## Time-unit keyword scheme. if (gb_is_charrow (scheme)) unit = lower (char (scheme)); if (any (strcmp (unit, gb_period_units ()))) if (! isa (col, 'datetime')) errmsg = sprintf (strcat ("binning grouping variable '%s' by time unit", ... " '%s' is only supported for datetime variables."), ... varname, unit); return; endif [idx, labs] = gb_period_bins (col, unit, incEdge); binned = gb_make_categorical (idx, labs, n); newname = [unit, '_', varname]; return; endif errmsg = sprintf (strcat ("binning grouping variable '%s' by '%s' is not", ... " yet supported."), varname, unit); return; endif ## Duration / calendarDuration bin-width scalar. if ((isa (scheme, 'duration') || isa (scheme, 'calendarDuration')) ... && isscalar (scheme)) [idx, labs, errmsg] = gb_width_bins (col, scheme, incEdge, varname); if (! isempty (errmsg)) return; endif binned = gb_make_categorical (idx, labs, n); newname = ['disc_', varname]; return; endif ## Number of bins / edge vector. [idx, labs, errmsg] = gb_edge_bins (col, scheme, incEdge, varname); if (! isempty (errmsg)) return; endif binned = gb_make_categorical (idx, labs, n); newname = ['disc_', varname]; endfunction ## Build a categorical from per-row bin indices IDX (NaN -> ) and the ## ordered category labels LABS. function c = gb_make_categorical (idx, labs, n) rowLab = repmat ({''}, n, 1); ok = ! isnan (idx); rowLab(ok) = labs(idx(ok)); c = categorical (rowLab, labs); endfunction ## ---- Number-of-bins / edge-vector binning (numeric, datetime, duration) ---- function [idx, labs, errmsg] = gb_edge_bins (col, scheme, incEdge, varname) idx = []; labs = {}; errmsg = ''; if (isa (col, 'datetime')) proxy = gb_dt2dn (col)(:); ctype = 'datetime'; elseif (isa (col, 'duration')) proxy = days (col)(:); ctype = 'duration'; elseif (isnumeric (col) || islogical (col)) proxy = double (col)(:); ctype = 'numeric'; else errmsg = sprintf (strcat ("binning is not supported for grouping variable", ... " '%s' of type '%s'."), varname, class (col)); return; endif if (isscalar (scheme) && (isnumeric (scheme) || islogical (scheme))) nb = double (scheme); if (! (nb >= 1 && nb == fix (nb))) errmsg = sprintf (strcat ("the number of bins for grouping variable", ... " '%s' must be a positive integer."), varname); return; endif good = proxy(! isnan (proxy)); if (isempty (good)) edgesP = [0, 1]; elseif (min (good) == max (good)) edgesP = [min(good), min(good) + 1]; else edgesP = min (good) + (0:nb) * (max (good) - min (good)) / nb; endif elseif (isnumeric (scheme)) if (! strcmp (ctype, 'numeric')) errmsg = sprintf (strcat ("bin edges for grouping variable '%s' must be", ... " of type '%s'."), varname, ctype); return; endif edgesP = sort (double (scheme(:)')); elseif (isa (scheme, 'datetime')) if (! strcmp (ctype, 'datetime')) errmsg = sprintf (strcat ("bin edges for grouping variable '%s' must be", ... " of type '%s'."), varname, ctype); return; endif edgesP = sort (gb_dt2dn (scheme)(:)'); elseif (isa (scheme, 'duration')) if (! strcmp (ctype, 'duration')) errmsg = sprintf (strcat ("bin edges for grouping variable '%s' must be", ... " of type '%s'."), varname, ctype); return; endif edgesP = sort (days (scheme(:))'); else errmsg = sprintf ("invalid binning scheme for grouping variable '%s'.", ... varname); return; endif if (numel (edgesP) < 2 || any (isnan (edgesP)) || any (diff (edgesP) <= 0)) errmsg = sprintf (strcat ("bin edges for grouping variable '%s' must be at", ... " least two finite, strictly increasing values."), varname); return; endif [idx, labs] = gb_assign_intervals (proxy, edgesP, incEdge, ... gb_edge_labels (edgesP, ctype, gb_dur_fmt (col, ctype))); endfunction ## ---- Duration / calendarDuration bin-width binning ---- function [idx, labs, errmsg] = gb_width_bins (col, width, incEdge, varname) idx = []; labs = {}; errmsg = ''; if (isa (col, 'datetime')) proxy = gb_dt2dn (col)(:); ctype = 'datetime'; elseif (isa (col, 'duration')) proxy = days (col)(:); ctype = 'duration'; else errmsg = sprintf (strcat ("bin-width binning is not supported for grouping", ... " variable '%s' of type '%s'."), varname, class (col)); return; endif good = proxy(! isnan (proxy)); if (isempty (good)) idx = NaN (numel (proxy), 1); labs = {'[0, 0)'}; return; endif if (! isa (width, 'duration')) # calendarDuration width errmsg = sprintf (strcat ("calendarDuration bin widths for grouping", ... " variable '%s' are not yet supported; use a time-unit keyword", ... " such as 'month'."), varname); return; endif w = days (width); if (! (w > 0)) errmsg = sprintf ("bin width for grouping variable '%s' must be positive.", ... varname); return; endif ## Anchor width bins to multiples of the width from 0. lo = floor (min (good) / w) * w; hi = max (good); ne = max (1, ceil ((hi - lo) / w + eps (hi))); edgesP = lo + (0:ne) * w; [idx, labs] = gb_assign_intervals (proxy, edgesP, incEdge, ... gb_edge_labels (edgesP, ctype, gb_dur_fmt (col, ctype))); endfunction ## ---- Calendar-period time-unit binning (datetime) ---- function [idx, labs] = gb_period_bins (col, unit, incEdge) DV = datevec (col(:)); n = size (DV, 1); nat = any (isnan (DV), 2); Y = DV(:,1); Mo = DV(:,2); D = DV(:,3); h = DV(:,4); mi = DV(:,5); s = DV(:,6); dn = gb_dt2dn (col)(:); ## Per-row integer period key and grid step (in key units). right = strcmpi (incEdge, 'right'); switch (unit) case 'year' key = Y; step = 1; case 'month' key = Y .* 12 + (Mo - 1); step = 1; case 'quarter' key = Y .* 4 + floor ((Mo - 1) / 3); step = 1; case 'decade' key = floor (Y / 10); step = 1; case 'century' key = floor (Y / 100); step = 1; case 'day' key = floor (dn); step = 1; case 'week' key = floor (dn) - (weekday (floor (dn)) - 1); step = 7; # Sunday-anchored case 'hour' key = floor (dn .* 24); step = 1; case 'minute' key = floor (dn .* 1440); step = 1; case 'second' key = round (dn .* 86400); step = 1; endswitch ## 'right' edge: a value exactly on a period boundary joins the previous bin. if (right) onEdge = gb_on_boundary (unit, Y, Mo, D, h, mi, s, dn); key(onEdge & ! nat) = key(onEdge & ! nat) - step; endif key(nat) = NaN; good = key(! isnan (key)); if (isempty (good)) idx = NaN (n, 1); labs = {gb_period_label(unit, 0)}; return; endif kmin = min (good); kmax = max (good); gridKeys = kmin:step:kmax; nb = numel (gridKeys); labs = cell (1, nb); for b = 1:nb labs{b} = gb_period_label (unit, gridKeys(b)); endfor idx = NaN (n, 1); ok = ! isnan (key); idx(ok) = (key(ok) - kmin) / step + 1; idx(idx < 1 | idx > nb) = NaN; endfunction ## Whether each row sits exactly on the start boundary of its period (used for ## 'IncludedEdge','right'). function tf = gb_on_boundary (unit, Y, Mo, D, h, mi, s, dn) switch (unit) case {'year', 'decade', 'century'} tf = (Mo == 1 & D == 1 & h == 0 & mi == 0 & s == 0); case {'month', 'quarter'} tf = (D == 1 & h == 0 & mi == 0 & s == 0); case {'day', 'week'} tf = (h == 0 & mi == 0 & s == 0); case 'hour' tf = (mi == 0 & s == 0); case 'minute' tf = (s == 0); otherwise tf = (s == round (s)); endswitch endfunction ## Category label for period key K of the given time UNIT. function lab = gb_period_label (unit, k) mon = {'Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'}; switch (unit) case 'year' lab = sprintf ("%d", k); case 'month' lab = sprintf ("%s-%04d", mon{mod(k,12)+1}, floor (k / 12)); case 'quarter' lab = sprintf ("Q%d %d", mod (k, 4) + 1, floor (k / 4)); case 'decade' lab = sprintf ("[%d, %d)", k * 10, k * 10 + 10); case 'century' lab = sprintf ("[%d, %d)", k * 100, k * 100 + 100); case 'day' lab = gb_date_str (k); case 'week' lab = sprintf ("[%s, %s)", gb_date_str (k), gb_date_str (k + 7)); case 'hour' lab = gb_datetime_str (k / 24); case 'minute' lab = gb_datetime_str (k / 1440); case 'second' lab = gb_datetime_str (k / 86400); endswitch endfunction ## 'dd-mmm-yyyy' for an integer datenum. function s = gb_date_str (dn) mon = {'Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'}; DV = datevec (dn); s = sprintf ("%02d-%s-%04d", DV(3), mon{DV(2)}, DV(1)); endfunction ## 'dd-mmm-yyyy HH:MM:SS' for a datenum. function s = gb_datetime_str (dn) mon = {'Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'}; DV = datevec (dn); s = sprintf ("%02d-%s-%04d %02d:%02d:%02d", DV(3), mon{DV(2)}, DV(1), ... DV(4), DV(5), round (DV(6))); endfunction ## ---- Shared interval assignment + labels ---- function [idx, labs] = gb_assign_intervals (proxy, edgesP, incEdge, estr) nb = numel (edgesP) - 1; left = ! strcmpi (incEdge, 'right'); idx = NaN (numel (proxy), 1); for k = 1:nb if (left) if (k < nb) in = proxy >= edgesP(k) & proxy < edgesP(k+1); else in = proxy >= edgesP(k) & proxy <= edgesP(k+1); endif else if (k == 1) in = proxy >= edgesP(k) & proxy <= edgesP(k+1); else in = proxy > edgesP(k) & proxy <= edgesP(k+1); endif endif idx(in) = k; endfor labs = cell (1, nb); for k = 1:nb if (left) br = '['; bl = ')'; if (k == nb) bl = ']'; endif else br = '('; bl = ']'; if (k == 1) br = '['; endif endif labs{k} = sprintf ("%s%s, %s%s", br, estr{k}, estr{k+1}, bl); endfor endfunction ## Format proxy bin edges as label strings of type CTYPE. DFMT is the duration ## column's display format (e.g. 'm'), so duration edges label in the same unit. function s = gb_edge_labels (edgesP, ctype, dfmt) n = numel (edgesP); s = cell (1, n); switch (ctype) case 'datetime' ## Use a uniform format across the edges: show the time component on every ## edge (including midnight) when any edge carries one, else date only. DVe = datevec (edgesP(:)); hasTime = any (DVe(:,4) != 0 | DVe(:,5) != 0 | round (DVe(:,6)) != 0); for i = 1:n if (hasTime) s{i} = gb_datetime_str (edgesP(i)); else s{i} = gb_date_str (edgesP(i)); endif endfor case 'duration' for i = 1:n ed = days (edgesP(i)); if (! isempty (dfmt)) ed.Format = dfmt; endif s{i} = char (ed); endfor otherwise for i = 1:n s{i} = gb_num_str (edgesP(i)); endfor endswitch endfunction function s = gb_num_str (v) if (isfinite (v) && v == fix (v) && abs (v) < 1e15) s = sprintf ("%d", v); else s = num2str (v); endif endfunction ## The duration column's display format (for duration edge labels), else ''. function f = gb_dur_fmt (col, ctype) f = ''; if (strcmp (ctype, 'duration')) f = col.Format; endif endfunction ## datetime -> datenum, NaT -> NaN. function dn = gb_dt2dn (v) sz = size (v); DV = datevec (v); nat = any (isnan (DV), 2); DV(nat,:) = 0; DV(nat,2:3) = 1; dn = datenum (DV); dn(nat) = NaN; dn = reshape (dn, sz); endfunction pr0m1th3as-datatypes-9c9a8d3/inst/private/__grp2idx__.m000066400000000000000000000050551522766574100230420ustar00rootroot00000000000000## Copyright (C) 2024-2026 Andreas Bertsatos ## ## This file is part of the datatypes package for GNU Octave. ## ## This program is free software; you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation; either version 3 of the ## License, or (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; if not, see ## . ## -*- texinfo -*- ## @deftypefn {private} {[@var{g}, @var{gn}, @var{gl}] =} __grp2idx__ (@var{s}) ## ## Get index for group variables. ## ## @end deftypefn function [g, gn, gl] = __grp2idx__ (s) if (nargin != 1) print_usage (); endif s_was_char = false; if (ischar (s)) s_was_char = true; s = cellstr (s); elseif (! isvector (s)) error (strcat ("grp2idx: S must be a vector, cell array of", ... " strings, or char matrix.")); endif [gl, I, g] = unique (s(:)); ## Fix order in here, since unique does not support this yet if (iscellstr (s)) I = sort (I); for i = 1:length (gl) gl_s(i) = gl(g(I(i))); idx(i,:) = (g == g(I(i))); endfor for i = 1:length (gl) g(idx(i,:)) = i; endfor gl = gl_s; gl = gl'; else I = sort (I); for i = 1:length (gl) gl_s(i) = gl(g(I(i))); idx(i,:) = (g == g(I(i))); endfor for i = 1:length (gl) g(idx(i,:)) = i; endfor gl = gl_s; gl = gl'; endif ## Handle NaNs and empty strings if (iscellstr (s)) empties = cellfun (@isempty, s); if (any (empties)) g(empties) = NaN; while (min (g) > 1) g--; endwhile endif empties = cellfun (@isempty, gl); if (any (empties)) gl(empties) = []; endif else ## This works fine because NaN come at the end after sorting, we don't ## have to worry about change on the indices. g(isnan (s)) = NaN; gl(isnan (gl)) = []; endif if (nargout > 1) if (iscellstr (gl)) gn = gl; elseif (iscell (gl)) gn = cellfun (@num2str, gl, "UniformOutput", false); else gn = arrayfun (@num2str, gl, "UniformOutput", false); endif endif if (nargout > 2 && s_was_char) gl = char (gl); endif endfunction pr0m1th3as-datatypes-9c9a8d3/inst/private/__ismember__.m000066400000000000000000000111761522766574100232670ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the datatypes package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {private} {@var{TF} =} __ismember__ (@var{a}, @var{s}) ## @deftypefnx {private} {@var{TF} =} __ismember__ (@var{a}, @var{s}, @qcode{'rows'}) ## @deftypefnx {private} {[@var{TF}, @var{index}] =} __ismember__ (@dots{}) ## @deftypefnx {private} {[@var{TF}, @var{index}] =} __ismember__ (@dots{}, @qcode{'legacy'}) ## ## Find set memders of data. ## ## @end deftypefn function [TF, index] = __ismember__ (a, s, varargin) if (nargin < 2 || nargin > 4) print_usage (); endif by_rows = any (strcmpi ('rows', varargin)); optlegacy = any (strcmpi ('legacy', varargin)); ## lookup() uses absolute values for complex input so we handle the ## real and imaginary parts separately (bug #52437) unless 'rows', which ## does not use lookup() so it can be handled normally. if (! by_rows && (iscomplex (a) || iscomplex (s))) real_argout = cell (nargout, 1); imag_argout = cell (nargout, 1); [real_argout{:}] = ismember (real (a), real (s), varargin{:}); [imag_argout{:}] = ismember (imag (a), imag (s), varargin{:}); TF = real_argout{1} & imag_argout{1}; if (nargout > 1) index = zeros (size (real_argout{2})); if (optlegacy) index(TF) = min (real_argout{2}(TF), imag_argout{2}(TF)); else index(TF) = max (real_argout{2}(TF), imag_argout{2}(TF)); endif endif return; endif ## lookup() does not handle logical values if (islogical (a)) a = uint8 (a); endif if (islogical (s)) s = uint8 (s); endif ## Matlab-compatible behavior (R2016b). See bug #51187. if (ischar (a) && rows (a) == 1 && iscell (s)) a = {a}; endif ## Another Matlab-compatible behavior. See bug #53924. if (isnumeric (a) && ischar (s)) s = double (s); elseif (ischar (a) && isnumeric (s)) a = double (a); endif if (! isempty (varargin)) if (! cellfun ('ischar', varargin)); error ("ismember: all options must be strings"); elseif (! all (strcmpi (varargin, 'rows') | strcmpi (varargin, 'legacy'))) error ('ismember: only "rows" and "legacy" are valid options'); endif endif if (! by_rows) s = s(:); ## Check sort status, because we expect the array will often be sorted. if (issorted (s)) is = []; else [s, is] = sort (s); endif ## Remove NaNs from table because lookup can't handle them if (isreal (s) && ! isempty (s) && isnan (s(end))) s = s(1:(end - sum (isnan (s)))); endif if (nargout > 1) if (!optlegacy) s = s(end : -1: 1); if (! (isempty (s) || isempty (a))) if (isempty (is)) is = [numel(s) : -1 : 1]; else is = is(1 : numel (s))(end : -1 : 1); endif endif endif index = lookup (s, a, "m"); TF = logical (index); if (! isempty (is)) index(TF) = is(index(TF)); endif else TF = lookup (s, a, "b"); endif else # "rows" argument if (isempty (a) || isempty (s)) TF = false (rows (a), 1); if (nargout > 1) index = zeros (rows (a), 1); endif else if (rows (s) == 1 && ! iscell (a) && ! iscell (s)) TF = all (a == s, 2); if (nargout > 1) index = double (TF); endif else ## FIXME: lookup does not support "rows", so we just use unique. na = rows (a); if (optlegacy) [~, ii, jj] = __unique__ ([a; s], 'rows', 'last'); jj = ii(jj(1:na)); TF = jj > na; if (nargout > 1) index = max (0, jj - na); endif else [~, ii, jj] = __unique__ ([s; a], 'rows', 'first'); nj = numel (jj) - (na - 1); jj = ii(jj(nj:end)); TF = jj < nj; if (nargout > 1) index = jj; index (jj > rows (s)) = 0; endif endif endif endif endif endfunction pr0m1th3as-datatypes-9c9a8d3/inst/private/__ismissing__.m000066400000000000000000000052641522766574100234720ustar00rootroot00000000000000## Copyright (C) 2024-2026 Andreas Bertsatos ## ## This file is part of the datatypes package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {private} {@var{TF} =} __ismissing__ (@var{A}) ## @deftypefnx {private} {@var{TF} =} __ismissing__ (@var{A}, @var{indicator}) ## ## Find missing data in a numeric or string array. ## ## @end deftypefn function TF = __ismissing__ (A, indicator) ## Check "indicator" if (nargin != 2) indicator = []; endif ## If A is an array of cell strings and indicator just a string, ## convert indicator to a cell string with one element if (iscellstr (A) && ischar (indicator) && ! iscellstr (indicator)) indicator = {indicator}; endif if ((! isempty (indicator)) && ((isnumeric (A) && ! (isnumeric (indicator) || islogical (indicator))) || (ischar (A) && ! ischar (indicator)) || (iscellstr (A) && ! iscellstr (indicator)))) error ("ismissing: 'indicator' and 'A' must have the same data type."); endif ## Main logic if (isempty (indicator)) if (isnumeric (A)) ## numeric matrix: just find the NaNs ## integer types have no missing value, but isnan will return false TF = isnan (A); elseif (iscellstr (A)) ## cell strings - find empty cells TF = cellfun ('isempty', A); elseif (ischar (A)) ## char matrix: find the white spaces TF = isspace (A); else ## no missing type defined, return false TF = false (size (A)); endif else ## Indicator specified for missing data TF = false (size (A)); if (isnumeric (A) || islogical (A)) for iter = 1:numel (indicator) if (isnan (indicator(iter))) TF(isnan(A)) = true; else TF(A == indicator(iter)) = true; endif endfor elseif (ischar (A)) for iter = 1:numel (indicator) TF(A == indicator(iter)) = true; endfor elseif (iscellstr (A)) for iter = 1:numel (indicator) TF(strcmp (A, indicator(iter))) = true; endfor endif endif endfunction pr0m1th3as-datatypes-9c9a8d3/inst/private/__nanmax__.m000066400000000000000000000115201522766574100227370ustar00rootroot00000000000000## Copyright (C) 2024-2026 Andreas Bertsatos ## ## This file is part of the datatypes package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {private} {@var{v} =} __nanmax__ (@var{x}) ## @deftypefnx {private} {@var{v} =} __nanmax__ (@var{x}, [], @var{dim}) ## @deftypefnx {private} {[@var{v}, @var{idx}] =} __nanmax__ (@dots{}) ## @deftypefnx {private} {@var{v} =} __nanmax__ (@var{x}, [], @qcode{'all'}) ## @deftypefnx {private} {@var{v} =} __nanmax__ (@var{x}, [], @var{vecdim}) ## @deftypefnx {private} {@var{v} =} __nanmax__ (@var{x}, [], @dots{}, @var{includenan}) ## @deftypefnx {private} {@var{v} =} __nanmax__ (@var{x}, @var{y}) ## @deftypefnx {private} {@var{v} =} __nanmax__ (@var{x}, @var{y}, @var{includenan}) ## ## Find the maximum while ignoring NaN values. ## ## @end deftypefn function [v, idx] = __nanmax__ (x, varargin) if (nargin < 1 || nargin > 4) print_usage; endif ## Get optional arguments nargs = numel (varargin); if (nargs == 0) y = []; dim = []; include = false; elseif (nargs == 1) y = varargin{1}; dim = []; include = false; elseif (nargs == 2) y = varargin{1}; if (isempty (y)) dim = varargin{2}; include = false; else dim = []; include = varargin{2}; endif elseif (nargs == 3) y = varargin{1}; if (isempty (y)) dim = varargin{2}; include = varargin{3}; else dim = []; include = varargin{2}; endif endif ## Process according to given input arguments if (isempty (y) && isempty (dim)) nanvals = isnan (x); x(nanvals) = -Inf; [v, idx] = max (x); if (include) v(any (nanvals)) = NaN; else v(all (nanvals)) = NaN; endif elseif (isempty (y) && strcmpi (dim, "all")) x = x(:); nanvals = isnan (x); x(nanvals) = -Inf; [v, idx] = max (x); if (include) v(any (nanvals)) = NaN; else v(all (nanvals)) = NaN; endif elseif (isempty (y)) if (isscalar (dim)) nanvals = isnan (x); x(nanvals) = -Inf; [v, idx] = max (x, [], dim); if (include) v(any (nanvals, dim)) = NaN; else v(all (nanvals, dim)) = NaN; endif else vecdim = sort (dim); if (! all (diff (vecdim))) error ("nanmax: VECDIM must contain non-repeating positive integers."); endif ## Ignore dimensions in VECDIM larger than actual array vecdim(find (vecdim > ndims (x))) = []; if (isempty (vecdim)) v = x; if (nargout > 1) idx = reshape ([1:numel(x)], size (x)); endif else ## Calculate permutation vector szx = size (x); remdims = 1:ndims (x); # All dimensions remdims(vecdim) = []; # Delete dimensions specified by vecdim nremd = numel (remdims); ## If all dimensions are given, it is equivalent to 'all' flag if (nremd == 0) x = x(:); nanvals = isnan (x); x(nanvals) = -Inf; [v, idx] = max (x); if (include) v(any (nanvals)) = NaN; else v(all (nanvals)) = NaN; endif else ## Permute to push vecdims to back perm = [remdims, vecdim]; x = permute (x, perm); ## Reshape to squash all vecdims in final dimension sznew = [szx(remdims), prod(szx(vecdim))]; x = reshape (x, sznew); ## Calculate nanmax on final dimension dim = nremd + 1; nanvals = isnan (x); x(nanvals) = -Inf; [v, idx] = max (x, [], dim); if (include) v(any (nanvals, dim)) = NaN; else v(all (nanvals, dim)) = NaN; endif ## Inverse permute back to correct dimensions v = ipermute (v, perm); idx = ipermute (idx, perm); endif endif endif else # y is not empty if (nargout > 1) error ("nanmax: a second output is not supported with this syntax."); endif Xnan = isnan (x); Ynan = isnan (y); x(Xnan) = -Inf; y(Ynan) = -Inf; v = max (x, y); if (include) v(Xnan | Ynan) = NaN; else v(Xnan & Ynan) = NaN; endif endif endfunction pr0m1th3as-datatypes-9c9a8d3/inst/private/__nanmin__.m000066400000000000000000000115111522766574100227350ustar00rootroot00000000000000## Copyright (C) 2024-2026 Andreas Bertsatos ## ## This file is part of the datatypes package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {private} {@var{v} =} __nanmin__ (@var{x}) ## @deftypefnx {private} {@var{v} =} __nanmin__ (@var{x}, [], @var{dim}) ## @deftypefnx {private} {[@var{v}, @var{idx}] =} __nanmin__ (@dots{}) ## @deftypefnx {private} {@var{v} =} __nanmin__ (@var{x}, [], @qcode{'all'}) ## @deftypefnx {private} {@var{v} =} __nanmin__ (@var{x}, [], @var{vecdim}) ## @deftypefnx {private} {@var{v} =} __nanmin__ (@var{x}, [], @dots{}, @var{includenan}) ## @deftypefnx {private} {@var{v} =} __nanmin__ (@var{x}, @var{y}) ## @deftypefnx {private} {@var{v} =} __nanmin__ (@var{x}, @var{y}, @var{includenan}) ## ## Find the minimum while ignoring NaN values. ## ## @end deftypefn function [v, idx] = __nanmin__ (x, varargin) if (nargin < 1 || nargin > 4) print_usage; endif ## Get optional arguments nargs = numel (varargin); if (nargs == 0) y = []; dim = []; include = false; elseif (nargs == 1) y = varargin{1}; dim = []; include = false; elseif (nargs == 2) y = varargin{1}; if (isempty (y)) dim = varargin{2}; include = false; else dim = []; include = varargin{2}; endif elseif (nargs == 3) y = varargin{1}; if (isempty (y)) dim = varargin{2}; include = varargin{3}; else dim = []; include = varargin{2}; endif endif ## Process according to given input arguments if (isempty (y) && isempty (dim)) nanvals = isnan (x); x(nanvals) = Inf; [v, idx] = min (x); if (include) v(any (nanvals)) = NaN; else v(all (nanvals)) = NaN; endif elseif (isempty (y) && strcmpi (dim, "all")) x = x(:); nanvals = isnan (x); x(nanvals) = Inf; [v, idx] = min (x); if (include) v(any (nanvals)) = NaN; else v(all (nanvals)) = NaN; endif elseif (isempty (y)) if (isscalar (dim)) nanvals = isnan (x); x(nanvals) = Inf; [v, idx] = min (x, [], dim); if (include) v(any (nanvals, dim)) = NaN; else v(all (nanvals, dim)) = NaN; endif else vecdim = sort (dim); if (! all (diff (vecdim))) error ("nanmin: VECDIM must contain non-repeating positive integers."); endif ## Ignore dimensions in VECDIM larger than actual array vecdim(find (vecdim > ndims (x))) = []; if (isempty (vecdim)) v = x; if (nargout > 1) idx = reshape ([1:numel(x)], size (x)); endif else ## Calculate permutation vector szx = size (x); remdims = 1:ndims (x); # All dimensions remdims(vecdim) = []; # Delete dimensions specified by vecdim nremd = numel (remdims); ## If all dimensions are given, it is equivalent to 'all' flag if (nremd == 0) x = x(:); nanvals = isnan (x); x(nanvals) = Inf; [v, idx] = min (x); if (include) v(any (nanvals)) = NaN; else v(all (nanvals)) = NaN; endif else ## Permute to push vecdims to back perm = [remdims, vecdim]; x = permute (x, perm); ## Reshape to squash all vecdims in final dimension sznew = [szx(remdims), prod(szx(vecdim))]; x = reshape (x, sznew); ## Calculate nanmin on final dimension dim = nremd + 1; nanvals = isnan (x); x(nanvals) = Inf; [v, idx] = min (x, [], dim); if (include) v(any (nanvals, dim)) = NaN; else v(all (nanvals, dim)) = NaN; endif ## Inverse permute back to correct dimensions v = ipermute (v, perm); idx = ipermute (idx, perm); endif endif endif else # y is not empty if (nargout > 1) error ("nanmin: a second output is not supported with this syntax."); endif Xnan = isnan (x); Ynan = isnan (y); x(Xnan) = Inf; y(Ynan) = Inf; v = min (x, y); if (include) v(Xnan | Ynan) = NaN; else v(Xnan & Ynan) = NaN; endif endif endfunction pr0m1th3as-datatypes-9c9a8d3/inst/private/__summary__.m000066400000000000000000000336361522766574100231660ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the datatypes package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . function __summary__ (S, DIM, NAMES, STATS) ## Grab size and type sz = S.Size; type = S.Type; name = S.Name; ## Print header str = strjoin (repmat ({'%d'}, 1, numel (sz)), 'x'); if (isempty (name)) str = sprintf ('\n%s %s array\n\n', str, type); else str = sprintf ('\n%s: %s %s array\n\n', name, str, type); endif fprintf (str, sz); ## Handle empty arrays if (false)#(any (sz == 0)) nanfn = {'Size', 'Type', 'Name', 'Categories', 'Counts'}; remfn = fieldnames (S); remfn(ismember (remfn, nanfn)) = []; if (! isempty (remfn)) fprintf ('Additional statistics:\n\n'); for i = 1:numel (remfn) stat = S.(remfn{i}); endfor endif return; endif ## Concatenate STATS (if any) disp_stats = false; if (! isempty (STATS)) disp_stats = true; stats = {}; for idx = 1:numel (STATS) sname = STATS{idx}; tmp = S.(sname); if (isnumeric (tmp)) tmp = num2cell (tmp); else tmp = dispstrings (tmp); endif stats = cat (DIM, stats, tmp); endfor ## Append stats to stat names tmpsz = sz; tmpsz(1:2) = 1; if (DIM == 1) tmp = repmat (STATS, tmpsz); stats = [tmp, stats]; elseif (DIM == 2) STATS = STATS'; tmpS = repmat (STATS, tmpsz); tmpE = repmat (repmat ({' '}, size (STATS)), tmpsz); stats = [tmpS; tmpE; stats]; endif endif ## Check if is included in NAMES disp_counts = false; if (! isempty (NAMES)) disp_counts = true; if (any (strcmpi (NAMES, ''))) counts = cat (DIM, S.Counts, S.NumMissing); else counts = S.Counts; endif counts = num2cell (counts); ## Append counts to categories tmpsz = sz; tmpsz(1:2) = 1; if (DIM == 1) tmp = repmat (NAMES, tmpsz); counts = [tmp, counts]; elseif (DIM == 2) tmp = repmat (NAMES', tmpsz); counts = [tmp; counts]; endif endif if (disp_counts) if (DIM < 3) disp_summary_low (counts); else disp_summary_high (counts, NAMES, DIM); endif endif if (disp_stats) if (DIM < 3) disp_summary_low (stats, true); else disp_summary_high (stats, STATS, DIM, true); endif endif endfunction function disp_summary_low (C, show_stats = false) if (ismatrix (C)) if (show_stats) fprintf ('Additional statistics:\n\n'); endif dispcellmatrix (C); else if (show_stats) fprintf ('Additional statistics:\n\n'); endif ## Handle each page separately sz = size (C); high_sz = sz(3:end); high_ixs = {}; for i = 1:numel (high_sz) high_ixs{i} = [1:high_sz(i)]'; endfor page_ixs = combvec (high_ixs); page_num = size (page_ixs, 1); for ix = 1:page_num p_ix = page_ixs(ix,:); pagestr = sprintf (strjoin (repmat ({'%d'}, 1, numel (p_ix)), ','), p_ix); if (show_stats) fprintf ('(:,:,%s) summary:\n\n', pagestr); else fprintf ('(:,:,%s) = \n\n', pagestr); endif idx.type = '()'; idx.subs = {':', ':', num2cell(p_ix){:}}; dispcellmatrix (subsref (C, idx)); endfor endif endfunction function disp_summary_high (C, D, DIM, show_stats = false) if (show_stats) fprintf ('Additional statistics:\n\n'); endif ## Handle each page separately sz = size (C); DIM = DIM - 2; high_sz = sz(3:end); high_ixs = {}; for i = 1:numel (high_sz) high_ixs{i} = [1:high_sz(i)]'; endfor if (isempty (high_ixs)) fprintf ('%s:\n', D{1}); dispcellmatrix (C); return; endif page_ixs = combvec (high_ixs); ## Group categories together if (DIM > 1) new_page = []; for i = 1:numel (D) new_page = [new_page; page_ixs(page_ixs(:,DIM) == i, :)]; endfor page_ixs = new_page; endif page_num = size (page_ixs, 1); for ix = 1:page_num p_ix = page_ixs(ix,:); cidx = p_ix(DIM); p_ixx = arrayfun (@(x) cellstr (num2str (x)), p_ix); p_ixx(DIM) = D(cidx); pagestr = sprintf (strjoin (repmat ({'%s'}, 1, numel (p_ix)), ','), ... p_ixx{:}); if (show_stats) fprintf ('(:,:,%s) summary:\n\n', pagestr); else fprintf ('(:,:,%s) = \n\n', pagestr); endif idx.type = '()'; idx.subs = {':', ':', num2cell(p_ix){:}}; dispcellmatrix (subsref (C, idx)); endfor endfunction function dispcellmatrix (C) sz = terminal_size (); cols = sz(2) - 4; colgap = " "; dispstr = {}; optLens = []; for iCol = 1:size (C, 2) [outstr, optLen] = mixedcell2str (C(:, iCol), cols); dispstr = [dispstr, outstr]; optLens = [optLens, optLen]; endfor if (sum (optLens + 6) <= cols) # all columns fit in terminal size rowSpat = ""; for iCol = 1:size (C, 2) rowSpat = [rowSpat, sprintf("%%-%ds", optLens(iCol)), colgap]; endfor for iRow = 1:size (C, 1) strrow = sprintf (rowSpat, dispstr{iRow,:}); fprintf (" %s\n", strrow); endfor fprintf ("\n"); else # we need to split rows optLen_cs = cumsum (optLens + 6); startCol = 1; while (! isempty (find (optLen_cs > cols))) stopCol = find (optLen_cs > cols, 1) - 1; ## Just in case a single column exceeds terminal size if (stopCol == 0) stopCol = 1; endif rowSpat = ""; for iCol = 1:stopCol rowSpat = [rowSpat, sprintf("%%-%ds", optLens(iCol)), colgap]; endfor optLens(1:iCol) = []; optLen_cs = cumsum (optLens + 6); stopCol = stopCol + startCol - 1; fprintf ("Columns %d through %d:\n\n", startCol, stopCol); for iRow = 1:size (C, 1) strrow = sprintf (rowSpat, dispstr{iRow,[startCol:stopCol]}); fprintf (" %s\n", strrow); endfor fprintf ("\n"); startCol = stopCol + 1; endwhile if (! isempty (optLens)) for iCol = 1:length (optLens) rowSpat = [rowSpat, sprintf("%%-%ds", optLens(iCol)), colgap]; endfor stopCol = startCol + iCol - 1; if (startCol == stopCol) fprintf ("Column %d:\n\n", startCol); else fprintf ("Columns %d through %d:\n\n", startCol, stopCol); endif for iRow = 1:size (C, 1) strrow = sprintf (rowSpat, dispstr{iRow,[startCol:stopCol]}); fprintf (" %s\n", strrow); endfor fprintf ("\n"); endif endif endfunction ## Special function to convert a mixed cell array to cellstr array ## that keeps MATLAB like formatting for each type of element function [dispstr, optLen] = mixedcell2str (data, cols) dispstr = cell (size (data)); ## Preallocate indexes to avoid truncation when last elements are 0 is_char = logical (zeros (size (data))); is_bool = is_char; is_numeric = is_char; is_object = is_char; is_struct = is_char; has_method = is_char; no_method = is_char; ## Nested cells are printed by size is_cell = cellfun ('iscell', data); sf = @(x) sprintf (strcat ([strjoin(repmat ({'%d'}, 1, ndims (x)), 'x'), ... ' cell']), size (x)); dispstr(is_cell) = cellfun (sf, data(is_cell), "UniformOutput", false); ## Empty cells are printed as doubles is_empty = cellfun (@isempty, data); sf = @(x) sprintf (strcat (repmat ("%dx", 1, ndims (x))(1:end-1), " %s"), ... size (x), class (x)); dispstr(is_empty) = cellfun (sf, data(is_empty), "UniformOutput", false); ## Index remaining scalar and row vector elements ve = cell2mat (cellfun (@(x) isrow (x), data, "UniformOutput", false)) == 1; ve = ve & ! (is_cell | is_empty); ## Index everything else me = cell2mat (cellfun (@(x) isrow (x), data, "UniformOutput", false)) != 1; me = me & ! (is_cell | is_empty); ## Catch 'char' scalars or row vectors is_char(ve) = cellfun ('ischar', data(ve)); sf = @(x) sprintf ("%s", x); dispstr(is_char) = cellfun (sf, data(is_char), "UniformOutput", false); ## Catch 'logical' scalars or row vectors is_bool(ve) = cellfun ('islogical', data(ve)); sf = @(x) sprintf ("%s", strtrim (sprintf ("%d ", x))); dispstr(is_bool) = cellfun (sf, data(is_bool), "UniformOutput", false); ## Catch 'numeric' scalars or row vectors is_numeric(ve) = cellfun ('isnumeric', data(ve)); sf = @(x) sprintf ("%s", strtrim (sprintf ("%g ", x))); dispstr(is_numeric) = cellfun (sf, data(is_numeric), "UniformOutput", false); ## Catch 'object' scalars or row vectors is_object(ve) = cellfun ('isobject', data(ve)); ## Handle objects with dispstring method available f = @(x) ismethod (x, 'dispstrings'); has_method(is_object) = cellfun (f, data(is_object)); sf = @(x) sprintf ("%s", strjoin (dispstrings (x), ' ')); dispstr(has_method) = cellfun (sf, data(has_method), "UniformOutput", false); ## Handle objects without no_method(is_object) = ! cellfun (f, data(is_object)); sf = @(x) sprintf (strcat ([strjoin(repmat ({'%d'}, 1, ndims (x)), 'x'), ... ' %s']), size (x), class (x)); dispstr(no_method) = cellfun (sf, data(no_method), "UniformOutput", false); ## Catch scalar elements or row vectors of 'struct' type is_struct(ve) = cellfun ('isstruct', data(ve)); sf = @(x) sprintf (strcat ([strjoin(repmat ({'%d'}, 1, ndims (x)), 'x'), ... ' struct']), size (x)); dispstr(is_struct) = cellfun (sf, data(is_struct), "UniformOutput", false); ## Catch remaining elements containing matrices or arrays of any type sf = @(x) sprintf (strcat ([strjoin(repmat ({'%d'}, 1, ndims (x)), 'x'), ... ' %s']), size (x), class (x)); dispstr(me) = cellfun (sf, data(me), "UniformOutput", false); ## Index numerical and logical values to right alignment pad_B = is_numeric | is_bool; # pad before: sprintf("{%%+%ds}" ## Index array types for bracketing brackets = pad_B | has_method; ## Get optimal length if (all (brackets)) optLen = max (cellfun (@length, dispstr(brackets))); elseif (any (brackets)) optLen1 = max (cellfun (@length, dispstr(brackets))); optLen2 = max (cellfun (@length, dispstr(! brackets))); optLen = max (optLen1, optLen2); else optLen = max (cellfun (@length, dispstr(! brackets))); endif ## Make sure a single column does not exceed terminal size if (optLen > cols) for i = 1:sum (is_char) hm_idx = find (is_char, i); sf = @(x) sprintf ("'%s ... '", x(1:cols-10)); dispstr(hm_idx) = cellfun (sf, data(hm_idx), "UniformOutput", false); endfor for i = 1:sum (is_bool) hm_idx = find (is_bool)(i); do_idx = find (cumsum (cellfun ('length', strsplit (dispstr{hm_idx})) ... + 1) > cols - 10, 1) - 1; if (! isempty (do_idx)) sf = @(x) sprintf ("%s ... ", strtrim (sprintf ("%d ", x(1:do_idx)))); dispstr(hm_idx) = cellfun (sf, data(hm_idx), "UniformOutput", false); endif endfor for i = 1:sum (is_numeric) hm_idx = find (is_numeric)(i); do_idx = find (cumsum (cellfun ('length', strsplit (dispstr{hm_idx})) ... + 1) > cols - 8, 1) - 1; if (! isempty (do_idx)) sf = @(x) sprintf ("%s ... ", strtrim (sprintf ("%g ", x(1:do_idx)))); dispstr(hm_idx) = cellfun (sf, data(hm_idx), "UniformOutput", false); endif endfor for i = 1:sum (has_method) hm_idx = find (has_method)(i); do_idx = find (cumsum (cellfun ('length', strsplit (dispstr{hm_idx})) ... + 4) > cols - 6, 1) - 1; if (! isempty (do_idx)) sf = @(x) sprintf ("%s ... ", ... strjoin (dispstrings (x(1:do_idx)), ' ')); dispstr(hm_idx) = cellfun (sf, data(hm_idx), "UniformOutput", false); endif endfor ## Recalculate optimal length if (all (brackets)) optLen = max (cellfun (@length, dispstr(brackets))) + 2; elseif (any (brackets)) optLen1 = max (cellfun (@length, dispstr(brackets))) + 2; optLen2 = max (cellfun (@length, dispstr(! brackets))); optLen = max (optLen1, optLen2); else optLen = max (cellfun (@length, dispstr(! brackets))); endif endif ## Pad data according to optimal length ## numeric and logical is right aligned, everything else is left aligned Ra_wB = sprintf ("%%+%ds", optLen); fcn = @(x) sprintf (Ra_wB, x); idx = pad_B & brackets; dispstr(idx) = cellfun (fcn, dispstr(idx), "UniformOutput", false); La_wB = sprintf ("%%-%ds", optLen); fcn = @(x) sprintf (La_wB, x); idx = ! pad_B & brackets; dispstr(idx) = cellfun (fcn, dispstr(idx), "UniformOutput", false); La_nB = sprintf ("%%-%ds", optLen); fcn = @(x) sprintf (La_nB, x); idx = (! pad_B & ! brackets) | me; dispstr(idx) = cellfun (fcn, dispstr(idx), "UniformOutput", false); endfunction function out = combvec (vecs) switch (numel (vecs)) case 1 out = vecs{1}(:); case 2 a = vecs{1}(:); b = vecs{2}(:); out = repmat (a, numel (b), 2); i_comb = 1; for i_a = 1:numel (a) for i_b = 1:numel (b) out(i_comb,:) = [a(i_a), b(i_b)]; i_comb = i_comb + 1; endfor endfor otherwise out = []; a = vecs{1}(:); rest = vecs(2:end); rest_combs = combvec (rest); for i = 1:numel (a) out = [out; [repmat(a(i), [size(rest_combs,1), 1]), rest_combs]]; endfor endswitch endfunction pr0m1th3as-datatypes-9c9a8d3/inst/private/__unique__.m000066400000000000000000000131001522766574100227570ustar00rootroot00000000000000## Copyright (C) 2024-2026 Andreas Bertsatos ## ## This file is part of the datatypes package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {private} {@var{B} =} __unique__ (@var{A}) ## @deftypefnx {private} {@var{B} =} __unique__ (@var{A}, @var{setOrder}) ## @deftypefnx {private} {@var{B} =} __unique__ (@var{A}, @var{occurrence}) ## @deftypefnx {private} {@var{B} =} __unique__ (@var{A}, @var{setOrder}, @var{occurrence}) ## @deftypefnx {private} {@var{B} =} __unique__ (@var{A}, @var{occurrence}, @var{setOrder}) ## @deftypefnx {private} {@var{B} =} __unique__ (@var{A}, @dots{}, @qcode{'rows'}) ## @deftypefnx {private} {@var{B} =} __unique__ (@var{A}, @qcode{'rows'}, @dots{}) ## @deftypefnx {private} {[@var{B}, @var{ixA}, @var{ixB}] =} __unique__ (@dots{}) ## ## Return the unique elements of @var{x}. ## ## @end deftypefn function [y, i, j] = __unique__ (x, varargin) if (nargin < 1) print_usage (); elseif (! (isnumeric (x) || islogical (x) || ischar (x) || iscellstr (x))) error ("unique: X must be an array or cell array of strings."); endif if (nargin > 1) ## Parse options if (! iscellstr (varargin)) error ("unique: options must be strings"); endif optrows = any (strcmp ("rows", varargin)); optfirst = any (strcmp ("first", varargin)); optlast = any (strcmp ("last", varargin)); optsorted = any (strcmp ("sorted", varargin)); optstable = any (strcmp ("stable", varargin)); if (optrows && ndims (x) != 2) error ("unique: 'rows' applies only to 2-D matrices."); elseif (optfirst && optlast) error ("unique: cannot specify both 'first' and 'last'."); elseif (optsorted && optstable) error ("unique: cannot specify both 'sorted' and 'stable'."); elseif (optrows + optfirst + optlast + optsorted + optstable != nargin-1) error ("unique: invalid optional argument."); endif ## Set defaults if not set earlier. if (! optfirst && ! optlast) optfirst = true; endif if (! optsorted && ! optstable) optsorted = true; endif else optrows = false; optfirst = true; optsorted = true; endif if (optrows) n = rows (x); isrowvec = false; else n = numel (x); isrowvec = isrow (x); endif ## Special cases 0 and 1 if (n == 0) y = x; if (! optrows && any (size (x))) if (iscellstr (x)) y = cell (0, 1); else y = zeros (0, 1, class (x)); endif endif i = j = []; return; elseif (n == 1) y = x; i = j = 1; return; endif ## Calculate y output if (optrows) if (nargout > 1 || ! optsorted) [y, j] = sortrows (x); j = j(:); else y = sortrows (x); endif if (iscellstr (x)) match = all (cellfun (@isequal, y(1:n-1,:), y(2:n,:)), 2); else match = all (y(1:n-1,:) == y(2:n,:), 2); endif if (optsorted) y(match,:) = []; else y = x; if (optfirst) y(j([false; match]), :) = []; else y(j([match; false]), :) = []; endif endif else if (isvector (x)) y = x; else y = x(:); endif if (nargout > 1 || ! optsorted) [y, j] = sort (y); j = j(:); else y = sort (y); endif if (iscellstr (y)) match = strcmp (y(1:n-1), y(2:n)); else match = (y(1:n-1) == y(2:n)); endif if (optsorted) y(match) = []; else if (isvector (x)) y = x; else y = x(:); endif if (optfirst) y(j([false; match(:)])) = []; else y(j([match(:); false])) = []; endif endif endif ## Calculate 2nd and 3rd outputs if (nargout > 1) if (optsorted) idx = find (match); if (optfirst) idx += 1; endif i = j; i(idx) = []; if (nargout > 2) j(j) = cumsum (! [false; match(:)]); endif else ## Get inverse of sort index j so that sort(x)(k) = x(j)(k) = x. k = j; # cheap way to copy dimensions k(j) = 1:n; ## Generate logical index of sorted unique value locations. if (optfirst) uniquex = ! [false; match(:)]; else uniquex = ! [match(:); false]; endif ## Remap unique locations to unsorted x, such that y = x(i). i = find (uniquex(k)); if (nargout > 2) ni = numel (i); u = find (uniquex); # Linear index of unique elements of sort(x) p = j; # cheap way to copy dimensions if (optfirst) l = u(cumsum (uniquex)); # Expand u for all elements in sort(x) p(i) = 1:ni; # set p to contain the vector positions of i elseif (optrows) l = u(cumsum (uniquex, 'reverse')); p(i) = ni:-1:1; else l = u(cumsum (! [false; match(:)])); p(i) = 1:ni; endif j = p(j(l(k))); # Replace j with 3rd output mapping y->x. endif endif endif endfunction pr0m1th3as-datatypes-9c9a8d3/inst/readtable.m000066400000000000000000000554041522766574100211430ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the datatypes package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {datatypes} {@var{tbl} =} readtable (@var{filename}) ## @deftypefnx {datatypes} {@var{tbl} =} readtable (@var{filename}, @var{Name}, @var{Value}) ## ## Read a file into a table, detecting variable types automatically. ## ## @code{@var{tbl} = readtable (@var{filename})} reads the file named by ## @var{filename} (a character vector or string scalar) and returns it as a ## @code{table}. The file type is inferred from the extension: ## @qcode{.txt}, @qcode{.csv}, and @qcode{.dat} are read as delimited text; ## @qcode{.ods} and @qcode{.fods} are read as OpenDocument spreadsheets. Use ## the @qcode{'FileType'} option to override the inferred type. ## ## By default the first row supplies the variable names and each column's data ## type is detected automatically (numeric, @code{datetime}, @code{duration}, or ## text). The following @var{Name}-@var{Value} options are supported: ## ## @multitable @columnfractions 0.28 0.72 ## @headitem @var{Name} @tab @var{Value} ## @item @qcode{'FileType'} @tab @qcode{'text'} or @qcode{'spreadsheet'}. ## @item @qcode{'ReadVariableNames'} @tab Logical; read the first row as variable ## names (default @qcode{true}). ## @item @qcode{'ReadRowNames'} @tab Logical; read the first column as row names ## (default @qcode{false}). ## @item @qcode{'Delimiter'} @tab Field delimiter for text files: a single ## character or one of @qcode{'comma'}, @qcode{'space'}, @qcode{'tab'}, ## @qcode{'semi'}, @qcode{'bar'} (default @qcode{','}). ## @item @qcode{'NumHeaderLines'} @tab Number of lines to skip before the header ## (default @qcode{0}). ## @item @qcode{'TextType'} @tab @qcode{'char'} or @qcode{'string'} for text ## columns (default @qcode{'char'}). ## @item @qcode{'VariableNamingRule'} @tab @qcode{'modify'} or @qcode{'preserve'} ## (default @qcode{'modify'}). ## @item @qcode{'Sheet'} @tab Spreadsheet only: the sheet to read, selected by ## name or by a 1-based index over the data sheets (default: the first sheet). ## @item @qcode{'Range'} @tab Spreadsheet only: an A1-style range such as ## @qcode{'C5'} or @qcode{'C5:D8'} limiting the region read. ## @end multitable ## ## Office Open XML spreadsheets (@qcode{.xlsx}, @qcode{.xlsm}) are read via the ## same interface as ODS. The legacy binary formats @qcode{.xls} and ## @qcode{.xlsb} are not supported; use @qcode{.xlsx}, @qcode{.ods}, or a text ## format. ## ## @seealso{writetable, csv2table, ods2table} ## @end deftypefn function tbl = readtable (filename, varargin) if (nargin < 1) print_usage (); endif if (! ((ischar (filename) && isvector (filename)) ... || (isa (filename, 'string') && isscalar (filename)))) error ("readtable: FILENAME must be a character vector or string scalar."); endif file = char (filename); optNames = {'FileType', 'ReadVariableNames', 'ReadRowNames', 'Delimiter', ... 'NumHeaderLines', 'TextType', 'VariableNamingRule', 'Sheet', ... 'Range'}; dfValues = {'', true, false, ',', 0, 'char', 'modify', [], ''}; [fileType, readVarNames, readRowNames, delim, numHeaderLines, textType, ... namingRule, sheet, range, args] = ... parsePairedArguments (optNames, dfValues, varargin(:)); if (! isempty (args)) error ("readtable: unknown option '%s'.", args{1}); endif if (isa (sheet, 'string')) sheet = char (sheet); endif if (isa (range, 'string')) range = char (range); endif ## Resolve the file type from the option or the extension [~, ~, ext] = fileparts (file); if (isempty (fileType)) switch (lower (ext)) case {'.txt', '.csv', '.dat'} fileType = 'text'; case {'.ods', '.fods', '.xlsx', '.xlsm'} fileType = 'spreadsheet'; case {'.xls', '.xlsb'} error (strcat ("readtable: '%s' Excel files are not supported; use", ... " '.xlsx', '.ods', or a text format."), ext); otherwise error (strcat ("readtable: cannot infer the file type from '%s';", ... " specify 'FileType'."), ext); endswitch endif ## Office Open XML (.xlsx/.xlsm) uses a separate reader from ODS. isXlsx = any (strcmpi (ext, {'.xlsx', '.xlsm'})); ## 'Sheet' and 'Range' apply only to spreadsheets; MATLAB rejects them on text ## files rather than silently ignoring them. if (strcmpi (fileType, 'text') && (! isempty (sheet) || ! isempty (range))) error (strcat ("readtable: 'Sheet' and 'Range' are not supported for", ... " text files.")); endif switch (lower (fileType)) case 'text' d = resolve_delimiter (delim); ## Hexadecimal auto-detection is a csv2table extension that MATLAB's ## readtable does not perform, so keep hex-like strings as text. tbl = csv2table (file, 'ReadVariableNames', readVarNames, ... 'ReadRowNames', readRowNames, 'RowNamesColumn', 1, ... 'NumHeaderLines', numHeaderLines, 'TextType', textType, ... 'VariableNamingRule', namingRule, 'Delimiter', d, ... 'HexType', 'text'); case 'spreadsheet' tbl = read_spreadsheet (file, readVarNames, readRowNames, textType, ... namingRule, sheet, range, isXlsx); otherwise error ("readtable: 'FileType' must be 'text' or 'spreadsheet'."); endswitch endfunction ## Translate a MATLAB delimiter (named or literal) into a single character. function d = resolve_delimiter (delim) if (isa (delim, 'string')) delim = char (delim); endif if (! ischar (delim)) error ("readtable: 'Delimiter' must be a character vector or string."); endif switch (lower (delim)) case {'comma', ','} d = ','; case {'space', ' '} d = ' '; case {'tab', "\t"} d = "\t"; case {'semi', ';'} d = ';'; case {'bar', '|'} d = '|'; otherwise if (isscalar (delim)) d = delim; else error ("readtable: unsupported 'Delimiter' value '%s'.", delim); endif endswitch endfunction ## Read an OpenDocument spreadsheet as a plain sheet: variable names from the ## first row (when requested), then one column per sheet column with its type ## taken from the native ODS cell value types. function tbl = read_spreadsheet (file, readVarNames, readRowNames, textType, ... namingRule, sheet, range, isXlsx) if (isXlsx) [data, vtype] = __xlsx2table__ (file, sheet); else [data, vtype] = __ods2table__ (file, sheet); endif if (ischar (data)) error ("readtable: %s", data); endif ## Apply an explicit 'Range' (absolute A1 coordinates), otherwise auto-trim ## the sheet to its used block so leading blank rows/columns are ignored. if (! isempty (range)) [data, vtype] = clip_range (data, vtype, range); else [data, vtype] = trim_used_block (data, vtype); endif ncols = size (data, 2); if (ncols == 0) tbl = table (); return; endif ## Variable names from the first row if (readVarNames && size (data, 1) >= 1) names = cell (1, ncols); for c = 1:ncols x = data{1,c}; if (ischar (x) && ! isempty (x)) names{c} = x; elseif (isempty (x)) names{c} = sprintf ("Var%d", c); else names{c} = num2str (x); endif endfor data(1,:) = []; vtype(1,:) = []; else names = arrayfun (@(c) sprintf ("Var%d", c), 1:ncols, ... 'UniformOutput', false); endif if (strcmpi (namingRule, 'modify')) names = matlab.lang.makeValidName (names); endif ## A leading column becomes row names when requested rowNames = {}; if (readRowNames) rowNames = ods_strings (data(:,1), vtype(:,1)); data(:,1) = []; vtype(:,1) = []; names(1) = []; endif ## Reconstruct each column from its native value type varValues = cell (1, numel (names)); for c = 1:numel (names) varValues{c} = reconstruct_column (data(:,c), vtype(:,c), textType); endfor if (isempty (rowNames)) tbl = table (varValues{:}, 'VariableNames', names); else tbl = table (varValues{:}, 'VariableNames', names, 'RowNames', rowNames); endif endfunction ## Clip the raw grid to an absolute A1 range; open-ended (Inf) bounds and ## bounds past the used area are capped to the grid, matching MATLAB, which ## reads whatever of the requested rectangle actually holds data. function [data, vtype] = clip_range (data, vtype, range) [r1, c1, r2, c2] = __a1ref__ (range); nr = size (data, 1); nc = size (data, 2); r2 = min (r2, nr); c2 = min (c2, nc); if (r1 > r2 || c1 > c2) data = cell (0, 0); vtype = cell (0, 0); return; endif data = data(r1:r2, c1:c2); vtype = vtype(r1:r2, c1:c2); endfunction ## Trim leading and trailing empty rows and columns to the used bounding box ## (interior blanks are preserved). This mirrors readtable's automatic used- ## range detection for foreign spreadsheets whose data does not start at A1. function [data, vtype] = trim_used_block (data, vtype) if (isempty (data)) return; endif blank = cellfun (@isempty, data); keptRows = find (! all (blank, 2)); keptCols = find (! all (blank, 1)); if (isempty (keptRows) || isempty (keptCols)) data = cell (0, 0); vtype = cell (0, 0); return; endif rr = keptRows(1):keptRows(end); cc = keptCols(1):keptCols(end); data = data(rr, cc); vtype = vtype(rr, cc); endfunction ## Reconstruct one column from its data cells and their ODS value types. function v = reconstruct_column (C, VT, textType) seen = VT(! cellfun (@isempty, VT)); if (isempty (seen)) kind = 'string'; else kind = seen{1}; endif switch (kind) case 'float' v = ods_numeric (C); case 'boolean' v = logical (ods_numeric (C)); case 'date' v = ods_iso2datetime (C); case 'time' v = ods_iso2duration (C); otherwise s = ods_strings (C, VT); if (strcmpi (textType, 'string')) v = string (s); else v = s; endif endswitch endfunction ## Numeric column from ODS cells; missing cells become NaN. function M = ods_numeric (C) M = nan (size (C)); for i = 1:numel (C) if (! isempty (C{i})) M(i) = double (C{i}); endif endfor endfunction ## Cellstr column from ODS cells; numbers are stringified, missing become ''. function S = ods_strings (C, VT) S = cell (size (C)); for i = 1:numel (C) x = C{i}; if (ischar (x)) S{i} = x; elseif (isempty (x)) S{i} = ''; else S{i} = num2str (x); endif endfor endfunction ## Parse an ISO 8601 date block into a datetime array; empty cells become NaT. function dt = ods_iso2datetime (C) sz = size (C); Y = nan (sz); Mo = nan (sz); D = nan (sz); h = nan (sz); mi = nan (sz); s = nan (sz); for i = 1:numel (C) str = C{i}; if (ischar (str) && ! isempty (str)) val = sscanf (str, "%d-%d-%dT%d:%d:%f"); if (numel (val) == 6) Y(i) = val(1); Mo(i) = val(2); D(i) = val(3); h(i) = val(4); mi(i) = val(5); s(i) = val(6); endif endif endfor dt = datetime (Y, Mo, D, h, mi, s); endfunction ## Parse an ISO 8601 duration block (PTnHnMnS) into a duration array; empty ## cells become NaN durations. function du = ods_iso2duration (C) tot = nan (size (C)); for i = 1:numel (C) str = C{i}; if (ischar (str) && ! isempty (str)) neg = (str(1) == '-'); if (neg) str(1) = []; endif tk = regexp (str, '^PT([\d.]+)H([\d.]+)M([\d.]+)S$', 'tokens'); if (! isempty (tk)) val = str2double (tk{1}{1}) * 3600 + str2double (tk{1}{2}) * 60 ... + str2double (tk{1}{3}); if (neg) val = -val; endif tot(i) = val; endif endif endfor du = seconds (tot); endfunction %!demo %! ## `readtable` is the unified, MATLAB-compatible reader: it picks text vs %! ## spreadsheet from the file extension. Here it reads a comma-delimited file, %! ## taking the first row as variable names and detecting each column's type. %! %! T = table ([38; 43], [71.5; 69.0], 'VariableNames', {'Age', 'Height'}); %! filename = fullfile (tempdir (), 'patients.csv'); %! writetable (T, filename); %! readtable (filename) %!demo %! ## Options let you override the defaults — for example read a specific %! ## spreadsheet `'Sheet'`, or turn the first column into row names. %! %! filename = fullfile (tempdir (), 'patients.ods'); %! T = table ([38; 43], 'VariableNames', {'Age'}, 'RowNames', {'Li', 'Diaz'}); %! writetable (T, filename, 'WriteRowNames', true); %! readtable (filename, 'ReadRowNames', true) %! %! delete (filename); ## Read a comma-delimited file with a header row and automatic type detection %!test %! fn = [tempname() '.csv']; %! fid = fopen (fn, 'w'); %! fputs (fid, sprintf ("id,val,name\n1,2.5,foo\n2,3.5,bar\n")); %! fclose (fid); %! unwind_protect %! t = readtable (fn); %! assert_equal (t.Properties.VariableNames, {'id', 'val', 'name'}); %! assert_equal (t.id, [1; 2]); %! assert_equal (t.val, [2.5; 3.5]); %! assert_equal (t.name, {'foo'; 'bar'}); %! unwind_protect_cleanup %! delete (fn); %! end_unwind_protect ## Read row names from the first column %!test %! fn = [tempname() '.csv']; %! fid = fopen (fn, 'w'); %! fputs (fid, sprintf ("rn,v\nr1,10\nr2,20\n")); %! fclose (fid); %! unwind_protect %! t = readtable (fn, 'ReadRowNames', true); %! assert_equal (t.Properties.RowNames, {'r1'; 'r2'}); %! assert_equal (t.v, [10; 20]); %! unwind_protect_cleanup %! delete (fn); %! end_unwind_protect ## A named delimiter selects the field separator %!test %! fn = [tempname() '.csv']; %! fid = fopen (fn, 'w'); %! fputs (fid, sprintf ("A;B\n1;x\n2;y\n")); %! fclose (fid); %! unwind_protect %! t = readtable (fn, 'Delimiter', 'semi'); %! assert_equal (t.Properties.VariableNames, {'A', 'B'}); %! assert_equal (t.A, [1; 2]); %! assert_equal (t.B, {'x'; 'y'}); %! unwind_protect_cleanup %! delete (fn); %! end_unwind_protect ## 'TextType' string returns text columns as string arrays %!test %! fn = [tempname() '.csv']; %! fid = fopen (fn, 'w'); %! fputs (fid, sprintf ("A,B\nfoo,bar\n")); %! fclose (fid); %! unwind_protect %! t = readtable (fn, 'TextType', 'string'); %! assert_equal (class (t.A), 'string'); %! unwind_protect_cleanup %! delete (fn); %! end_unwind_protect ## A datetime column that is not the first column is detected as datetime %!test %! fn = [tempname() '.csv']; %! fid = fopen (fn, 'w'); %! fputs (fid, sprintf ("id,d\n1,2024-01-15\n2,2024-02-20\n")); %! fclose (fid); %! unwind_protect %! t = readtable (fn); %! assert_equal (class (t.d), 'datetime'); %! unwind_protect_cleanup %! delete (fn); %! end_unwind_protect ## Hex-like text is kept as text (readtable does not auto-detect hexadecimals) %!test %! fn = [tempname() '.csv']; %! fid = fopen (fn, 'w'); %! fputs (fid, sprintf ("id,code\n1,a\n2,b\n")); %! fclose (fid); %! unwind_protect %! t = readtable (fn); %! assert_equal (t.code, {'a'; 'b'}); %! unwind_protect_cleanup %! delete (fn); %! end_unwind_protect ## Round-trip a spreadsheet written by writetable, restoring native types %!test %! fn = [tempname() '.ods']; %! T = table ([1; 2], {'x'; 'y'}, datetime (2024, 1, [1; 2]), ... %! seconds ([30; 90]), 'VariableNames', {'a', 'b', 'c', 'd'}); %! unwind_protect %! writetable (T, fn); %! R = readtable (fn); %! assert_equal (R.Properties.VariableNames, {'a', 'b', 'c', 'd'}); %! assert_equal (R.a, [1; 2]); %! assert_equal (R.b, {'x'; 'y'}); %! assert_equal (class (R.c), 'datetime'); %! assert_equal (isequaln (datevec (R.c), datevec (T.c)), true); %! assert_equal (class (R.d), 'duration'); %! assert_equal (isequaln (seconds (R.d), [30; 90]), true); %! unwind_protect_cleanup %! delete (fn); %! end_unwind_protect ## Build a flat '.fods' fixture with three sheets: Alpha (x,y), Beta (p,q), and ## Offset, whose data starts at C2 and is padded with the large repeat counts a ## real spreadsheet emits -- exercising sheet selection, used-block trimming, ## and the trailing-repeat cap. %!function fn = wr_multisheet () %! fn = [tempname() '.fods']; %! sc = @(s) ['' s ... %! '']; %! fc = @(x) sprintf (['%g'], x, x); %! rw = @(c) ['' c '']; %! alpha = ['' rw([sc('x') sc('y')]) ... %! rw([fc(1) fc(10)]) rw([fc(2) fc(20)]) rw([fc(3) fc(30)]) ... %! '']; %! beta = ['' rw([sc('p') sc('q')]) ... %! rw([fc(4) fc(40)]) rw([fc(5) fc(50)]) '']; %! pad = ''; %! big = ''; %! offset = ['' ... %! '' ... %! rw([pad sc('a') sc('b') big]) rw([pad fc(7) fc(8)]) ... %! '' ... %! '' ... %! '']; %! doc = ['' ... %! '' ... %! '' alpha beta offset ... %! '']; %! fid = fopen (fn, 'w'); %! fputs (fid, doc); %! fclose (fid); %!endfunction ## Select a sheet by name %!test %! fn = wr_multisheet (); %! unwind_protect %! R = readtable (fn, 'Sheet', 'Beta'); %! assert_equal (R.Properties.VariableNames, {'p', 'q'}); %! assert_equal (R.p, [4; 5]); %! assert_equal (R.q, [40; 50]); %! unwind_protect_cleanup %! delete (fn); %! end_unwind_protect ## Select a sheet by 1-based index (2 -> Beta) %!test %! fn = wr_multisheet (); %! unwind_protect %! R = readtable (fn, 'Sheet', 2); %! assert_equal (R.Properties.VariableNames, {'p', 'q'}); %! assert_equal (R.p, [4; 5]); %! unwind_protect_cleanup %! delete (fn); %! end_unwind_protect ## A sheet whose data is offset and padded is trimmed to its used block %!test %! fn = wr_multisheet (); %! unwind_protect %! R = readtable (fn, 'Sheet', 'Offset'); %! assert_equal (size (R), [1, 2]); %! assert_equal (R.Properties.VariableNames, {'a', 'b'}); %! assert_equal (R.a, 7); %! assert_equal (R.b, 8); %! unwind_protect_cleanup %! delete (fn); %! end_unwind_protect ## An explicit 'Range' clips absolutely: with the header row, and without it %!test %! fn = wr_multisheet (); %! unwind_protect %! R = readtable (fn, 'Sheet', 'Alpha', 'Range', 'A2:B3', ... %! 'ReadVariableNames', false); %! assert_equal (R.Var1, [1; 2]); %! assert_equal (R.Var2, [10; 20]); %! R2 = readtable (fn, 'Sheet', 'Alpha', 'Range', 'A1:B2'); %! assert_equal (R2.Properties.VariableNames, {'x', 'y'}); %! assert_equal (R2.x, 1); %! assert_equal (R2.y, 10); %! unwind_protect_cleanup %! delete (fn); %! end_unwind_protect ## Error: a requested sheet that does not exist %!error ... %! fn = wr_multisheet (); ... %! readtable (fn, 'Sheet', 'Nope'); ## Error: a sheet index past the last data sheet %!error ... %! fn = wr_multisheet (); ... %! readtable (fn, 'Sheet', 9); ## Error: 'Sheet' / 'Range' rejected on text files %!error ... %! readtable ([tempname() '.csv'], 'Sheet', 'A'); %!error ... %! readtable ([tempname() '.csv'], 'Range', 'A1:B2'); ## Round-trip through an '.xlsx' workbook written by writetable, including ## native datetime/duration/boolean columns and sheet/range selection %!test %! T = table ([1; 2; 3], {'a'; 'bb'; 'ccc'}, datetime (2024, 3, [1; 15; 31]), ... %! seconds ([30; 90; 3661]), [true; false; true], ... %! 'VariableNames', {'n', 's', 'when', 'dur', 'flag'}); %! fn = [tempname() '.xlsx']; %! unwind_protect %! writetable (T, fn, 'Sheet', 'Data'); %! R = readtable (fn, 'Sheet', 'Data'); %! assert_equal (R.Properties.VariableNames, {'n', 's', 'when', 'dur', 'flag'}); %! assert_equal (R.n, [1; 2; 3]); %! assert_equal (R.s, {'a'; 'bb'; 'ccc'}); %! assert_equal (class (R.when), 'datetime'); %! assert_equal (isequaln (datevec (R.when), datevec (T.when)), true); %! assert_equal (class (R.dur), 'duration'); %! assert_equal (isequaln (seconds (R.dur), [30; 90; 3661]), true); %! assert_equal (R.flag, [true; false; true]); %! unwind_protect_cleanup %! delete (fn); %! end_unwind_protect ## '.xlsx' 'Range' anchor on write, clipped on read %!test %! T = table ([1; 2; 3], {'a'; 'b'; 'c'}, 'VariableNames', {'n', 's'}); %! fn = [tempname() '.xlsx']; %! unwind_protect %! writetable (T, fn, 'Range', 'C3'); %! R = readtable (fn); %! assert_equal (R.n, [1; 2; 3]); %! R2 = readtable (fn, 'Range', 'C4:D6', 'ReadVariableNames', false); %! assert_equal (R2.Var1, [1; 2; 3]); %! unwind_protect_cleanup %! delete (fn); %! end_unwind_protect ## '.xlsm' round-trips too %!test %! T = table ([7; 8], 'VariableNames', {'v'}); %! fn = [tempname() '.xlsm']; %! unwind_protect %! writetable (T, fn); %! assert_equal (readtable (fn).v, [7; 8]); %! unwind_protect_cleanup %! delete (fn); %! end_unwind_protect ## Error: legacy binary Excel formats are not supported %!error ... %! readtable ('data.xls'); %!error ... %! readtable ('data.xlsb'); ## Error: an unknown option %!error ... %! readtable ([tempname() '.csv'], 'Bogus', 1); pr0m1th3as-datatypes-9c9a8d3/inst/seconds.m000066400000000000000000000044521522766574100206530ustar00rootroot00000000000000## Copyright (C) 2024-2026 Andreas Bertsatos ## ## This file is part of the datatypes package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {datatypes} {@var{D} =} seconds (@var{X}) ## ## Fixed-time duration in seconds. ## ## @code{@var{D} = seconds (@var{X})} returns a @qcode{duration} array ## representing fixed-time duration seconds equivalent to the values in @var{X}, ## which must be a numeric array. ## ## @code{seconds} is also available as a method for @qcode{duration} arrays, in ## which case it performs the opposite conversion. ## ## @seealso{duration, years, days, hours, minutes, milliseconds, ## duration.seconds} ## @end deftypefn function out = seconds (x) if (nargin == 0) x = 1; elseif (! isnumeric (x)) error ("seconds: input array must be numeric."); elseif (! isreal (x)) error ("seconds: input array must be real."); endif out = duration (0, 0, double (x), 'Format', 's'); endfunction %!demo %! ## `seconds` builds a fixed-length duration from a number of seconds — one of %! ## the builders (`seconds`, `minutes`, `hours`, `days`, `years`, %! ## `milliseconds`) you add together to make longer spans. %! %! seconds (90) %! %! ## A numeric array builds one duration per element. %! seconds ([30, 60, 90]) %!test %! X = magic (3); %! D = seconds (X); %! assert_equal (size (D), size (X)); %!test %! D = seconds ([1, 2, 3]); %! assert_equal (seconds (D), [1, 2, 3]); %!test %! D = seconds (int16 (1)); %! assert_equal (seconds (D), 1); %!test %! D = seconds (); %! assert_equal (seconds (D), 1); %!error seconds ("asd"); %!error seconds (1+i); pr0m1th3as-datatypes-9c9a8d3/inst/splitapply.m000066400000000000000000000120221522766574100214060ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the datatypes package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {datatypes} {@var{Y} =} splitapply (@var{func}, @var{X}, @var{G}) ## @deftypefnx {datatypes} {@var{Y} =} splitapply (@var{func}, @var{X1}, @dots{}, @var{XN}, @var{G}) ## @deftypefnx {datatypes} {[@var{Y1}, @dots{}, @var{YM}] =} splitapply (@dots{}) ## ## Split data into groups and apply a function to each group. ## ## @code{@var{Y} = splitapply (@var{func}, @var{X}, @var{G})} splits the data ## variable @var{X} into groups according to the group numbers @var{G} ## (typically produced by @code{findgroups}), applies the function handle ## @var{func} to each group, and concatenates the per-group results into the ## output @var{Y}. @var{G} must be a vector of positive integers with one ## element per element of @var{X}; if it identifies @var{N} groups, every ## integer between 1 and @var{N} must occur at least once. Elements for which ## @var{G} is @code{NaN} are omitted. ## ## @code{@var{Y} = splitapply (@var{func}, @var{X1}, @dots{}, @var{XN}, @var{G})} ## splits each of the data variables @var{X1}, @dots{}, @var{XN} and passes the ## corresponding group of each as a separate input argument to @var{func}. ## ## @code{[@var{Y1}, @dots{}, @var{YM}] = splitapply (@dots{})} returns the ## multiple outputs of @var{func}, each concatenated across groups. ## ## To split the variables of a @code{table}, call ## @code{splitapply (@var{func}, @var{T}, @var{G})}, which dispatches to the ## @code{table} method. ## ## @seealso{findgroups, table} ## @end deftypefn function varargout = splitapply (func, varargin) if (nargin < 3) print_usage (); endif if (! is_function_handle (func)) error ("splitapply: FUNC must be a function handle."); endif ## The last argument is the grouping vector; the rest are data variables. G = varargin{end}; data = varargin(1:end-1); ## Force each data variable to a column (char matrices keep their rows). for k = 1:numel (data) v = data{k}; if (! ischar (v) && isvector (v)) data{k} = v(:); endif endfor ## All data variables must share the same number of rows. n = size (data{1}, 1); for k = 2:numel (data) if (size (data{k}, 1) != n) error (strcat ("splitapply: all data variables must have the same", ... " number of rows.")); endif endfor ## Validate the grouping vector. if (! (isnumeric (G) && isvector (G) && numel (G) == n)) error (strcat ("splitapply: G must be a numeric vector with one element", ... " per row of the data variables.")); endif G = G(:); gv = G(! isnan (G)); if (any (gv != fix (gv)) || any (gv < 1)) error ("splitapply: G must contain positive integers."); endif if (isempty (gv)) N = 0; else N = max (gv); if (! isequal (unique (gv), (1:N)')) error (strcat ("splitapply: G must contain every integer between 1", ... " and the number of groups.")); endif endif ## Apply FUNC to each group and concatenate the per-group results. nvar = numel (data); nout = max (nargout, 1); results = cell (N, nout); for g = 1:N rows = (G == g); args = cell (1, nvar); for j = 1:nvar v = data{j}; args{j} = v(rows,:); endfor [results{g,:}] = func (args{:}); endfor varargout = cell (1, nout); for k = 1:nout varargout{k} = vertcat (results{:,k}); endfor endfunction %!assert_equal (splitapply (@(x) mean (x), [1; 2; 3; 4], [1; 1; 2; 2]), [1.5; 3.5]) %!assert_equal (splitapply (@(x) sum (x), [1; 2; 3], [2; 1; 2]), [2; 4]) %!test %! ## Multiple data variables become separate arguments to FUNC %! y = splitapply (@(a, b) sum (a) + sum (b), [1; 2; 3; 4], [10; 20; 30; 40], ... %! [1; 1; 2; 2]); %! assert_equal (y, [33; 77]); %!test %! ## Multiple outputs are concatenated separately %! [lo, hi] = splitapply (@(x) deal (min (x), max (x)), [4; 1; 3; 2], ... %! [1; 1; 2; 2]); %! assert_equal (lo, [1; 2]); %! assert_equal (hi, [4; 3]); %!test %! ## NaN group numbers omit the corresponding elements %! y = splitapply (@(x) mean (x), [1; 2; 99; 4], [1; 1; NaN; 2]); %! assert_equal (y, [1.5; 4]); %!error splitapply (@mean, [1; 2]) %!error splitapply (1, [1; 2], [1; 2]) %!error splitapply (@mean, [1; 2], [1; 3]) pr0m1th3as-datatypes-9c9a8d3/inst/string.m000066400000000000000000006206671522766574100205370ustar00rootroot00000000000000## Copyright (C) 2024-2026 Andreas Bertsatos ## ## This file is part of the datatypes package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . classdef string ## -*- texinfo -*- ## @deftp {datatypes} string ## ## Array representing sequences of characters. ## ## A string array is an array, where each element stores a sequence of ## characters of arbitrary length. ## ## A string array can also have missing elements, which differ from a sequence ## of characters of zero length (the equivalent of an empty character vector). ## ## To enable existing functions to handle string arrays as if they were cell ## arrays of character vectors or character arrays, use the ## @code{convertCharsToStrings} function inside your code. To enable ## functions working with string arrays to accept cell arrays of character ## vectors or character vectors as if they were string arrays or string ## scalars, use the @code{convertStringsToChars} function. ## ## @seealso{convertCharsToStrings, convertStringsToChars} ## @end deftp properties (SetAccess = private, Hidden) ## Text data strs = {''} ## Missing values flag isMissing = false endproperties methods (Hidden) ## Custom display function display (this) in_name = inputname (1); if (! isempty (in_name)) fprintf ('%s =\n', in_name); endif __disp__ (this, 'string', in_name); endfunction ## Custom display function disp (this) __disp__ (this, 'string'); endfunction endmethods ################################################################################ ## ** Create String and Convert Type ** ## ################################################################################ ## Available Methods ## ## ## ## 'string' 'dispstrings' 'cellstr' 'cell' ## ## 'char' 'double' ## ## ## ################################################################################ methods (Access = public) ## -*- texinfo -*- ## @deftypefn {string} {@var{str} =} string () ## @deftypefnx {string} {@var{str} =} string (@var{in}) ## @deftypefnx {string} {@var{str} =} string (@var{calendarDuration}, @qcode{'Format'}, @var{FMT}) ## @deftypefnx {string} {@var{str} =} string (@var{duration}, @qcode{'Format'}, @var{FMT}) ## @deftypefnx {string} {@var{str} =} string (@{@var{in1}, @dots{}, @var{inN}@}) ## ## Create a new string array. ## ## @code{@var{str} = string ()} creates a scalar string array, whose element ## contains an empty character vector. ## ## @code{@var{str} = string (@var{in})} creates a string array of the same ## size as @var{in}, which is converted to string according to the following ## options: ## ## @itemize ## @item character arrays are converted so that each row becomes a string ## element, with any trailing whitespace preserved; cell arrays of character ## vectors are stored as-is. ## @item numeric arrays are converted via the code @code{num2str} function. ## @item logical arrays are converted to either @qcode{false} or ## @qcode{true} character sequences. ## @item categorical arrays are converted via their @code{cellstr} method. ## @item datetime arrays are converted via their @code{dispstrings} method. ## @item calendarDuration arrays and duration arrays are converted via their ## respective @code{cellstr} methods, in which case an extra pair argument ## is supported to allow parsing to the respective method the appropriate ## display format. See @qcode{calendarDuration} and @qcode{duration} for ## valid formats parsed through @var{FMT} to each class method. Extra input ## arguments to the @code{string} constructor except for this case are ## ignored. ## @item missing arrays are converted to a string array of missing elements. ## @end itemize ## ## @code{@var{str} = string (@{@var{in1}, @dots{}, @var{inN}@})} creates a ## string array from a cell array, which may contain any combination of the ## aforementioned data types, provided that each cell element is compatible ## to a string scalar. When using this syntax, calendarDuration arrays and ## duration arrays are converted via their @code{dispstrings} method, hence ## no extra format argument is meaningful. ## ## @seealso{calendarDuration, categorical, datetime, duration, missing} ## @end deftypefn function this = string (in, varargin) ## Return empty string if (nargin == 0) return; endif ## Handle empty input if (isempty (in) && ! ischar (in)) sz = size (in); this.strs = repmat ({''}, sz); this.isMissing = false (sz); return; endif ## Handle string input first if (isa (in, "string")) this.strs = in.strs; this.isMissing = in.isMissing; return endif ## Handle all other valid cases if (isa (in, "categorical")) this.strs = cellstr (in); this.isMissing = isundefined (in); elseif (ischar (in)) ## Convert each row to a string element with 'num2cell' rather than ## 'cellstr', which would deblank and silently drop trailing whitespace ## (MATLAB's 'string' preserves it). An empty char array yields a ## single empty string element, matching the no-argument constructor. if (isempty (in)) this.strs = {''}; elseif (ndims (in) > 2) sz = size (in); nr = prod (sz([1,3:end])); nc = sz(2); in = reshape (in, nr, nc); in = num2cell (in, 2); sz(2) = []; this.strs = reshape (in, sz); else this.strs = num2cell (in, 2); endif this.isMissing = false (size (this.strs)); elseif (iscellstr (in)) this.strs = in; this.isMissing = false (size (this.strs)); elseif (iscell (in)) sz = size (in); this.strs = repmat ({''}, sz); this.isMissing = false (sz); fcn = @(x) isscalar (x) | isempty (x) | (ischar (x) & isvector (x)); all_scalar = all (cellfun (fcn, in)); if (! all_scalar) error (strcat ("string: cell array must explicitly contain", ... " scalar elements or character vectors.")); endif is_numeric = cellfun (@isnumeric, in); is_logical = cellfun (@islogical, in); is_cellstr = cellfun (@iscellstr, in); is_charvec = cellfun (@ischar, in); is_strings = cellfun (@(x) isa (x, "string"), in); is_missing = cellfun (@(x) isa (x, "missing"), in); is_datetime = cellfun (@(x) isa (x, "datetime"), in); class_types = {"duration", "calendarDuration"}; is_duration = cellfun (@(x) any (isa (x, class_types)), in); ## Check for unsupported classes all_support = all (is_numeric | is_logical | is_cellstr | ... is_charvec | is_strings | is_missing | ... is_datetime | is_duration); if (! all_support) error ("string: cell array contains unsupported types."); endif ## Handle numeric elements first (including empty cells []) if (any (is_numeric(:))) tmpval = in(is_numeric); sz = size (tmpval); strs = repmat ({''}, sz); tf_m = false (sz); is_empty = cellfun (@isempty, tmpval); tf_m(is_empty) = true; tmpval = cell2mat (tmpval(! tf_m)); strs(! tf_m) = arrayfun (@num2str, tmpval, "UniformOutput", false); is_nan = strcmp (strs, 'NaN'); strs(is_nan) = {''}; tf_m(is_nan) = true; this.strs(is_numeric) = strs; this.isMissing(is_numeric) = tf_m; endif ## Handle logical (no missing values here) if (any (is_logical(:))) tmpval = in(is_logical); sz = size (tmpval); strs = repmat ({'false'}, sz); tf_m = false (sz); is_true = logical (cell2mat (tmpval)); strs(is_true) = {'true'}; this.strs(is_logical) = strs; this.isMissing(is_logical) = tf_m; endif ## Handle cell arrays of character vectors if (any (is_cellstr(:))) this.strs(is_cellstr) = in{is_cellstr}; endif ## Handle character vectors (including empty 0x0 char vectors '') if (any (is_charvec(:))) this.strs(is_charvec) = in(is_charvec); endif ## Handle strings if (any (is_strings(:))) this.strs(is_strings) = [in{is_strings}].strs; this.isMissing(is_strings) = [in{is_strings}].isMissing; endif ## Handle missing objects if (any (is_missing(:))) this.isMissing(is_missing) = true; endif ## Handle datetime objects if (any (is_datetime(:))) tmpval = in(is_datetime); sz = size (tmpval); strs = repmat ({''}, sz); tf_m = false (sz); is_nat = cellfun (@isnat, tmpval); tf_m(is_nat) = true; strs(! is_nat) = cellfun (@dispstrings, tmpval(! is_nat)); this.strs(is_datetime) = strs; this.isMissing(is_datetime) = tf_m; endif ## Handle duration and calendarDuration objects if (any (is_duration(:))) tmpval = in(is_duration); sz = size (tmpval); strs = repmat ({''}, sz); tf_m = false (sz); is_nan = cellfun (@isnan, tmpval); tf_m(is_nan) = true; strs(! is_nan) = cellfun (@dispstrings, tmpval(! is_nan)); this.strs(is_duration) = strs; this.isMissing(is_duration) = tf_m; endif elseif (isnumeric (in)) is_nan = isnan (in); this.isMissing = is_nan; if (any (is_nan(:))) strs = repmat ({''}, size (in)); strs(! is_nan) = arrayfun (@(x) {num2str(x)}, in(! is_nan)); this.strs = strs; else this.strs = arrayfun (@(x) {num2str(x)}, in); endif elseif (islogical (in)) sz = size (in); strs = repmat ({'false'}, sz); strs(in) = {'true'}; this.strs = strs; this.isMissing = false (sz); elseif (isa (in, 'datetime')) is_nat = isnat (in); this.isMissing = is_nat; if (any (is_nat(:))) strs = repmat ({''}, size (in)); strs(! is_nat) = dispstrings (in(! is_nat)); this.strs = strs; else this.strs = dispstrings (in); endif elseif (isa (in, 'duration') || isa (in, 'calendarDuration')) is_nan = isnan (in); this.isMissing = is_nan; if (any (is_nan(:))) strs = repmat ({''}, size (in)); strs(! is_nan) = cellstr (in(! is_nan), varargin{:}); this.strs = strs; else this.strs = cellstr (in, varargin{:}); endif elseif (isa (in, 'missing')) this.strs = repmat ({''}, size (in)); this.isMissing = true (size (in)); else error ("string: unsupported input type: '%s'", class (in)); endif endfunction ## -*- texinfo -*- ## @deftypefn {string} {@var{cstr} =} dispstrings (@var{str}) ## ## Get display formatted strings for each element of a string object. ## ## @code{@var{cstr} = dispstrings (@var{str})} returns a cellstr array of ## character vectors, @var{cstr}, which has the same size as the input ## string object, @var{str}. These character vectors will either be the ## string contents of each corresponding element or @qcode{} for ## missing values. ## ## Composed string elements, i.e. double quoted strings, are translated so ## that any special characters are represented by their corresponding ## escaped character sequence, unless the input string, @var{str}, is a ## scalar, in which case text retains its original composition but newlines ## are prepadded with four white space characters for aligned display. ## ## @end deftypefn function cstr = dispstrings (this) cstr = strcat ({'"'}, this.strs, {'"'}); if (! isscalar (this)) cstr = strrep (cstr, sprintf ("\r\n"), '↵'); cstr = strrep (cstr, sprintf ("\n"), '↵'); cstr = strrep (cstr, sprintf ("\t"), '→'); else cstr = strrep (cstr, sprintf ("\n"), sprintf ("\n%s", ' ')); endif cstr(this.isMissing) = ""; endfunction ## -*- texinfo -*- ## @deftypefn {string} {@var{cstr} =} cellstr (@var{str}) ## ## Convert string array to a cell array of character vectors. ## ## @code{@var{cstr} = cellstr (@var{str})} returns a cell array of ## character vectors, @var{cstr}, which has the same size as the input ## string @var{str}. Both empty strings and missing values are returned as ## empty character vectors. ## ## @end deftypefn function cstr = cellstr (this) cstr = this.strs; endfunction ## -*- texinfo -*- ## @deftypefn {string} {@var{c_arr} =} cell (@var{str}) ## ## Convert string array to a cell array. ## ## @code{@var{c_arr} = cell (@var{str})} returns a cell array, @var{c_arr}, ## which has the same size as the input string @var{str}. All elements in ## @var{str} that represent real or complex numbers are converted to ## equivalent double values, whereas all other non-missing elements are ## converted to character vectors. Zero-length strings are converted to ## @qcode{''} empty character vectors, while missing values are returned as ## @qcode{[]} empty numeric vectors. ## ## @end deftypefn function c_arr = cell (this) ## Convert numbers first X = double (this); c_arr = num2cell (X); ## Grab everything else is_text = isnan (X) & ! this.isMissing; c_arr(is_text) = this.strs(is_text); c_arr(this.isMissing) = {[]}; endfunction ## -*- texinfo -*- ## @deftypefn {string} {@var{c_mat} =} char (@var{str}) ## ## Convert string array to a character matrix. ## ## @code{@var{c_mat} = char (@var{str})} returns a character matrix, ## @var{c_mat}, which contains as many rows as the elements of the string. ## Both empty strings and missing values are returned as empty character ## vectors. ## ## @end deftypefn function c_mat = char (this) c_mat = char (this.strs{:}); endfunction ## -*- texinfo -*- ## @deftypefn {string} {@var{X} =} double (@var{str}) ## ## Convert string array to a double array. ## ## @code{@var{X} = char (@var{str})} returns a double array, @var{X}, which ## has the same size as the input string @var{str}. All elements in ## @var{str} that represent real or complex numbers are converted to ## equivalent double values. Otherwise, @qcode{NaN} is returned. ## ## @end deftypefn function out = double (this) out = NaN (size (this)); fcn = @(x) str2num (char (x)); c_out = cellfun (fcn, this.strs, "UniformOutput", false); t_num = ! cellfun (@isempty, c_out); out(t_num) = cell2mat (c_out(t_num)); endfunction endmethods ################################################################################ ## ** Summary Information ** ## ################################################################################ ## Available Methods ## ## ## ## 'size' 'ndims' 'numel' 'length' ## ## 'strlength' 'count' 'keyHash' ## ## ## ################################################################################ methods (Access = public) ## -*- texinfo -*- ## @deftypefn {string} {@var{sz} =} size (@var{str}) ## @deftypefnx {string} {@var{dim_sz} =} size (@var{str}, @var{dim}) ## @deftypefnx {string} {@var{dim_sz} =} size (@var{str}, @var{d1}, @var{d2}, @dots{}) ## @deftypefnx {string} {[@var{rows}, @var{columns}, @dots{}, @var{dim_n_sz}] =} size (@dots{}) ## ## Return the size of a string array. ## ## @code{@var{sz} = size (@var{str})} returns a row vector with the size ## (number of elements) of each dimension for the string array @var{str}. ## ## @code{@var{dim_sz} = size (@var{str}, @var{dim})} returns the size of ## the corresponding dimension specified in @var{dim}. If @var{dim} is a ## vector, then @var{dim_sz} is a vector of the same length and with each ## element corresponding to a specified dimension. Multiple dimensions may ## also be specified as separate arguments. ## ## With a single output argument, @code{size} returns a row vector. When ## called with multiple output arguments, @code{size} returns the size of ## dimension N in the Nth argument. ## ## @end deftypefn function varargout = size (this, varargin) if (! isempty (varargin)) sz = size (this.strs, varargin{:}); else sz = size (this.strs); endif if (nargout == 0 || nargout == 1) varargout{1} = sz; elseif (numel (sz) != nargout) error (["string.size: nargout > 1 but does not", ... " match number of requested dimensions."]); else for i = 1:nargout varargout{i} = sz(i); endfor endif endfunction ## -*- texinfo -*- ## @deftypefn {string} {@var{out} =} ndims (@var{str}) ## ## Number of dimensions in a string array. ## ## @code{@var{out} = ndims (@var{str})} returns the number of dimensions of ## the string array @var{D}. ## ## @end deftypefn function out = ndims (this) out = ndims (this.strs); endfunction ## -*- texinfo -*- ## @deftypefn {string} {@var{out} =} numel (@var{str}) ## ## Total number of elements in a string array. ## ## @end deftypefn function out = numel (this, varargin) out = numel (this.strs); #out = 1 endfunction ## -*- texinfo -*- ## @deftypefn {string} {@var{N} =} length (@var{str}) ## ## Length of a string vector. ## ## @code{@var{N} = length (@var{str})} returns the size of the longest ## dimension of the string array @var{str}, unless any of its dimensions ## has zero length, in which case @code{length (@var{D})} returns 0. ## ## @end deftypefn function N = length (this) if (isempty (this.strs)) N = 0; else N = max (size (this.strs)); endif endfunction ## -*- texinfo -*- ## @deftypefn {string} {@var{out} =} strlength (@var{str}) ## ## Length of text in string arrays. ## ## @end deftypefn function out = strlength (this) out = NaN (size (this)); fcn = @(x) __unicode_length__ (x); TF = ! this.isMissing; out(TF) = cell2mat (cellfun (fcn, this.strs(TF), "UniformOutput", false)); endfunction ## -*- texinfo -*- ## @deftypefn {string} {@var{out} =} count (@var{str}, @var{pattern}) ## @deftypefnx {string} {@var{out} =} count (@var{str}, @var{pattern}, @qcode{'IgnoreCase'}, @qcode{true}) ## ## Count occurences of pattern in string array. ## ## @code{@var{out} = count (@var{str}, @var{pattern})} returns a numerical ## array @var{out} of the same size as @var{str} containing the number of ## occurences of @var{pattern} in each corresponding element of @var{str}. ## ## @code{@var{out} = count (@var{str}, @var{pattern}, @qcode{'IgnoreCase'}, ## @qcode{true})} ignores case when identifying occurences of @var{pattern}. ## ## @end deftypefn function out = count (this, pattern, varargin) ## Check pattern if (nargin < 2) error ("string.count: PATTERN is required."); elseif (ischar (pattern) || isstring (pattern)) pattern = cellstr (pattern); elseif (! iscellstr (pattern)) error (strcat ("string.count: PATTERN must be a character vector,", ... " a string array, or cell array of character vectors.")); endif ## Parse optional Name-Value paired arguments optNames = {'IgnoreCase'}; dfValues = {false}; [IgnoreCase, arg] = parsePairedArguments (optNames, dfValues, ... varargin(:)); ## Check optional Name-Value paired arguments if (! (islogical (IgnoreCase) && isscalar (IgnoreCase))) error ("string.count: 'IgnoreCase' must be a logical scalar."); elseif (! isempty (arg)) error ("string.count: unrecognized input argument."); endif ## Accumulate the number of occurences of each pattern in each element out = zeros (size (this)); vid = ! this.isMissing; str = this.strs(vid); if (IgnoreCase) for i = 1:numel (pattern) out(vid) += cellfun ('numel', ... strfind (lower (str), lower (pattern{i}))); endfor else for i = 1:numel (pattern) out(vid) += cellfun ('numel', strfind (str, pattern{i})); endfor endif endfunction ## -*- texinfo -*- ## @deftypefn {string} {@var{hey} =} keyHash (@var{str}) ## ## Generate a hash code for string array. ## ## @code{@var{h} = keyHash (@var{str})} generates a @qcode{uint64} scalar ## that represents the input array @var{str}. @code{keyHash} utilizes the ## 64-bit FNV-1a variant of the Fowler-Noll-Vo non-cryptographic hash ## function. ## ## @code{@var{h} = keyHash (@var{str}), @var{base}} also generates a 64-bit ## hash code using @var{base} as the offset basis for the FNV-1a hash ## algorithm. @var{base} must be a @qcode{uint64} integer type scalar. Use ## this syntax to cascade @code{keyHash} on multiple objects for which a ## single hash code is required. ## ## Note that unlike MATLAB, this implementation does not use any random ## seed. As a result, @code{keyHash} will always generate the exact same ## hash key for any particular input across different workers and Octave ## sessions. ## ## @end deftypefn function key = keyHash (this, base = []) ## Initialize string with size and class name size_str = sprintf ('%dx', size (this.strs))(1:end-1); init_str = [size_str 'string']; if (base) if (! (isscalar (base) && isa (base, 'uint64'))) error ("string.keyHash: BASE must be a UINT64 scalar."); endif key = __ckeyHash__(init_str, base); else key = __ckeyHash__(init_str); endif ## Compute hash with underlying string array values strs = [this.strs{:}]; key = __ckeyHash__(strs, key); key = __nkeyHash__(this.isMissing(:), key); endfunction endmethods ################################################################################ ## ** Query Operations ** ## ################################################################################ ## Available Methods ## ## ## ## 'contains' 'endsWith' 'startsWith' 'matches' ## ## 'iscolumn' 'isempty' 'ismatrix' 'ismember' ## ## 'ismissing' 'isrow' 'isscalar' 'issorted' ## ## 'issortedrows' 'isstring' 'isvector' ## ## ## ################################################################################ methods (Access = public) ## -*- texinfo -*- ## @deftypefn {string} {@var{TF} =} contains (@var{str}, @var{pattern}) ## @deftypefnx {string} {@var{TF} =} contains (@var{str}, @var{pattern}, @qcode{'IgnoreCase'}, @qcode{true}) ## ## Test if strings contain pattern. ## ## @code{@var{TF} = contains (@var{str}, @var{pattern})} returns a logical ## array @var{TF} of the same size as @var{A} containing @qcode{true} for ## each corresponding element of @var{str} that contains the specified ## @var{pattern} and @qcode{false} otherwise. Similarly to @qcode{NaN} ## values, @qcode{} elements do not match any pattern and always ## return @qcode{false}. ## ## @code{@var{TF} = contains (@var{str}, @var{pattern}, ## @qcode{'IgnoreCase'}, @qcode{true})} ignores case when determining if ## @var{str} ends with @var{pattern}. ## ## @end deftypefn function TF = contains (this, pattern, varargin) ## Check pattern if (nargin < 2) error ("string.contains: PATTERN is required."); elseif (ischar (pattern) || isstring (pattern)) pattern = cellstr (pattern); elseif (! iscellstr (pattern)) error (strcat ("string.contains: PATTERN must be a character", ... " vector, a string array, or cell array of", ... " character vectors.")); endif ## Parse optional Name-Value paired arguments optNames = {'IgnoreCase'}; dfValues = {false}; [IgnoreCase, arg] = parsePairedArguments (optNames, dfValues, ... varargin(:)); ## Check optional Name-Value paired arguments if (! (islogical (IgnoreCase) && isscalar (IgnoreCase))) error ("string.contains: 'IgnoreCase' must be a logical scalar."); elseif (! isempty (arg)) error ("string.contains: unrecognized input argument."); endif ## Check for the occurence of each pattern in the nonmissing elements of ## the input string array and boolean OR the results TF = false (size (this)); vid = ! this.isMissing; str = this.strs(vid); if (IgnoreCase) for i = 1:numel (pattern) idx = strfind (lower (str), lower (pattern{i})); TF(vid) |= ! cellfun ('isempty', idx); endfor else for i = 1:numel (pattern) idx = strfind (str, pattern{i}); TF(vid) |= ! cellfun ('isempty', idx); endfor endif endfunction ## -*- texinfo -*- ## @deftypefn {string} {@var{TF} =} endsWith (@var{str}, @var{pattern}) ## @deftypefnx {string} {@var{TF} =} endsWith (@var{str}, @var{pattern}, @qcode{'IgnoreCase'}, @qcode{true}) ## ## Test if strings end with pattern. ## ## @code{@var{TF} = endsWith (@var{str}, @var{pattern})} returns a logical ## array @var{TF} of the same size as @var{A} containing @qcode{true} for ## each corresponding element of @var{str} that ends with the specified ## @var{pattern} and @qcode{false} otherwise. Similarly to @qcode{NaN} ## values, @qcode{} elements do not match any pattern and always ## return @qcode{false}. ## ## @code{@var{TF} = endsWith (@var{str}, @var{pattern}, ## @qcode{'IgnoreCase'}, @qcode{true})} ignores case when determining if ## @var{str} ends with @var{pattern}. ## ## @end deftypefn function TF = endsWith (this, pattern, varargin) ## Check pattern if (nargin < 2) error ("string.endsWith: PATTERN is required."); elseif (ischar (pattern) || isstring (pattern)) pattern = cellstr (pattern); elseif (! iscellstr (pattern)) error (strcat ("string.endsWith: PATTERN must be a character", ... " vector, a string array, or cell array of", ... " character vectors.")); endif ## Parse optional Name-Value paired arguments optNames = {'IgnoreCase'}; dfValues = {false}; [IgnoreCase, arg] = parsePairedArguments (optNames, dfValues, ... varargin(:)); ## Check optional Name-Value paired arguments if (! (islogical (IgnoreCase) && isscalar (IgnoreCase))) error ("string.endsWith: 'IgnoreCase' must be a logical scalar."); elseif (! isempty (arg)) error ("string.endsWith: unrecognized input argument."); endif ## Reverse str and pattern str = cellfun ('flip', this.strs, "UniformOutput", false); pattern = cellfun ('flip', cellstr (pattern), "UniformOutput", false); ## For each pattern, trim all elements of the input string array to the ## length of the pattern and compare strings according to IgnoreCase TF = false (size (this)); vid = ! this.isMissing; if (IgnoreCase) for i = 1:numel (pattern) TF(vid) |= strncmpi (str(vid), pattern{i}, length (pattern{i})); endfor else for i = 1:numel (pattern) TF(vid) |= strncmp (str(vid), pattern{i}, length (pattern{i})); endfor endif endfunction ## -*- texinfo -*- ## @deftypefn {string} {@var{TF} =} startsWith (@var{str}, @var{pattern}) ## @deftypefnx {string} {@var{TF} =} startsWith (@var{str}, @var{pattern}, @qcode{'IgnoreCase'}, @qcode{true}) ## ## Test if strings start with pattern. ## ## @code{@var{TF} = startsWith (@var{str}, @var{pattern})} returns a logical ## array @var{TF} of the same size as @var{A} containing @qcode{true} for ## each corresponding element of @var{str} that starts with the specified ## @var{pattern} and @qcode{false} otherwise. Similarly to @qcode{NaN} ## values, @qcode{} elements do not match any pattern and always ## return @qcode{false}. ## ## @code{@var{TF} = startsWith (@var{str}, @var{pattern}, ## @qcode{'IgnoreCase'}, @qcode{true})} ignores case when determining if ## @var{str} starts with @var{pattern}. ## ## @end deftypefn function TF = startsWith (this, pattern, varargin) ## Check pattern if (nargin < 2) error ("string.startsWith: PATTERN is required."); elseif (ischar (pattern) || isstring (pattern)) pattern = cellstr (pattern); elseif (! iscellstr (pattern)) error (strcat ("string.startsWith: PATTERN must be a character", ... " vector, a string array, or cell array of", ... " character vectors.")); endif ## Parse optional Name-Value paired arguments optNames = {'IgnoreCase'}; dfValues = {false}; [IgnoreCase, arg] = parsePairedArguments (optNames, dfValues, ... varargin(:)); ## Check optional Name-Value paired arguments if (! (islogical (IgnoreCase) && isscalar (IgnoreCase))) error ("string.startsWith: 'IgnoreCase' must be a logical scalar."); elseif (! isempty (arg)) error ("string.startsWith: unrecognized input argument."); endif ## For each pattern, trim all elements of the input string array to the ## length of the pattern and compare strings according to IgnoreCase TF = false (size (this)); vid = ! this.isMissing; str = this.strs(vid); if (IgnoreCase) for i = 1:numel (pattern) TF(vid) |= strncmpi (str, pattern{i}, length (pattern{i})); endfor else for i = 1:numel (pattern) TF(vid) |= strncmp (str, pattern{i}, length (pattern{i})); endfor endif endfunction ## -*- texinfo -*- ## @deftypefn {string} {@var{TF} =} matches (@var{str}, @var{pattern}) ## @deftypefnx {string} {@var{TF} =} matches (@var{str}, @var{pattern}, @qcode{'IgnoreCase'}, @qcode{true}) ## ## Test if strings match pattern. ## ## @code{@var{TF} = matches (@var{str}, @var{pattern})} returns a logical ## array @var{TF} of the same size as @var{A} containing @qcode{true} for ## each corresponding element of @var{str} that matches the specified ## @var{pattern} and @qcode{false} otherwise. Similarly to @qcode{NaN} ## values, @qcode{} elements do not match any pattern and always ## return @qcode{false}. ## ## @code{@var{TF} = matches (@var{str}, @var{pattern}, @qcode{'IgnoreCase'}, ## @qcode{true})} ignores case when determining if @var{str} starts with ## @var{pattern}. ## ## @end deftypefn function TF = matches (this, pattern, varargin) ## Check pattern if (nargin < 2) error ("string.matches: PATTERN is required."); elseif (ischar (pattern) || isstring (pattern)) pattern = cellstr (pattern); elseif (! iscellstr (pattern)) error (strcat ("string.matches: PATTERN must be a character", ... " vector, a string array, or cell array of", ... " character vectors.")); endif ## Parse optional Name-Value paired arguments optNames = {'IgnoreCase'}; dfValues = {false}; [IgnoreCase, arg] = parsePairedArguments (optNames, dfValues, ... varargin(:)); ## Check optional Name-Value paired arguments if (! (islogical (IgnoreCase) && isscalar (IgnoreCase))) error ("string.matches: 'IgnoreCase' must be a logical scalar."); elseif (! isempty (arg)) error ("string.matches: unrecognized input argument."); endif ## Check for the matching of each pattern in the nonmissing elements of ## the input string array and boolean OR the results TF = false (size (this)); vid = ! this.isMissing; str = this.strs(vid); if (IgnoreCase) for i = 1:numel (pattern) TF(vid) |= strcmpi (str, pattern{i}); endfor else for i = 1:numel (pattern) TF(vid) |= strcmp (str, pattern{i}); endfor endif endfunction ## -*- texinfo -*- ## @deftypefn {string} {@var{TF} =} iscolumn (@var{str}) ## ## Test if string array is a column vector. ## ## @var{TF} is @qcode{true}, if string array @var{str} is a column vector. ## ## @end deftypefn function TF = iscolumn (this) TF = iscolumn (this.isMissing); endfunction ## -*- texinfo -*- ## @deftypefn {string} {@var{TF} =} isempty (@var{str}) ## ## Test if string array is empty. ## ## @var{TF} is @qcode{true}, if string array @var{str} is empty. ## ## @end deftypefn function TF = isempty (this) TF = isempty (this.isMissing); endfunction ## -*- texinfo -*- ## @deftypefn {string} {@var{TF} =} ismatrix (@var{str}) ## ## Test if string array is a matrix. ## ## @var{TF} is @qcode{true}, if string array @var{str} is a matrix. ## ## @end deftypefn function TF = ismatrix (this) TF = ismatrix (this.isMissing); endfunction ## -*- texinfo -*- ## @deftypefn {string} {@var{TF} =} ismember (@var{A}, @var{B}) ## @deftypefnx {string} {@var{TF} =} ismember (@var{A}, @var{B}, @qcode{'rows'}) ## @deftypefnx {string} {[@var{TF}, @var{index}] =} ismember (@dots{}) ## @deftypefnx {string} {[@var{TF}, @var{index}] =} ismember (@dots{}, @qcode{'legacy'}) ## ## Find string elements in a set. ## ## @code{@var{TF} = ismember (@var{A}, @var{B})} returns a logical array ## @var{TF} of the same size as @var{A} containing @qcode{true} for each ## corresponding element of @var{A} that is in @var{B} and @qcode{false} ## otherwise. Similarly to @qcode{NaN} values, @qcode{} elements ## are not equal with each other and always return @qcode{false}. ## ## @code{@var{TF} = ismember (@var{A}, @var{B}, @qcode{'rows'})} only ## applies to string matrices with the same number of columns, in which ## case the logical vector @var{TF} contains @qcode{true} for each row of ## @var{A} that is also a row in @var{B}. @var{TF} has the same number of ## rows as @var{A}. ## ## @code{[@var{TF}, @var{index}] = ismember (@var{A}, @var{B})} also returns ## an index array of the same size as @var{A} containing the lowest index in ## @var{B} for each element of @var{A} that is a member of @var{B} and 0 ## otherwise. If the @qcode{'rows'} optional argument is used, then the ## returning index is a column vector with the same rows as @var{A} and it ## contains the lowest index in @var{B} for each row of @var{A} that is a ## member of @var{B} and 0 otherwise. If the @qcode{'legacy'} optional ## argument is specified, then the highest index of matched elements is ## returned. Unless multiple matches exist, the @qcode{'legacy'} option has ## no effect on the returned @var{index}. ## ## @end deftypefn function varargout = ismember (A, B, varargin) if (iscategorical (B)) A = categorical (A); if (nargout > 1) [varargout{1}, varargout{2}] = ismember (A, B, varargin{:}); else varargout{1} = ismember (A, B, varargin{:}); endif return; endif if (iscellstr (A) || ischar (A)) A = string (A); elseif (iscellstr (B) || ischar (B)) B = string (B); endif if (! isstring (A)) error ("string.ismember: first input argument must be text."); endif if (! isstring (B)) error ("string.ismember: second input argument must be text."); endif if (nargin > 2 && any (strcmp (varargin, 'rows'))) if (columns (A) != columns (B)) error (strcat ("string.ismember: A and B must have the same", ... " number of columns with the 'rows' option.")); endif endif ## Handle empty input array if (isempty (A) || isempty (B)) sz = size (A); varargout{1} = false (sz); if (nargout > 1) varargout{2} = zeros (sz); endif else if (nargout > 1) [TF, index] = __ismember__ (A.strs, B.strs, varargin{:}); TF(A.isMissing) = false; index(A.isMissing) = 0; varargout{1} = TF; varargout{2} = index; else TF = __ismember__ (A.strs, B.strs, varargin{:}); TF(A.isMissing) = false; varargout{1} = TF; endif endif endfunction ## -*- texinfo -*- ## @deftypefn {string} {@var{TF} =} ismissing (@var{str}) ## @deftypefnx {string} {@var{TF} =} ismissing (@var{str}, @var{indicator}) ## ## Find missing elements in string array. ## ## @code{@var{TF} = ismissing (@var{str})} returns a logical array, ## @var{TF}, with any @qcode{true} values corresponding to missing elements ## in the input string array @var{str}. ## ## @code{@var{TF} = ismissing (@var{str}, @var{indicator})} also returns a ## logical array, @var{TF}, with any @qcode{true} values corresponding to ## elements in the input string array @var{str}, which are lexicographically ## equal to the values in @var{indicator}. ## ## @var{indicator} must be either a character vector or a string vector ## or a cell vector of character vectors. ## ## The output array @var{TF} has the same size as the input array @var{str}. ## ## @end deftypefn function TF = ismissing (this, varargin) if (nargin > 2) error ("string.ismissing: too many input arguments."); endif if (! isempty (varargin)) indicator = varargin{1}; TF = false (size (this)); if (isvector (indicator)) if (ischar (indicator)) TF(this == indicator) = true; elseif (isstring (indicator)) for idx = 1:numel (indicator) TF(this == indicator.strs(idx)) = true; endfor elseif (iscellstr (indicator)) for idx = 1:numel (indicator) TF(this == indicator(idx)) = true; endfor else error ("string.ismissing: INDICATOR must be a text array."); endif else error ("string.ismissing: INDICATOR must be a vector."); endif else TF = this.isMissing; endif endfunction ## -*- texinfo -*- ## @deftypefn {string} {@var{TF} =} isrow (@var{str}) ## ## Test if string array is a row vector. ## ## @var{TF} is @qcode{true}, if string array @var{str} is a row vector. ## ## @end deftypefn function TF = isrow (this) TF = isrow (this.isMissing); endfunction ## -*- texinfo -*- ## @deftypefn {string} {@var{TF} =} isscalar (@var{str}) ## ## Test if string array is a scalar. ## ## @var{TF} is @qcode{true}, if string array @var{str} is a scalar. ## ## @end deftypefn function TF = isscalar (this) TF = isscalar (this.isMissing); endfunction ## -*- texinfo -*- ## @deftypefn {string} {@var{TF} =} issorted (@var{str}) ## @deftypefnx {string} {@var{TF} =} issorted (@var{str}, @var{dim}) ## @deftypefnx {string} {@var{TF} =} issorted (@var{str}, @var{direction}) ## @deftypefnx {string} {@var{TF} =} issorted (@var{str}, @var{dim}, @var{direction}) ## @deftypefnx {string} {@var{TF} =} issorted (@dots{}, @qcode{'MissingPlacement'}, @var{MP}) ## ## Return true if string array is sorted. ## ## @code{@var{TF} = issorted (@var{str})} returns a logical scalar @var{TF}, ## which is @qcode{true}, if the string array @var{str} is sorted in ## ascending order, and @qcode{false} otherwise. ## ## @code{@var{TF} = issorted (@var{str}, @var{dim})} returns a logical ## scalar @var{TF}, which is @qcode{true}, if the string array @var{str} ## is sorted in ascending order along the dimension @var{dim}, and ## @qcode{false} otherwise. ## ## @code{@var{TF} = issorted (@var{str}, @var{direction})} returns a logical ## scalar @var{TF}, which is @qcode{true}, if the string array @var{str} is ## sorted in the direction specified by @var{direction}, and @qcode{false} ## otherwise. @var{direction} can be any of the following options: ## ## @itemize ## @item @qcode{'ascend'}, which is the default, checks if elements are in ## ascending order. ## @item @qcode{'descend'} checks if elements are in descending order. ## @item @qcode{'monotonic'} checks if elements are either in ascending or ## descending order. ## @item @qcode{'strictascend'} checks if elements are in ascending order ## and there are no duplicate or missing elements. ## @item @qcode{'strictdescend'} checks if elements are in descending order ## and there are no duplicate or missing elements. ## @item @qcode{'strictmonotonic'} checks if elements are either in ## ascending or descending order and there are no duplicate or missing ## elements. ## @end itemize ## ## @code{@var{TF} = issorted (@dots{}, @qcode{'MissingPlacement'}, ## @var{MP})} specifies where missing elements (@qcode{}) are ## placed with one of the following options specified in @var{MP}: ## ## @itemize ## @item @qcode{'auto'}, which is the default, places missing elements last ## for ascending sort and first for descending sort. ## @item @qcode{'first'} places missing elements first. ## @item @qcode{'last'} places missing elements last. ## @end itemize ## ## @end deftypefn function TF = issorted (this, varargin) ## Single input argument if (nargin == 1) TF = isequaln (this, sort (this)); return; endif ## Get operating dimension cid = cellfun (@isnumeric, varargin); if (any (cid)) dim = varargin{cid}; else sz = size (this); dim = find (sz != 1, 1); if (isempty (dim)) # scalar dim = 1; endif endif ## Parse and validate optional 'MissingPlacement' paired argument optNames = {'MissingPlacement'}; dfValues = {'auto'}; [MP, args] = parsePairedArguments (optNames, dfValues, varargin(:)); if (! ismember (MP, {'auto', 'first', 'last'})) error ("string.issorted: invalid value for 'MissingPlacement'."); endif ## Force strings to character vectors [args{:}] = convertStringsToChars (args{:}); ## Get direction cid = cellfun (@(x) ischar (x), args); if (any (cid)) direction = args{cid}; ## Check for type of direction valid_direction = {'ascend', 'descend', 'monotonic', 'strictascend', ... 'strictdescend', 'strictmonotonic'}; if (! ismember (direction, valid_direction)) error ("string.issorted: invalid DIRECTION value."); endif switch (direction) case {'ascend', 'descend'} TF = isequaln (this, sort (this, args{:}, 'MissingPlacement', MP)); case {'strictascend', 'strictdescend'} ## Check for missing values first (fast) if (any (this.isMissing, 'all')) TF = false; return; endif args{cid} = strrep (direction, 'strict', ''); sorted = sort (this, args{:}, 'MissingPlacement', MP); ## Detect duplicate adjacent elements through a numeric rank proxy ## (unique ranks preserve order); equal strings share a rank. [~, ~, ic] = unique (sorted.strs); ranks = reshape (ic, size (sorted)); if (any (diff (ranks, 1, dim) == 0, 'all')) TF = false; return; endif TF = isequal (this, sorted); case 'monotonic' ## Check for either ascending or descending args{cid} = 'ascend'; TF = isequaln (this, sort (this, args{:}, 'MissingPlacement', MP)); if (TF) return; endif args{cid} = 'descend'; TF = isequaln (this, sort (this, args{:}, 'MissingPlacement', MP)); case 'strictmonotonic' ## Check missing values first (fast) if (any (this.isMissing, 'all')) TF = false; return; endif ## Check for either ascending or descending args{cid} = 'ascend'; sorted = sort (this, args{:}, 'MissingPlacement', MP); [~, ~, ic] = unique (sorted.strs); ranks = reshape (ic, size (sorted)); if (any (diff (ranks, 1, dim) == 0, 'all')) TF = false; return; endif TF = isequal (this, sorted); if (TF) return; endif args{cid} = 'descend'; sorted = sort (this, args{:}, 'MissingPlacement', MP); [~, ~, ic] = unique (sorted.strs); ranks = reshape (ic, size (sorted)); if (any (diff (ranks, 1, dim) == 0, 'all')) TF = false; return; endif TF = isequal (this, sorted); endswitch else ## No DIRECTION input argument TF = isequaln (this, sort (this, args{:}, 'MissingPlacement', MP)); endif endfunction ## -*- texinfo -*- ## @deftypefn {string} {@var{TF} =} issortedrows (@var{str}) ## @deftypefnx {string} {@var{TF} =} issortedrows (@var{str}, @var{col}) ## @deftypefnx {string} {@var{TF} =} issortedrows (@var{str}, @var{direction}) ## @deftypefnx {string} {@var{TF} =} issortedrows (@var{str}, @var{col}, @var{direction}) ## @deftypefnx {string} {@var{TF} =} issortedrows (@dots{}, @qcode{'MissingPlacement'}, @var{MP}) ## ## Return true if string matrix rows are sorted. ## ## @code{@var{TF} = issortedrows (@var{str})} returns a logical scalar ## @var{TF}, which is @qcode{true}, if the rows in the 2-D string array ## @var{str} are sorted in ascending order, and @qcode{false} otherwise. ## ## @code{@var{TF} = issortedrows (@var{str}, @var{col})} returns a logical ## scalar @var{TF}, which is @qcode{true}, if the string array @var{str} is ## sorted according to the columns specified by the vector @var{col}, and ## @qcode{false} otherwise. @var{col} must explicitly contain non-zero ## integers whose absolute values index existing columns in @var{str}. ## Positive elements sort the corresponding columns in ascending order, ## while negative elements sort the corresponding columns in descending ## order. ## ## @code{@var{TF} = issortedrows (@var{str}, @var{direction})} checks if the ## rows in @var{str} are sorted according to the specified direction, which ## can be one of the following options: ## ## @itemize ## @item @qcode{'ascend'}, which is the default, checks if elements are in ## ascending order. ## @item @qcode{'descend'} checks if elements are in descending order. ## @item @qcode{'monotonic'} checks if elements are either in ascending or ## descending order. ## @item @qcode{'strictascend'} checks if elements are in ascending order ## and there are no duplicate or missing elements. ## @item @qcode{'strictdescend'} checks if elements are in descending order ## and there are no duplicate or missing elements. ## @item @qcode{'strictmonotonic'} checks if elements are either in ## ascending or descending order and there are no duplicate or missing ## elements. ## @end itemize ## ## Alternatively, @var{direction} can be a cell array of character vectors ## specifying the sorting direction for each individual column of @var{str}, ## in which case the number of elements in @var{direction} must equal the ## number of columns in @var{str}. ## ## @code{@var{TF} = issortedrows (@var{str}, @var{col}, @var{direction})} ## checks if the rows in the string array @var{str} are sorted according to ## the columns specified in @var{col} using the corresponding sorting ## direction specified in @var{direction}. In this case, the sign of the ## values in @var{col} is ignored. @var{col} and @var{direction} must have ## the same length, but not necessarily the same number of elements as the ## columns in @var{str}. ## ## @code{@var{TF} = issortedrows (@dots{}, @qcode{'MissingPlacement'}, ## @var{MP})} specifies where missing elements (@qcode{}) are ## placed with one of the following options specified in @var{MP}: ## ## @itemize ## @item @qcode{'auto'}, which is the default, places missing elements last ## for ascending sort and first for descending sort. ## @item @qcode{'first'} places missing elements first. ## @item @qcode{'last'} places missing elements last. ## @end itemize ## ## @end deftypefn function TF = issortedrows (this, varargin) ## Single input argument if (nargin == 1) TF = isequaln (this, sortrows (this)); return; endif ## Force strings to character vectors or cell arrays of character vectors [varargin{:}] = convertStringsToChars (varargin{:}); ## Get valid direction(s) from input argument list valid = {'ascend', 'descend', 'monotonic', 'strictascend', ... 'strictdescend', 'strictmonotonic'}; fcn = @(x) (ischar (x) && ismember (x, valid)) || iscellstr (x); cid = cellfun (fcn, varargin); if (! any (cid)) ## No DIRECTION input argument TF = isequaln (this, sortrows (this, varargin{:})); return; endif direction = cellstr (varargin{cid}); ## Check for valid type of directions in cellstring if (! all (cellfun (@(x) ismember (x, valid), direction))) error ("string.issortedrows: invalid DIRECTION value."); endif ## Handle purely non-strict directions (ascend/descend) if (all (cellfun (@(x) ismember (x, {'ascend', 'descend'}), direction))) TF = isequaln (this, sortrows (this, varargin{:})); return; endif ## ... and the simple set including 'monotonic' but no strict modes simple_types = {'ascend', 'descend', 'monotonic'}; if (all (cellfun (@(x) ismember (x, simple_types), direction))) idx = strcmp (direction, 'monotonic'); direction(idx) = {'ascend'}; varargin{cid} = direction; TF = isequaln (this, sortrows (this, varargin{:})); if (TF) return; endif direction(idx) = {'descend'}; varargin{cid} = direction; TF = isequaln (this, sortrows (this, varargin{:})); return; endif ## Handle strict modes. Determine the sort-key column order and the ## sort position holding the first strict direction, then derive the ## prefix (the columns sorted up to and including that position, on ## which the duplicate test operates) and the binding strict column ## (the single column on which the missing test operates). A missing ## value in the binding column, or two consecutive sorted rows equal ## across the prefix columns, rules out a strict ordering; strict ## positions after the first are subsumed by it. strict_types = {'strictascend', 'strictdescend', 'strictmonotonic'}; sflag = cellfun (@(x) ismember (x, strict_types), direction); nc = size (this, 2); nummask = cellfun (@isnumeric, varargin); if (any (nummask)) col = varargin{nummask}; ocols = abs (col(:)'); if (isscalar (direction)) sflag = repmat (sflag, 1, numel (col)); elseif (numel (direction) != numel (col)) error ("string.issortedrows: COL and DIRECTION mismatch."); endif else ocols = 1:nc; if (isscalar (direction)) sflag = repmat (sflag, 1, nc); endif endif K = find (sflag, 1); prefix = ocols(1:K); bindcol = ocols(K); ## A missing value in the binding strict column rules out strictness. if (any (this.isMissing(:,bindcol), 'all')) TF = false; return; endif ## Replace strict modes with their plain counterparts for the sort. direction = strrep (direction, 'strict', ''); varargin{cid} = direction; if (all (cellfun (@(x) ismember (x, {'ascend', 'descend'}), direction))) sorted = sortrows (this, varargin{:}); ## No two consecutive rows may tie across the prefix columns; a numeric ## rank proxy (unique ranks) gives equal strings an equal rank. [~, ~, ic] = unique (sorted.strs); ranks = reshape (ic, size (sorted)); tmpcol = ranks(:, prefix); if (any (all (diff (tmpcol, 1, 1) == 0, 2))) TF = false; return; endif TF = isequaln (this, sorted); else # a 'monotonic' position also exists idx = strcmp (direction, 'monotonic'); direction(idx) = {'ascend'}; varargin{cid} = direction; sorted = sortrows (this, varargin{:}); [~, ~, ic] = unique (sorted.strs); ranks = reshape (ic, size (sorted)); tmpcol = ranks(:, prefix); if (any (all (diff (tmpcol, 1, 1) == 0, 2))) TF = false; return; endif TF = isequaln (this, sorted); if (TF) return; endif direction(idx) = {'descend'}; varargin{cid} = direction; sorted = sortrows (this, varargin{:}); [~, ~, ic] = unique (sorted.strs); ranks = reshape (ic, size (sorted)); tmpcol = ranks(:, prefix); if (any (all (diff (tmpcol, 1, 1) == 0, 2))) TF = false; return; endif TF = isequaln (this, sorted); endif endfunction ## -*- texinfo -*- ## @deftypefn {string} {@var{TF} =} isstring (@var{str}) ## ## Test for string array. ## ## @var{TF} is @qcode{true} for @code{string} inputs. ## ## @end deftypefn function TF = isstring (this) TF = true; endfunction ## -*- texinfo -*- ## @deftypefn {string} {@var{TF} =} isvector (@var{str}) ## ## Test if string array is a vector. ## ## @var{TF} is @qcode{true}, if string array @var{str} is a vector. ## ## @end deftypefn function TF = isvector (this) TF = isvector (this.isMissing); endfunction endmethods ################################################################################ ## ** Relational Operations ** ## ################################################################################ ## Available Methods ## ## ## ## 'eq' 'ge' 'gt' 'le' ## ## 'lt' 'ne' 'strcmp' 'strcmpi' ## ## 'strncmp' 'strncmpi' ## ## ## ################################################################################ methods (Access = public) ## -*- texinfo -*- ## @deftypefn {string} {@var{TF} =} eq (@var{A}, @var{B}) ## ## Test for equality. ## ## @var{TF} is @qcode{true}, if string @var{A} is lexicographically equal to ## @var{B}. If one input is a string array, the other input can be a string ## array, a character vector, or a cell array of character vectors. This is ## equivalent to the @code{strcmp} function. ## ## @end deftypefn function TF = eq (A, B) ## Overload methods for certain data types if (any (isa (B, {'categorical', 'duration'}))) TF = eq (cellstr (A), B); return; endif if (iscellstr (A) || ischar (A)) A = string (A); elseif (iscellstr (B) || ischar (B)) B = string (B); elseif (! isa (A, 'string') || ! isa (B, 'string')) error (strcat ("string.eq: comparison between '%s' and '%s'", ... " is not supported."), class (A), class (B)); endif szA = size (A); szB = size (B); if (isscalar (A)) A = repmat (A, szB); elseif (isscalar (B)) B = repmat (B, szA); elseif (! isequal (szA, szB)) ## Force size dim vectors to equal length ndA = numel (szA); ndB = numel (szB); if (ndA > ndB) szB = [szB, ones(1, ndA - ndB)]; elseif (ndB > ndA) szA = [szA, ones(1, ndB - ndA)]; endif ## Check for compatible dimensions A_1 = szA != 1 & szA != szB; B_1 = szB != 1 & szA != szB; A_B = A_1 & B_1; if (any (A_B)) error ("string.eq: incompatible dimensions."); endif ## Expand arrays to same size eqAB = szA == szB; szA(eqAB) = 1; szB(eqAB) = 1; A = repmat (A, szB); B = repmat (B, szA); endif TF = strcmp (A.strs, B.strs); TF(A.isMissing | B.isMissing) = false; endfunction ## -*- texinfo -*- ## @deftypefn {string} {@var{TF} =} ge (@var{A}, @var{B}) ## ## Test for greater than or equal to. ## ## @var{TF} is @qcode{true}, if string @var{A} is lexicographically greater ## than or equal to @var{B}. If one input is a string array, the other ## input can be a string array, a character vector, or a cell array of ## character vectors. ## ## @end deftypefn function TF = ge (A, B) ## Overload methods for certain data types if (any (isa (B, {'categorical', 'duration'}))) TF = ge (cellstr (A), B); return; endif if (iscellstr (A) || ischar (A)) A = string (A); elseif (iscellstr (B) || ischar (B)) B = string (B); elseif (! isa (A, 'string') || ! isa (B, 'string')) error (strcat ("string.ge: comparison between '%s' and '%s'", ... " is not supported."), class (A), class (B)); endif szA = size (A); szB = size (B); if (isscalar (A)) A = repmat (A, szB); elseif (isscalar (B)) B = repmat (B, szA); elseif (! isequal (szA, szB)) ## Force size dim vectors to equal length ndA = numel (szA); ndB = numel (szB); if (ndA > ndB) szB = [szB, ones(1, ndA - ndB)]; elseif (ndB > ndA) szA = [szA, ones(1, ndB - ndA)]; endif ## Check for compatible dimensions A_1 = szA != 1 & szA != szB; B_1 = szB != 1 & szA != szB; A_B = A_1 & B_1; if (any (A_B)) error ("string.ge: incompatible dimensions."); endif ## Expand arrays to same size eqAB = szA == szB; szA(eqAB) = 1; szB(eqAB) = 1; A = repmat (A, szB); B = repmat (B, szA); endif out = sign_strings (A.strs, B.strs); TF = false (size (out)); TF(out >= 0) = true; TF(A.isMissing | B.isMissing) = false; endfunction ## -*- texinfo -*- ## @deftypefn {string} {@var{TF} =} gt (@var{A}, @var{B}) ## ## Test for greater than. ## ## @var{TF} is @qcode{true}, if string @var{A} is lexicographically greater ## than @var{B}. If one input is a string array, the other input can be a ## string array, a character vector, or a cell array of character vectors. ## ## @end deftypefn function TF = gt (A, B) ## Overload methods for certain data types if (any (isa (B, {'categorical', 'duration'}))) TF = gt (cellstr (A), B); return; endif if (iscellstr (A) || ischar (A)) A = string (A); elseif (iscellstr (B) || ischar (B)) B = string (B); elseif (! isa (A, 'string') || ! isa (B, 'string')) error (strcat ("string.gt: comparison between '%s' and '%s'", ... " is not supported."), class (A), class (B)); endif szA = size (A); szB = size (B); if (isscalar (A)) A = repmat (A, szB); elseif (isscalar (B)) B = repmat (B, szA); elseif (! isequal (szA, szB)) ## Force size dim vectors to equal length ndA = numel (szA); ndB = numel (szB); if (ndA > ndB) szB = [szB, ones(1, ndA - ndB)]; elseif (ndB > ndA) szA = [szA, ones(1, ndB - ndA)]; endif ## Check for compatible dimensions A_1 = szA != 1 & szA != szB; B_1 = szB != 1 & szA != szB; A_B = A_1 & B_1; if (any (A_B)) error ("string.gt: incompatible dimensions."); endif ## Expand arrays to same size eqAB = szA == szB; szA(eqAB) = 1; szB(eqAB) = 1; A = repmat (A, szB); B = repmat (B, szA); endif out = sign_strings (A.strs, B.strs); TF = false (size (out)); TF(out > 0) = true; TF(A.isMissing | B.isMissing) = false; endfunction ## -*- texinfo -*- ## @deftypefn {string} {@var{TF} =} le (@var{A}, @var{B}) ## ## Test for less than or equal to. ## ## @var{TF} is @qcode{true}, if string @var{A} is lexicographically less ## than or equal to @var{B}. If one input is a string array, the other ## input can be a string array, a character vector, or a cell array of ## character vectors. ## ## @end deftypefn function TF = le (A, B) ## Overload methods for certain data types if (any (isa (B, {'categorical', 'duration'}))) TF = le (cellstr (A), B); return; endif if (iscellstr (A) || ischar (A)) A = string (A); elseif (iscellstr (B) || ischar (B)) B = string (B); elseif (! isa (A, 'string') || ! isa (B, 'string')) error (strcat ("string.le: comparison between '%s' and '%s'", ... " is not supported."), class (A), class (B)); endif szA = size (A); szB = size (B); if (isscalar (A)) A = repmat (A, szB); elseif (isscalar (B)) B = repmat (B, szA); elseif (! isequal (szA, szB)) ## Force size dim vectors to equal length ndA = numel (szA); ndB = numel (szB); if (ndA > ndB) szB = [szB, ones(1, ndA - ndB)]; elseif (ndB > ndA) szA = [szA, ones(1, ndB - ndA)]; endif ## Check for compatible dimensions A_1 = szA != 1 & szA != szB; B_1 = szB != 1 & szA != szB; A_B = A_1 & B_1; if (any (A_B)) error ("string.le: incompatible dimensions."); endif ## Expand arrays to same size eqAB = szA == szB; szA(eqAB) = 1; szB(eqAB) = 1; A = repmat (A, szB); B = repmat (B, szA); endif out = sign_strings (A.strs, B.strs); TF = false (size (out)); TF(out <= 0) = true; TF(A.isMissing | B.isMissing) = false; endfunction ## -*- texinfo -*- ## @deftypefn {string} {@var{TF} =} lt (@var{A}, @var{B}) ## ## Test for less than. ## ## @var{TF} is @qcode{true}, if string @var{A} is lexicographically less ## than @var{B}. If one input is a string array, the other input can be a ## string array, a character vector, or a cell array of character vectors. ## ## @end deftypefn function TF = lt (A, B) ## Overload methods for certain data types if (any (isa (B, {'categorical', 'duration'}))) TF = lt (cellstr (A), B); return; endif if (iscellstr (A) || ischar (A)) A = string (A); elseif (iscellstr (B) || ischar (B)) B = string (B); elseif (! isa (A, 'string') || ! isa (B, 'string')) error (strcat ("string.lt: comparison between '%s' and '%s'", ... " is not supported."), class (A), class (B)); endif szA = size (A); szB = size (B); if (isscalar (A)) A = repmat (A, szB); elseif (isscalar (B)) B = repmat (B, szA); elseif (! isequal (szA, szB)) ## Force size dim vectors to equal length ndA = numel (szA); ndB = numel (szB); if (ndA > ndB) szB = [szB, ones(1, ndA - ndB)]; elseif (ndB > ndA) szA = [szA, ones(1, ndB - ndA)]; endif ## Check for compatible dimensions A_1 = szA != 1 & szA != szB; B_1 = szB != 1 & szA != szB; A_B = A_1 & B_1; if (any (A_B)) error ("string.lt: incompatible dimensions."); endif ## Expand arrays to same size eqAB = szA == szB; szA(eqAB) = 1; szB(eqAB) = 1; A = repmat (A, szB); B = repmat (B, szA); endif out = sign_strings (A.strs, B.strs); TF = false (size (out)); TF(out < 0) = true; TF(A.isMissing | B.isMissing) = false; endfunction ## -*- texinfo -*- ## @deftypefn {string} {@var{TF} =} ne (@var{A}, @var{B}) ## ## Test for inequality. ## ## @var{TF} is @qcode{true}, if string @var{A} is lexicographically not ## equal to @var{B}. If one input is a string array, the other input can be ## a string array, a character vector, or a cell array of character vectors. ## @code{@var{TF} = ne (@var{A}, @var{B})} is equivalent to ## @code{@var{TF} = ! strcmp (@var{A}, @var{B})}. ## ## @end deftypefn function TF = ne (A, B) ## Overload methods for certain data types if (any (isa (B, {'categorical', 'duration'}))) TF = ne (cellstr (A), B); return; endif if (iscellstr (A) || ischar (A)) A = string (A); elseif (iscellstr (B) || ischar (B)) B = string (B); elseif (! isa (A, 'string') || ! isa (B, 'string')) error (strcat ("string.ne: comparison between '%s' and '%s'", ... " is not supported."), class (A), class (B)); endif szA = size (A); szB = size (B); if (isscalar (A)) A = repmat (A, szB); elseif (isscalar (B)) B = repmat (B, szA); elseif (! isequal (szA, szB)) ## Force size dim vectors to equal length ndA = numel (szA); ndB = numel (szB); if (ndA > ndB) szB = [szB, ones(1, ndA - ndB)]; elseif (ndB > ndA) szA = [szA, ones(1, ndB - ndA)]; endif ## Check for compatible dimensions A_1 = szA != 1 & szA != szB; B_1 = szB != 1 & szA != szB; A_B = A_1 & B_1; if (any (A_B)) error ("string.ne: incompatible dimensions."); endif ## Expand arrays to same size eqAB = szA == szB; szA(eqAB) = 1; szB(eqAB) = 1; A = repmat (A, szB); B = repmat (B, szA); endif TF = ! strcmp (A.strs, B.strs); TF(A.isMissing | B.isMissing) = true; endfunction ## -*- texinfo -*- ## @deftypefn {string} {@var{TF} =} strcmp (@var{A}, @var{B}) ## ## Compare strings. ## ## @var{TF} is @qcode{true}, if string @var{A} is lexicographically equal to ## @var{B}. If one input is a string array, the other input can be a string ## array, a character vector, or a cell array of character vectors. ## ## If either @var{A} or @var{B} is a string array or a cell array of ## character vectors, then a logical array @var{TF} of the same size is ## returned, containing the values described above for every member of the ## array. In this case, the other argument may also be a string array or a ## cell array of character vectors (of the same size or scalar), or a ## character vector. ## ## @end deftypefn function TF = strcmp (A, B) if (iscellstr (A) || ischar (A)) A = string (A); elseif (iscellstr (B) || ischar (B)) B = string (B); elseif (! isa (A, 'string') || ! isa (B, 'string')) error (strcat ("string.strcmp: comparison between '%s' and '%s'", ... " is not supported."), class (A), class (B)); endif if (isscalar (A)) A = repmat (A, size (B)); elseif (isscalar (B)) B = repmat (B, size (A)); elseif (! isequal (size (A), size (B))) error ("string.strcmp: inconsistent dimensions."); endif TF = strcmp (A.strs, B.strs); TF(A.isMissing | B.isMissing) = false; endfunction ## -*- texinfo -*- ## @deftypefn {string} {@var{TF} =} strcmpi (@var{A}, @var{B}) ## ## Compare strings (case insensitive). ## ## @var{TF} is @qcode{true}, if string @var{A} is lexicographically equal to ## @var{B}, disregarding case of alphabetic characters. If one input is a ## string array, the other input can be a string array, a character vector, ## or a cell array of character vectors. ## ## If either @var{A} or @var{B} is a string array or a cell array of ## character vectors, then a logical array @var{TF} of the same size is ## returned, containing the values described above for every member of the ## array. In this case, the other argument may also be a string array or a ## cell array of character vectors (of the same size or scalar), or a ## character vector. ## ## @end deftypefn function TF = strcmpi (A, B) if (iscellstr (A) || ischar (A)) A = string (A); elseif (iscellstr (B) || ischar (B)) B = string (B); elseif (! isa (A, 'string') || ! isa (B, 'string')) error (strcat ("string.strcmpi: comparison between '%s' and '%s'", ... " is not supported."), class (A), class (B)); endif if (isscalar (A)) A = repmat (A, size (B)); elseif (isscalar (B)) B = repmat (B, size (A)); elseif (! isequal (size (A), size (B))) error ("string.strcmpi: inconsistent dimensions."); endif TF = strcmpi (A.strs, B.strs); TF(A.isMissing | B.isMissing) = false; endfunction ## -*- texinfo -*- ## @deftypefn {string} {@var{TF} =} strncmp (@var{A}, @var{B}, @var{n}) ## ## Compare first @var{n} characters of strings. ## ## @var{TF} is @qcode{true}, if the first @var{n} characters of strings ## @var{A} and @var{B} are lexicographically equal. If one input is a ## string array, the other input can be a string array, a character vector, ## or a cell array of character vectors. ## ## If either @var{A} or @var{B} is a string array or a cell array of ## character vectors, then a logical array @var{TF} of the same size is ## returned, containing the values described above for every member of the ## array. In this case, the other argument may also be a string array or a ## cell array of character vectors (of the same size or scalar), or a ## character vector. ## ## @end deftypefn function TF = strncmp (A, B, n) if (iscellstr (A) || ischar (A)) A = string (A); elseif (iscellstr (B) || ischar (B)) B = string (B); elseif (! isa (A, 'string') || ! isa (B, 'string')) error (strcat ("string.strncmp: comparison between '%s' and '%s'", ... " is not supported."), class (A), class (B)); endif if (isscalar (A)) A = repmat (A, size (B)); elseif (isscalar (B)) B = repmat (B, size (A)); elseif (! isequal (size (A), size (B))) error ("string.strncmp: inconsistent dimensions."); endif TF = strncmp (A.strs, B.strs, n); TF(A.isMissing | B.isMissing) = false; endfunction ## -*- texinfo -*- ## @deftypefn {string} {@var{TF} =} strncmpi (@var{A}, @var{B}, @var{n}) ## ## Compare first @var{n} characters of strings (case insensitive). ## ## @var{TF} is @qcode{true}, if the first @var{n} characters of strings ## @var{A} and @var{B} are lexicographically equal, disregarding case of ## alphabetic characters. If one input is a string array, the other input ## can be a string array, a character vector, or a cell array of character ## vectors. ## ## If either @var{A} or @var{B} is a string array or a cell array of ## character vectors, then a logical array @var{TF} of the same size is ## returned, containing the values described above for every member of the ## array. In this case, the other argument may also be a string array or a ## cell array of character vectors (of the same size or scalar), or a ## character vector. ## ## @end deftypefn function TF = strncmpi (A, B, n) if (iscellstr (A) || ischar (A)) A = string (A); elseif (iscellstr (B) || ischar (B)) B = string (B); elseif (! isa (A, 'string') || ! isa (B, 'string')) error (strcat ("string.strncmpi: comparison between '%s' and '%s'", ... " is not supported."), class (A), class (B)); endif if (isscalar (A)) A = repmat (A, size (B)); elseif (isscalar (B)) B = repmat (B, size (A)); elseif (! isequal (size (A), size (B))) error ("string.strncmpi: inconsistent dimensions."); endif TF = strncmpi (A.strs, B.strs, n); TF(A.isMissing | B.isMissing) = false; endfunction endmethods ################################################################################ ## ** String Operations ** ## ################################################################################ ## Available Methods ## ## ## ## 'append' 'compose' 'erase' 'eraseBetween' ## ## 'extract' 'extractAfter' 'extractBefore' 'extractBetween' ## ## 'insertAfter' 'insertBefore' 'replace' 'replaceBetween' ## ## 'reverse' 'sort' 'sortrows' 'split' ## ## 'splitlines' 'strcat' 'strip' 'pad' ## ## 'join' 'plus' 'lower' 'upper' ## ## 'unique' ## ## ## ################################################################################ methods (Access = public) ## -*- texinfo -*- ## @deftypefn {string} {@var{newstr} =} append (@var{str1}, @dots{}, @var{strN}) ## ## Combine string arrays. ## ## @code{@var{newstr} = append (@var{str1}, @dots{}, @var{strN})} combines ## the text from each input argument, @var{str1}, @dots{}, @var{strN}), ## which must be either string arrays, cell arrays of character vectors, or ## character vectors or matrices. All input arguments must be of compatible ## sizes. Character vectors are treated as a single text element and ## character matrices are treated as a column of elements. @code{append} ## preserves any trailing white spaces, unlike the @code{strcat} function. ## ## @end deftypefn function out = append (varargin) ## Check input for valid types fcn = @(x) iscellstr (x) || ischar (x) || isa (x, 'string'); dtypes = cellfun (fcn, varargin); if (! all (dtypes)) error (strcat ("string.append: input arguments must be string", ... " arrays, cell arrays of character vectors, or", ... " character matrices.")); endif ## Convert all inputs to string arrays ctypes = cellfun (@(x) ! isa (x, 'string'), varargin); if (any (ctypes)) varargin(ctypes) = cellfun (@(x) string (char (x)), ... varargin(ctypes), "UniformOutput", false); endif ## A single argument is returned unchanged out = varargin{1}; if (numel (varargin) == 1) return; endif ## Handle compatible dimensions. An element is missing whenever any input ## contributes a missing value at that position, since a missing string ## propagates like NaN through concatenation. in_sz = cellfun (@size, varargin, "UniformOutput", false); strArgs = cellfun (@(x) x.strs, varargin, 'UniformOutput', false); ismArgs = cellfun (@(x) x.isMissing, varargin, 'UniformOutput', false); if (isequal (in_sz{:})) out.strs = strcat (strArgs{:}); out.isMissing = or (ismArgs{:}); else try out.isMissing = or (ismArgs{:}); szo = size (out.isMissing); catch error ("string.append: inputs have incompatible sizes."); end_try_catch for n = 1:numel (strArgs) strArg = strArgs{n}; newsz = szo ./ size (strArg); strArgs{n} = repmat (strArg, newsz); endfor out.strs = strcat (strArgs{:}); endif ## Missing elements carry no text, as elsewhere in the class out.strs(out.isMissing) = {''}; endfunction ## -*- texinfo -*- ## @deftypefn {string} {@var{str} =} compose (@var{formatSpec}, @var{A}) ## @deftypefnx {string} {@var{str} =} compose (@var{formatSpec}, @var{A1}, @dots{}, @var{AN}) ## @deftypefnx {string} {@var{str} =} compose (@var{txt}) ## ## Format data into a string array or translate escape-character sequences. ## ## @code{@var{str} = compose (@var{formatSpec}, @var{A})} formats the data ## in the array @var{A} according to the formatting operators in ## @var{formatSpec}, which must be a string scalar or character vector, and ## returns the result in the string array @var{str}. The formatting ## operators are the same as those accepted by the @code{sprintf} function. ## Unlike @code{sprintf}, which returns a single character vector, ## @code{compose} returns a string array whose elements correspond to the ## rows of @var{A}. ## ## @code{compose} applies @var{formatSpec} to each row of @var{A} so that ## @var{str} has the same number of rows as @var{A}. The size of @var{str} ## is further determined as follows: ## ## @itemize ## @item If the number of columns in @var{A} exceeds the number of ## formatting operators in @var{formatSpec}, then @var{formatSpec} is ## applied repeatedly along each row of @var{A}, adding columns to ## @var{str}. ## @item If the number of columns in @var{A} is less than the number of ## formatting operators, then the operators left without a corresponding ## value appear unchanged in @var{str}. ## @item If @var{A} has zero columns, then @var{str} has the same size as ## @var{A} and no formatting operators are applied. ## @end itemize ## ## @code{@var{str} = compose (@var{formatSpec}, @var{A1}, @dots{}, ## @var{AN})} formats the data from the arrays @var{A1}, @dots{}, @var{AN}. ## The formatting operators are assigned to the input arrays in order: once ## an operator has consumed a value from an input array, it becomes ## unavailable to the following arrays. All input arrays must be of ## compatible sizes. ## ## @code{@var{str} = compose (@var{txt})} translates escape-character ## sequences, such as @qcode{'\n'} and @qcode{'\t'}, in @var{txt} and ## returns the result in @var{str}, which has the same size as @var{txt}. ## Any formatting operators in @var{txt} are left unchanged. ## ## In all syntaxes, escape-character sequences appearing in literal text are ## translated and each @qcode{'%%'} literal is converted to a single ## @qcode{'%'} character, following the same rules as the @code{sprintf} ## function. The only difference from @code{sprintf} is that a formatting ## operator left without a corresponding value is emitted unchanged rather ## than dropped. ## ## @end deftypefn function out = compose (this, varargin) ## Escape-sequence translation syntax: compose (TXT) if (nargin == 1) out = this; cstr = this.strs; for k = 1:numel (cstr) if (this.isMissing(k)) continue; endif cstr{k} = compose_apply (compose_tokenize (cstr{k}), {}); endfor out.strs = cstr; return; endif ## Formatting syntax: compose (FORMATSPEC, A1, ..., AN) if (! isscalar (this)) error (strcat ("string.compose: FORMATSPEC must be a", ... " string scalar or a character vector.")); endif if (this.isMissing) error ("string.compose: FORMATSPEC cannot be a missing value."); endif tok = compose_tokenize (this.strs{1}); ## Count the values consumed by one full pass of FORMATSPEC vpa = 0; for t = 1:numel (tok) if (strcmp (tok{t}.type, 'op')) vpa += tok{t}.nval; endif endfor ## Normalize each input array to a 2-D cell matrix of values N = numel (varargin); C = cell (1, N); nrows = ones (1, N); for n = 1:N A = varargin{n}; if (ischar (A)) Cn = cellstr (A); elseif (isa (A, 'string')) Cn = cellstr (A); elseif (isnumeric (A) || islogical (A)) Cn = num2cell (A); else error ("string.compose: unsupported input type: '%s'.", class (A)); endif Cn = reshape (Cn, size (Cn, 1), []); C{n} = Cn; nrows(n) = size (Cn, 1); endfor ## Determine the common number of rows (singletons expand) R = max (nrows); for n = 1:N rn = size (C{n}, 1); if (rn == R) continue; elseif (rn == 1) C{n} = repmat (C{n}, R, 1); else error ("string.compose: input arrays must be of compatible sizes."); endif endfor ## Combine all values per row, in column order across input arrays vals = [C{:}]; V = columns (vals); ## With zero data columns no formatting is applied (size preserved) if (V == 0) out = string (cell (R, 0)); return; endif ## Number of times FORMATSPEC is applied per row if (vpa == 0) nApp = 1; else nApp = max (1, ceil (V / vpa)); endif ## Apply FORMATSPEC, consuming VPA values at a time outc = cell (R, nApp); for r = 1:R for a = 1:nApp lo = (a - 1) * vpa + 1; if (vpa == 0 || lo > V) slice = {}; else slice = vals(r, lo:min (a * vpa, V)); endif outc{r,a} = compose_apply (tok, slice); endfor endfor out = string (outc); endfunction ## -*- texinfo -*- ## @deftypefn {string} {@var{newstr} =} erase (@var{str}, @var{match}) ## ## Remove content from string array. ## ## @code{@var{newstr} = erase (@var{str}, @var{match})} removes the ## occurrences of @var{match} from each element of the string array ## @var{str}. @var{match} can be a string array, a character vector, or a ## cell array of character vectors. When @var{match} contains more than one ## piece of text, every occurrence of every element of @var{match} is ## removed. @var{newstr} is a string array of the same size as @var{str}; ## the size of @var{match} need not match the size of @var{str}. Missing ## values in @var{str} are preserved. ## ## @end deftypefn function out = erase (this, match) if (isa (match, 'string')) pats = cellstr (match); elseif (ischar (match) || iscellstr (match)) ## Route char/cellstr through the constructor, which keeps trailing ## whitespace that bare 'cellstr' would deblank pats = cellstr (string (match)); else error (strcat ("string.erase: MATCH must be a string array, a", ... " character vector, or a cell array of character", ... " vectors.")); endif out = this; for i = 1:numel (pats) out.strs = strrep (out.strs, pats{i}, ''); endfor endfunction ## -*- texinfo -*- ## @deftypefn {string} {@var{newstr} =} eraseBetween (@var{str}, @var{startPat}, @var{endPat}) ## @deftypefnx {string} {@var{newstr} =} eraseBetween (@var{str}, @var{startPos}, @var{endPos}) ## @deftypefnx {string} {@var{newstr} =} eraseBetween (@dots{}, @qcode{"Boundaries"}, @var{bounds}) ## ## Erase content between start and end boundaries. ## ## @code{@var{newstr} = eraseBetween (@var{str}, @var{startPat}, ## @var{endPat})} removes from each element of the string array @var{str} ## the text that occurs between the substrings @var{startPat} and ## @var{endPat}, keeping the boundary substrings themselves. @var{startPat} ## and @var{endPat} can be string arrays, character vectors, or cell arrays ## of character vectors. For each element, the first occurrence of ## @var{startPat} is matched and then the first occurrence of @var{endPat} ## that begins after it; if either boundary is not found, the element is ## returned unchanged. ## ## @code{@var{newstr} = eraseBetween (@var{str}, @var{startPos}, ## @var{endPos})} removes the text between the character positions ## @var{startPos} and @var{endPos}, inclusive of the characters at those ## positions. @var{startPos} and @var{endPos} must be positive integers ## with @var{startPos} not exceeding @var{endPos} and both within the ## length of the corresponding element of @var{str}. ## ## @code{@var{newstr} = eraseBetween (@dots{}, @qcode{"Boundaries"}, ## @var{bounds})} specifies whether the boundaries are included in or ## excluded from the erased text. @var{bounds} can be either ## @qcode{"inclusive"} or @qcode{"exclusive"}. When boundaries are given ## as substrings, the default is @qcode{"exclusive"} and the boundary ## substrings are preserved; when given as positions, the default is ## @qcode{"inclusive"} and the characters at those positions are erased. ## ## @var{startPat}/@var{endPat} and @var{startPos}/@var{endPos} must either ## be scalars, applied to every element of @var{str}, or be of the same size ## as @var{str} and applied element-wise. @var{newstr} is a string array of ## the same size as @var{str}. Missing values in @var{str} are preserved. ## ## @end deftypefn function out = eraseBetween (this, start, stop, varargin) if (nargin < 3) error ("string.eraseBetween: not enough input arguments."); endif ## Position boundaries are numeric, pattern boundaries are text; the two ## modes cannot be mixed and have different default 'Boundaries' values. istxt = @(x) ischar (x) || iscellstr (x) || isa (x, 'string'); if (isnumeric (start) && isnumeric (stop)) posMode = true; dfBounds = 'inclusive'; elseif (istxt (start) && istxt (stop)) posMode = false; dfBounds = 'exclusive'; else error (strcat ("string.eraseBetween: START and STOP must be either", ... " both numeric positions or both text patterns.")); endif ## Parse the optional 'Boundaries' Name/Value pair [bounds, rem] = parsePairedArguments ({'Boundaries'}, {dfBounds}, ... varargin); if (! isempty (rem)) error ("string.eraseBetween: invalid optional arguments."); endif if (isa (bounds, 'string')) bounds = char (bounds); endif if (! ischar (bounds) || ... ! any (strcmpi (bounds, {'inclusive', 'exclusive'}))) error (strcat ("string.eraseBetween: BOUNDARIES must be", ... " 'inclusive' or 'exclusive'.")); endif inclusive = strcmpi (bounds, 'inclusive'); ## Normalize text boundaries to cell arrays of character vectors. Route ## char/cellstr through the constructor, which keeps trailing whitespace ## that bare 'cellstr' would deblank. if (! posMode) if (isa (start, 'string')) start = cellstr (start); else start = cellstr (string (start)); endif if (isa (stop, 'string')) stop = cellstr (stop); else stop = cellstr (string (stop)); endif endif ## Broadcast scalar boundaries; otherwise sizes must match STR sz = size (this.strs); [start, errmsg] = eb_expand (start, sz, 'START'); if (! isempty (errmsg)) error ("string.eraseBetween: %s", errmsg); endif [stop, errmsg] = eb_expand (stop, sz, 'STOP'); if (! isempty (errmsg)) error ("string.eraseBetween: %s", errmsg); endif out = this; cstr = this.strs; for k = 1:numel (cstr) if (this.isMissing(k)) continue; endif if (posMode) [cstr{k}, errmsg] = eb_between (cstr{k}, start(k), stop(k), true, ... inclusive); else [cstr{k}, errmsg] = eb_between (cstr{k}, start{k}, stop{k}, false, ... inclusive); endif if (! isempty (errmsg)) error ("string.eraseBetween: %s", errmsg); endif endfor out.strs = cstr; endfunction ## -*- texinfo -*- ## @deftypefn {string} {@var{newstr} =} extract (@var{str}, @var{pat}) ## @deftypefnx {string} {@var{newstr} =} extract (@var{str}, @var{pos}) ## ## Extract substrings from a string array. ## ## @code{@var{newstr} = extract (@var{str}, @var{pat})} returns the ## substrings of @var{str} that match @var{pat}. @var{pat} can be a string ## array, a character vector, or a cell array of character vectors; when it ## contains more than one piece of text, any of them may match. Within each ## element of @var{str} the matches are found left to right and do not ## overlap; where alternatives match at the same position, the first listed ## in @var{pat} is taken. ## ## The matches of an element occupy a row of @var{newstr}, so the matches ## run along the second dimension and every element of @var{str} must yield ## the same number of matches. For a string scalar with @var{n} matches, ## @var{newstr} is @code{1x@var{n}}; for a non-scalar @var{str}, ## @var{newstr} has one row per element (taken in column-major order) and ## one column per match. Elements with no match, including missing values, ## are treated as having zero matches. ## ## @code{@var{newstr} = extract (@var{str}, @var{pos})} returns the single ## character located at position @var{pos} in each element of @var{str}. ## @var{pos} must be a positive integer that is either a scalar, applied to ## every element, or the same size as @var{str}, applied element-wise. In ## this syntax @var{newstr} has the same size as @var{str} and missing ## values are preserved. ## ## @end deftypefn function out = extract (this, pat) if (nargin < 2) error ("string.extract: not enough input arguments."); endif ## Numeric POS selects a single character per element; text PAT extracts ## substring matches. if (isnumeric (pat)) pos = pat; sz = size (this.strs); if (isscalar (pos)) pos = repmat (pos, sz); elseif (! isequal (size (pos), sz)) error ("string.extract: POS must be scalar or the same size as STR."); endif if (any (pos(:) != fix (pos(:))) || any (pos(:) < 1)) error ("string.extract: POS must be a positive integer."); endif out = this; cstr = this.strs; for k = 1:numel (cstr) if (this.isMissing(k)) continue; endif cp = str2cp (cstr{k}); if (pos(k) > numel (cp)) error ("string.extract: POS exceeds the length of the string."); endif cstr{k} = cp2str (cp(pos(k))); endfor out.strs = cstr; return; endif ## Text pattern form if (isa (pat, 'string')) pats = cellstr (pat); elseif (ischar (pat) || iscellstr (pat)) ## Route char/cellstr through the constructor, which keeps trailing ## whitespace that bare 'cellstr' would deblank pats = cellstr (string (pat)); else error (strcat ("string.extract: PAT must be a string array, a", ... " character vector, or a cell array of character", ... " vectors.")); endif ## Collect the matches of each element (missing elements yield none) ne = numel (this.strs); matches = cell (ne, 1); counts = zeros (ne, 1); for k = 1:ne if (this.isMissing(k)) matches{k} = {}; else matches{k} = extract_matches (this.strs{k}, pats); endif counts(k) = numel (matches{k}); endfor ## Matches run along the columns, so every element must match the same ## number of times to form a rectangular string array. if (ne > 0 && any (counts != counts(1))) error (strcat ("string.extract: all elements of STR must have the", ... " same number of matches.")); endif ncol = 0; if (ne > 0) ncol = counts(1); endif outc = cell (ne, ncol); for e = 1:ne outc(e,:) = matches{e}; endfor out = string (outc); endfunction ## -*- texinfo -*- ## @deftypefn {string} {@var{newstr} =} extractAfter (@var{str}, @var{pat}) ## @deftypefnx {string} {@var{newstr} =} extractAfter (@var{str}, @var{pos}) ## ## Extract the substring after a position or pattern. ## ## @code{@var{newstr} = extractAfter (@var{str}, @var{pat})} returns, for ## each element of @var{str}, the part of the text that follows the first ## occurrence of @var{pat}, excluding @var{pat} itself. @var{pat} can be a ## string array, a character vector, or a cell array of character vectors, ## and must either be a scalar, applied to every element of @var{str}, or ## be of the same size as @var{str} and applied element-wise. If @var{pat} ## is not found in an element, the corresponding element of @var{newstr} is ## a missing value. ## ## @code{@var{newstr} = extractAfter (@var{str}, @var{pos})} returns the ## part of each element of @var{str} that follows the character position ## @var{pos}, that is, from @code{@var{pos}+1} to the end. @var{pos} must ## be a positive integer that is either a scalar or the same size as ## @var{str}. ## ## @var{newstr} is a string array of the same size as @var{str}. Missing ## values in @var{str} are preserved. ## ## @end deftypefn function out = extractAfter (this, pat) if (nargin < 2) error ("string.extractAfter: not enough input arguments."); endif [out, errmsg] = extract_side (this, pat, true); if (! isempty (errmsg)) error ("string.extractAfter: %s", errmsg); endif endfunction ## -*- texinfo -*- ## @deftypefn {string} {@var{newstr} =} extractBefore (@var{str}, @var{pat}) ## @deftypefnx {string} {@var{newstr} =} extractBefore (@var{str}, @var{pos}) ## ## Extract the substring before a position or pattern. ## ## @code{@var{newstr} = extractBefore (@var{str}, @var{pat})} returns, for ## each element of @var{str}, the part of the text that precedes the first ## occurrence of @var{pat}, excluding @var{pat} itself. @var{pat} can be a ## string array, a character vector, or a cell array of character vectors, ## and must either be a scalar, applied to every element of @var{str}, or ## be of the same size as @var{str} and applied element-wise. If @var{pat} ## is not found in an element, the corresponding element of @var{newstr} is ## a missing value. ## ## @code{@var{newstr} = extractBefore (@var{str}, @var{pos})} returns the ## part of each element of @var{str} that precedes the character position ## @var{pos}, that is, from the start up to @code{@var{pos}-1}. @var{pos} ## must be a positive integer that is either a scalar or the same size as ## @var{str}. ## ## @var{newstr} is a string array of the same size as @var{str}. Missing ## values in @var{str} are preserved. ## ## @end deftypefn function out = extractBefore (this, pat) if (nargin < 2) error ("string.extractBefore: not enough input arguments."); endif [out, errmsg] = extract_side (this, pat, false); if (! isempty (errmsg)) error ("string.extractBefore: %s", errmsg); endif endfunction ## -*- texinfo -*- ## @deftypefn {string} {@var{newstr} =} extractBetween (@var{str}, @var{startPat}, @var{endPat}) ## @deftypefnx {string} {@var{newstr} =} extractBetween (@var{str}, @var{startPos}, @var{endPos}) ## @deftypefnx {string} {@var{newstr} =} extractBetween (@dots{}, @qcode{"Boundaries"}, @var{bounds}) ## ## Extract the substrings between start and end boundaries. ## ## @code{@var{newstr} = extractBetween (@var{str}, @var{startPat}, ## @var{endPat})} returns the text that occurs between the substrings ## @var{startPat} and @var{endPat} in each element of @var{str}. ## @var{startPat} and @var{endPat} can be string arrays, character vectors, ## or cell arrays of character vectors. Within each element the boundary ## pairs are matched from left to right and do not overlap, so an element ## may yield several substrings; these run along the second dimension, and ## every element of @var{str} must yield the same number of matches. For a ## string scalar with @var{n} matches, @var{newstr} is @code{1x@var{n}}; ## for a non-scalar @var{str}, @var{newstr} has one row per element (taken ## in column-major order) and one column per match. Elements with no match, ## including missing values, are treated as having zero matches. ## ## @code{@var{newstr} = extractBetween (@var{str}, @var{startPos}, ## @var{endPos})} returns the substring between the character positions ## @var{startPos} and @var{endPos}, inclusive of the characters at those ## positions. This syntax extracts a single substring per element, so ## @var{newstr} has the same size as @var{str}. ## ## @code{@var{newstr} = extractBetween (@dots{}, @qcode{"Boundaries"}, ## @var{bounds})} specifies whether the boundaries are included in or ## excluded from the extracted text. @var{bounds} can be either ## @qcode{"inclusive"} or @qcode{"exclusive"}. When boundaries are given ## as substrings, the default is @qcode{"exclusive"} and the boundary ## substrings are not included; when given as positions, the default is ## @qcode{"inclusive"} and the characters at those positions are included. ## ## @var{startPat}/@var{endPat} and @var{startPos}/@var{endPos} must either ## be scalars, applied to every element of @var{str}, or be of the same size ## as @var{str}. Missing values in @var{str} are preserved. ## ## @end deftypefn function out = extractBetween (this, start, stop, varargin) if (nargin < 3) error ("string.extractBetween: not enough input arguments."); endif istxt = @(x) ischar (x) || iscellstr (x) || isa (x, 'string'); if (isnumeric (start) && isnumeric (stop)) posMode = true; dfBounds = 'inclusive'; elseif (istxt (start) && istxt (stop)) posMode = false; dfBounds = 'exclusive'; else error (strcat ("string.extractBetween: START and STOP must be", ... " either both numeric positions or both text", ... " patterns.")); endif [bounds, rem] = parsePairedArguments ({'Boundaries'}, {dfBounds}, ... varargin); if (! isempty (rem)) error ("string.extractBetween: invalid optional arguments."); endif if (isa (bounds, 'string')) bounds = char (bounds); endif if (! ischar (bounds) || ... ! any (strcmpi (bounds, {'inclusive', 'exclusive'}))) error (strcat ("string.extractBetween: BOUNDARIES must be", ... " 'inclusive' or 'exclusive'.")); endif inclusive = strcmpi (bounds, 'inclusive'); sz = size (this.strs); ne = numel (this.strs); if (posMode) [start, errmsg] = eb_expand (start, sz, 'START'); if (! isempty (errmsg)) error ("string.extractBetween: %s", errmsg); endif [stop, errmsg] = eb_expand (stop, sz, 'STOP'); if (! isempty (errmsg)) error ("string.extractBetween: %s", errmsg); endif ## One span per element, so the output matches the size of STR out = this; cstr = this.strs; for k = 1:ne if (this.isMissing(k)) continue; endif [cstr{k}, errmsg] = eb_span (cstr{k}, start(k), stop(k), inclusive); if (! isempty (errmsg)) error ("string.extractBetween: %s", errmsg); endif endfor out.strs = cstr; return; endif ## Pattern mode: each element may contribute several substrings. Route ## char/cellstr through the constructor, which keeps trailing whitespace ## that bare 'cellstr' would deblank. if (isa (start, 'string')) start = cellstr (start); else start = cellstr (string (start)); endif if (isa (stop, 'string')) stop = cellstr (stop); else stop = cellstr (string (stop)); endif [start, errmsg] = eb_expand (start, sz, 'START'); if (! isempty (errmsg)) error ("string.extractBetween: %s", errmsg); endif [stop, errmsg] = eb_expand (stop, sz, 'STOP'); if (! isempty (errmsg)) error ("string.extractBetween: %s", errmsg); endif matches = cell (ne, 1); counts = zeros (ne, 1); for k = 1:ne if (this.isMissing(k)) matches{k} = {}; else matches{k} = extract_between_matches (this.strs{k}, start{k}, ... stop{k}, inclusive); endif counts(k) = numel (matches{k}); endfor if (ne > 0 && any (counts != counts(1))) error (strcat ("string.extractBetween: all elements of STR must", ... " have the same number of matches.")); endif ncol = 0; if (ne > 0) ncol = counts(1); endif outc = cell (ne, ncol); for e = 1:ne outc(e,:) = matches{e}; endfor out = string (outc); endfunction ## -*- texinfo -*- ## @deftypefn {string} {@var{newstr} =} insertAfter (@var{str}, @var{pat}, @var{newtext}) ## @deftypefnx {string} {@var{newstr} =} insertAfter (@var{str}, @var{pos}, @var{newtext}) ## ## Insert text after a pattern or position. ## ## @code{@var{newstr} = insertAfter (@var{str}, @var{pat}, @var{newtext})} ## inserts the text @var{newtext} into each element of @var{str} after ## every non-overlapping occurrence of the substring @var{pat}. @var{pat} ## can be a string array, a character vector, or a cell array of character ## vectors, and must either be a scalar, applied to every element of ## @var{str}, or be of the same size as @var{str} and applied ## element-wise. If @var{pat} is not found in an element, that element is ## returned unchanged. ## ## @code{@var{newstr} = insertAfter (@var{str}, @var{pos}, @var{newtext})} ## inserts @var{newtext} after the character position @var{pos}, that is, ## between the characters at positions @var{pos} and @code{@var{pos}+1}. ## @var{pos} must be a positive integer not exceeding the length of the ## corresponding element of @var{str}, and must be either a scalar or the ## same size as @var{str}. ## ## @var{newtext} can be a string array, a character vector, or a cell array ## of character vectors, and must be either a scalar, inserted at every ## position, or of the same size as @var{str}. @var{newstr} is a string ## array of the same size as @var{str}. Missing values in @var{str} are ## preserved, and a missing value in @var{newtext} makes the corresponding ## element of @var{newstr} missing. ## ## @end deftypefn function out = insertAfter (this, pat, new) if (nargin < 3) error ("string.insertAfter: not enough input arguments."); endif [out, errmsg] = insert_side (this, pat, new, true); if (! isempty (errmsg)) error ("string.insertAfter: %s", errmsg); endif endfunction ## -*- texinfo -*- ## @deftypefn {string} {@var{newstr} =} insertBefore (@var{str}, @var{pat}, @var{newtext}) ## @deftypefnx {string} {@var{newstr} =} insertBefore (@var{str}, @var{pos}, @var{newtext}) ## ## Insert text before a pattern or position. ## ## @code{@var{newstr} = insertBefore (@var{str}, @var{pat}, @var{newtext})} ## inserts the text @var{newtext} into each element of @var{str} before ## every non-overlapping occurrence of the substring @var{pat}. @var{pat} ## can be a string array, a character vector, or a cell array of character ## vectors, and must either be a scalar, applied to every element of ## @var{str}, or be of the same size as @var{str} and applied ## element-wise. If @var{pat} is not found in an element, that element is ## returned unchanged. ## ## @code{@var{newstr} = insertBefore (@var{str}, @var{pos}, @var{newtext})} ## inserts @var{newtext} before the character position @var{pos}, that is, ## between the characters at positions @code{@var{pos}-1} and @var{pos}. ## @var{pos} must be a positive integer not exceeding the length of the ## corresponding element of @var{str}, and must be either a scalar or the ## same size as @var{str}. ## ## @var{newtext} can be a string array, a character vector, or a cell array ## of character vectors, and must be either a scalar, inserted at every ## position, or of the same size as @var{str}. @var{newstr} is a string ## array of the same size as @var{str}. Missing values in @var{str} are ## preserved, and a missing value in @var{newtext} makes the corresponding ## element of @var{newstr} missing. ## ## @end deftypefn function out = insertBefore (this, pat, new) if (nargin < 3) error ("string.insertBefore: not enough input arguments."); endif [out, errmsg] = insert_side (this, pat, new, false); if (! isempty (errmsg)) error ("string.insertBefore: %s", errmsg); endif endfunction ## -*- texinfo -*- ## @deftypefn {string} {@var{newstr} =} replace (@var{str}, @var{old}, @var{new}) ## ## Replace substrings in string array. ## ## @code{@var{newstr} = replace (@var{str}, @var{old}, @var{new})} replaces ## every occurrence of the substring @var{old} with @var{new} in each ## element of the string array @var{str}. @var{old} and @var{new} can be ## string arrays, character vectors, or cell arrays of character vectors. ## ## When @var{old} contains several substrings, @var{new} must either be a ## single substring, used to replace all of them, or be of the same size as ## @var{old}, replacing each substring of @var{old} with the corresponding ## element of @var{new}. All substrings are replaced in a single left to ## right pass over each element: at each position the substrings of ## @var{old} are tried in order, the first that matches is replaced, and ## the scan resumes past the inserted text, so replacements are not ## themselves re-scanned. @var{old} and @var{new} are applied to every ## element of @var{str}; their size need not match the size of @var{str}. ## ## @var{newstr} is a string array of the same size as @var{str}. A ## substring of @var{old} that does not occur leaves the text unchanged, and ## missing values in @var{str} are preserved. ## ## @end deftypefn function out = replace (this, old, new) if (nargin < 3) error ("string.replace: not enough input arguments."); endif ## Normalize OLD and NEW to cell arrays of character vectors. Route ## char/cellstr through the constructor, which keeps trailing whitespace ## that bare 'cellstr' would deblank. if (isa (old, 'string')) olds = cellstr (old); elseif (ischar (old) || iscellstr (old)) olds = cellstr (string (old)); else error (strcat ("string.replace: OLD must be a string array, a", ... " character vector, or a cell array of character", ... " vectors.")); endif if (isa (new, 'string')) news = cellstr (new); elseif (ischar (new) || iscellstr (new)) news = cellstr (string (new)); else error (strcat ("string.replace: NEW must be a string array, a", ... " character vector, or a cell array of character", ... " vectors.")); endif ## NEW must be a single substring or pair up with the OLD substrings if (numel (news) == 1) news = repmat (news, size (olds)); elseif (numel (news) != numel (olds)) error (strcat ("string.replace: NEW must be a single substring or", ... " the same size as OLD.")); endif out = this; if (numel (olds) == 1) ## Single pattern: a vectorised strrep replaces every occurrence and ## leaves missing elements (stored as '') untouched out.strs = strrep (this.strs, olds{1}, news{1}); else ## Multiple patterns are applied simultaneously, in one pass per element cstr = this.strs; for k = 1:numel (cstr) if (this.isMissing(k)) continue; endif cstr{k} = replace_pairs (cstr{k}, olds, news); endfor out.strs = cstr; endif endfunction ## -*- texinfo -*- ## @deftypefn {string} {@var{newstr} =} replaceBetween (@var{str}, @var{startPat}, @var{endPat}, @var{newtext}) ## @deftypefnx {string} {@var{newstr} =} replaceBetween (@var{str}, @var{startPos}, @var{endPos}, @var{newtext}) ## @deftypefnx {string} {@var{newstr} =} replaceBetween (@dots{}, @qcode{"Boundaries"}, @var{bounds}) ## ## Replace content between start and end boundaries. ## ## @code{@var{newstr} = replaceBetween (@var{str}, @var{startPat}, ## @var{endPat}, @var{newtext})} replaces, in each element of the string ## array @var{str}, the text that occurs between the substrings ## @var{startPat} and @var{endPat} with @var{newtext}, keeping the boundary ## substrings themselves. @var{startPat} and @var{endPat} can be string ## arrays, character vectors, or cell arrays of character vectors. For each ## element, the first occurrence of @var{startPat} is matched and then the ## first occurrence of @var{endPat} that begins after it; if either boundary ## is not found, the element is returned unchanged. ## ## @code{@var{newstr} = replaceBetween (@var{str}, @var{startPos}, ## @var{endPos}, @var{newtext})} replaces the text between the character ## positions @var{startPos} and @var{endPos}, inclusive of the characters at ## those positions, with @var{newtext}. @var{startPos} and @var{endPos} ## must be positive integers with @var{startPos} not exceeding @var{endPos} ## and both within the length of the corresponding element of @var{str}. ## ## @code{@var{newstr} = replaceBetween (@dots{}, @qcode{"Boundaries"}, ## @var{bounds})} specifies whether the boundaries are included in or ## excluded from the replaced text. @var{bounds} can be either ## @qcode{"inclusive"} or @qcode{"exclusive"}. When boundaries are given ## as substrings, the default is @qcode{"exclusive"} and the boundary ## substrings are preserved; when given as positions, the default is ## @qcode{"inclusive"} and the characters at those positions are replaced. ## ## @var{startPat}/@var{endPat} and @var{startPos}/@var{endPos} must either ## be scalars, applied to every element of @var{str}, or be of the same size ## as @var{str} and applied element-wise. @var{newtext} can be a string ## array, a character vector, or a cell array of character vectors, and must ## be either a scalar or the same size as @var{str}. @var{newstr} is a ## string array of the same size as @var{str}. Missing values in @var{str} ## are preserved, and a missing value in @var{newtext} makes the ## corresponding element of @var{newstr} missing. ## ## @end deftypefn function out = replaceBetween (this, start, stop, new, varargin) if (nargin < 4) error ("string.replaceBetween: not enough input arguments."); endif ## Position boundaries are numeric, pattern boundaries are text; the two ## modes cannot be mixed and have different default 'Boundaries' values. istxt = @(x) ischar (x) || iscellstr (x) || isa (x, 'string'); if (isnumeric (start) && isnumeric (stop)) posMode = true; dfBounds = 'inclusive'; elseif (istxt (start) && istxt (stop)) posMode = false; dfBounds = 'exclusive'; else error (strcat ("string.replaceBetween: START and STOP must be", ... " either both numeric positions or both text", ... " patterns.")); endif ## Parse the optional 'Boundaries' Name/Value pair [bounds, rem] = parsePairedArguments ({'Boundaries'}, {dfBounds}, ... varargin); if (! isempty (rem)) error ("string.replaceBetween: invalid optional arguments."); endif if (isa (bounds, 'string')) bounds = char (bounds); endif if (! ischar (bounds) || ... ! any (strcmpi (bounds, {'inclusive', 'exclusive'}))) error (strcat ("string.replaceBetween: BOUNDARIES must be", ... " 'inclusive' or 'exclusive'.")); endif inclusive = strcmpi (bounds, 'inclusive'); ## Normalize text boundaries to cell arrays of character vectors. Route ## char/cellstr through the constructor, which keeps trailing whitespace ## that bare 'cellstr' would deblank. if (! posMode) if (isa (start, 'string')) start = cellstr (start); else start = cellstr (string (start)); endif if (isa (stop, 'string')) stop = cellstr (stop); else stop = cellstr (string (stop)); endif endif ## Broadcast scalar boundaries and the replacement; otherwise match STR sz = size (this.strs); [start, errmsg] = eb_expand (start, sz, 'START'); if (! isempty (errmsg)) error ("string.replaceBetween: %s", errmsg); endif [stop, errmsg] = eb_expand (stop, sz, 'STOP'); if (! isempty (errmsg)) error ("string.replaceBetween: %s", errmsg); endif [newc, newMiss, errmsg] = norm_new (new, sz); if (! isempty (errmsg)) error ("string.replaceBetween: %s", errmsg); endif out = this; cstr = this.strs; isMiss = this.isMissing; for k = 1:numel (cstr) if (isMiss(k)) continue; endif if (newMiss(k)) cstr{k} = ''; isMiss(k) = true; continue; endif if (posMode) [cstr{k}, errmsg] = rb_between (cstr{k}, start(k), stop(k), true, ... inclusive, newc{k}); else [cstr{k}, errmsg] = rb_between (cstr{k}, start{k}, stop{k}, false, ... inclusive, newc{k}); endif if (! isempty (errmsg)) error ("string.replaceBetween: %s", errmsg); endif endfor out.strs = cstr; out.isMissing = isMiss; endfunction ## -*- texinfo -*- ## @deftypefn {string} {@var{newstr} =} reverse (@var{str}) ## ## Reverse order of characters in string array. ## ## @code{@var{newstr} = reverse (@var{str})} reverses the order of the ## characters in every each element of the string array @var{str}. ## @var{newstr} is a string array of the same size as @var{str}. ## ## @end deftypefn function out = reverse (this) fu2n = @(x) typecast (unicode2native (x, 'UTF-32LE'), 'uint32'); fn2u = @(x) native2unicode (typecast (x, 'uint8'), 'UTF-32LE'); frev = @(x) x(end:-1:1); notempty = ! cellfun (@isempty, this.strs); out = this; code = cellfun (fu2n, this.strs(notempty), "UniformOutput", false); code = cellfun (frev, code, "UniformOutput", false); out.strs(notempty) = cellfun (fn2u, code, "UniformOutput", false); out.isMissing = this.isMissing; endfunction ## -*- texinfo -*- ## @deftypefn {string} {@var{B} =} sort (@var{A}) ## @deftypefnx {string} {@var{B} =} sort (@var{A}, @var{dim}) ## @deftypefnx {string} {@var{B} =} sort (@var{A}, @var{direction}) ## @deftypefnx {string} {@var{B} =} sort (@var{A}, @var{dim}, @var{direction}) ## @deftypefnx {string} {@var{B} =} sort (@dots{}, @qcode{'MissingPlacement'}, @var{MP}) ## @deftypefnx {string} {[@var{B}, @var{index}] =} sort (@var{A}, @dots{}) ## ## Sort elements in a string array. ## ## @code{@var{B} = sort (@var{A})} sorts the elements of the string array ## @var{A} in ascending order. Elements are compared lexicographically by ## their Unicode code points, with the empty string @qcode{""} sorting ## before any non-empty string. If @var{A} is a matrix, ## @code{sort (@var{A})} sorts each column of @var{A} in ascending order. ## For multidimensional arrays, @code{sort (@var{A})} sorts along the first ## non-singleton dimension. ## ## @code{@var{B} = sort (@var{A}, @var{dim})} sorts along the dimension ## specified by @var{dim}. ## ## @code{@var{B} = sort (@var{A}, @var{direction})} also specifies the ## sorting direction, which can be either @qcode{'ascend'} (default) or ## @qcode{'descend'}. ## ## @code{@var{B} = sort (@dots{}, @qcode{'MissingPlacement'}, @var{MP})} ## specifies where to place the missing elements (@qcode{}) ## returned in @var{B} with any of the following options specified in ## @var{MP}: ## ## @itemize ## @item @qcode{'auto'}, which is the default, places missing elements last ## for ascending sort and first for descending sort. ## @item @qcode{'first'} places missing elements first. ## @item @qcode{'last'} places missing elements last. ## @end itemize ## ## @code{[@var{B}, @var{index}] = sort (@var{A}, @dots{})} also returns a ## sorting index containing the original indices of the elements in the ## sorted array. ## ## @itemize ## @item If @var{A} is a vector, then @var{index} contains the original ## linear indices of the elements in the sorted vector @var{B} such that ## @code{@var{B} = @var{A}(@var{index})}. ## @item If @var{A} is an @math{M*N} matrix and @qcode{@var{dim} = 1}, then ## @var{index} contains the original row indices of the elements in the ## sorted vector @var{B} such that for @qcode{j = 1:N}, ## @code{@var{B}(:,j) = @var{A}(@var{index}(:,j),j)}. ## @end itemize ## ## @end deftypefn function [B, index] = sort (A, varargin) ## Parse and validate optional 'MissingPlacement' paired argument optNames = {'MissingPlacement'}; dfValues = {'auto'}; [MP, args] = parsePairedArguments (optNames, dfValues, varargin(:)); if (! ismember (MP, {'auto', 'first', 'last'})) error ("string.sort: invalid value for 'MissingPlacement'."); endif ## Force strings to character vectors [args{:}] = convertStringsToChars (args{:}); ## Get direction cid = cellfun (@ischar, args); if (any (cid)) dir = args{cid}; else dir = 'ascend'; endif ## Get operating dimension szA = size (A); cid = cellfun (@isnumeric, args); if (any (cid)) dim = args{cid}; else dim = find (szA != 1, 1); if (isempty (dim)) # scalar dim = 1; endif endif ## Encode the text as numeric ranks that preserve lexicographic order, so ## that the missing elements (stored internally as '') can be told apart ## from genuine empty strings and placed according to 'MissingPlacement'. ## Missing elements are flagged as NaN, mirroring the numeric-backed ## classes, which lets the workaround below reuse the same logic. [~, ~, ic] = unique (A.strs); data = reshape (ic, szA); data(A.isMissing) = NaN; ## Special handling for missing elements when missing placement overrides ## default behavior (only if missing data actually exist). is_nan = isnan (data); if (any (is_nan, 'all')) ## FIX ME: this workaround will be removed once the 'sort' function ## in core Octave supports the 'MissingPlacement' optional argument. ## The default core behavior already matches 'auto' (missing last for ## ascending, first for descending), so only the overriding cases are ## handled here. Ranks are positive integers, so -Inf never collides. if ((strcmp (dir, 'ascend') && strcmp (MP, 'first')) || (strcmp (dir, 'descend') && strcmp (MP, 'last'))) ## Convert missing values to -Inf so that they are placed first ## (ascending) or last (descending), overriding the default. data(is_nan) = -Inf; endif endif ## Sort the numeric proxy [~, index] = sort (data, args{:}); ## Calculate linear index n_dims = ndims (A); dimarg = cell (1, n_dims); for i = 1:n_dims if (i == dim) dimarg{i} = index; else dim_sz = szA(i); tmpvec = ones (1, n_dims); tmpvec(i) = dim_sz; tmp_sz = szA; tmp_sz(i) = 1; dimarg{i} = repmat (reshape ([1:dim_sz], tmpvec), tmp_sz); endif endfor ## Return sorted string array B = subset (A, sub2ind (szA, dimarg{:})); endfunction ## -*- texinfo -*- ## @deftypefn {string} {@var{B} =} sortrows (@var{A}) ## @deftypefnx {string} {@var{B} =} sortrows (@var{A}, @var{col}) ## @deftypefnx {string} {@var{B} =} sortrows (@var{A}, @var{direction}) ## @deftypefnx {string} {@var{B} =} sortrows (@var{A}, @var{col}, @var{direction}) ## @deftypefnx {string} {@var{B} =} sortrows (@dots{}, @qcode{'MissingPlacement'}, @var{MP}) ## @deftypefnx {string} {[@var{B}, @var{index}] =} sortrows (@var{A}, @dots{}) ## ## Sort rows in a string array. ## ## @code{@var{B} = sortrows (@var{A})} sorts the rows of the 2-D string ## array @var{A} in ascending order. The sorted array @var{B} has the same ## size as @var{A}. ## ## @code{@var{B} = sortrows (@var{A}, @var{col})} sorts @var{A} according to ## the columns specified by the numeric vector @var{col}, which must ## explicitly contain non-zero integers whose absolute values index existing ## columns in @var{A}. Positive elements sort the corresponding columns in ## ascending order, while negative elements sort the corresponding columns ## in descending order. ## ## @code{@var{B} = sortrows (@var{A}, @var{direction})} also specifies the ## sorting direction, which can be either @qcode{'ascend'} (default) or ## @qcode{'descend'} applying to all columns in @var{A}. Alternatively, ## @var{direction} can be either a string array or a cell array of character ## vectors specifying the sorting direction for each individual column of ## @var{A}, in which case the number of elements in @var{direction} must ## equal the number of columns in @var{A}. ## ## @code{@var{B} = sortrows (@var{A}, @var{col}, @var{direction})} sorts the ## string array @var{A} according to the columns specified in @var{col} ## using the corresponding sorting direction specified in @var{direction}. ## In this case, the sign of the values in @var{col} is ignored. @var{col} ## and @var{direction} must have the same number of elements, but not ## necessarily equal to the columns of @var{A}. ## ## @code{@var{B} = sortrows (@dots{}, @qcode{'MissingPlacement'}, @var{MP})} ## specifies where to place the missing elements (@qcode{}) ## returned in @var{B} with any of the following options specified in ## @var{MP}: ## ## @itemize ## @item @qcode{'auto'}, which is the default, places missing elements last ## for ascending sort and first for descending sort. ## @item @qcode{'first'} places missing elements first. ## @item @qcode{'last'} places missing elements last. ## @end itemize ## ## @code{[@var{B}, @var{index}] = sortrows (@var{A}, @dots{})} also returns ## an index vector containing the original row indices of @var{A} in the ## sorted matrix @var{B} such that @code{@var{B} = @var{A}(@var{index},:)}. ## ## @end deftypefn function [B, index] = sortrows (A, varargin) ## Input array must be a matrix if (ndims (A) != 2) error ("string.sortrows: A must be a 2-D matrix."); endif ## Parse and validate optional 'MissingPlacement' paired argument optNames = {'MissingPlacement'}; dfValues = {'auto'}; [MP, args] = parsePairedArguments (optNames, dfValues, varargin(:)); if (! any (strcmp (MP, {'auto', 'first', 'last'}))) error ("string.sortrows: invalid value for 'MissingPlacement'."); endif ## Parse COL / DIRECTION input nc = size (A, 2); col = [1:nc]; # default ascending direction dir_flag = false; if (numel (args) > 2) error ("string.sortrows: too many input arguments."); endif if (numel (args) > 0) col = args{1}; if (isnumeric (col)) if (! isvector (col) || any (fix (col) != col) || any (col == 0)) error (strcat ("string.sortrows: COL must be a vector", ... " of nonzero integers indexing columns in A.")); endif if (max (abs (col)) > nc) error ("string.sortrows: COL indexes non-existing column."); endif elseif (isvector (col) && (ischar (col) || iscellstr (col) || isa (col, 'string'))) direction = cellstr (col); if (! all (ismember (direction, {'ascend', 'descend'}))) error (strcat ("string.sortrows: DIRECTION input must", ... " contain either 'ascend' or 'descend' values.")); endif ## Apply scalar expansion if (isscalar (direction)) direction = repmat (direction, 1, nc); endif if (numel (direction) != nc) error (strcat ("string.sortrows: DIRECTION", ... " does not match the columns in A.")); endif ## Assign DIRECTION to COL col = [1:nc]; idx = strcmp (direction, 'descend'); col(idx) = - col(idx); dir_flag = true; else error ("string.sortrows: invalid type for COL argument."); endif endif if (numel (args) > 1) if (dir_flag) error ("string.sortrows: invalid third input argument."); endif if ((isvector (args{2}) && ischar (args{2})) || isa (args{2}, 'string')) direction = cellstr (args{2}); elseif (isvector (args{2}) && iscellstr (args{2})) direction = args{2}; else error ("string.sortrows: invalid type for DIRECTION argument."); endif if (! all (ismember (direction, {'ascend', 'descend'}))) error (strcat ("string.sortrows: DIRECTION input must", ... " contain either 'ascend' or 'descend' values.")); endif ## Assign DIRECTION to COL if (isscalar (direction) && strcmp (direction, 'ascend')) col = abs (col); elseif (isscalar (direction) && strcmp (direction, 'descend')) col = - abs (col); else if (numel (direction) != numel (col)) error (strcat ("string.sortrows: DIRECTION does", ... " not match the elements in COL.")); endif col = abs (col); idx = strcmp (direction, 'descend'); col(idx) = - col(idx); endif endif ## Encode the text as numeric ranks that preserve lexicographic order, so ## that the missing elements (stored internally as '') can be told apart ## from genuine empty strings and placed according to 'MissingPlacement'. ## Missing elements are flagged as NaN, mirroring the numeric-backed ## classes, which lets the workaround below reuse the same logic. [~, ~, ic] = unique (A.strs); data = reshape (ic, size (A)); data(A.isMissing) = NaN; ## FIX ME: this workaround will be removed once the 'sortrows' function ## in core Octave supports the 'MissingPlacement' optional argument. ## The default core behavior already matches 'auto' (missing last for ## ascending columns, first for descending columns), so only the ## overriding cases are handled here. Ranks are positive integers, so ## -Inf never collides with an existing value. pos_dir = col > 0; neg_dir = col < 0; fix_pos_dir = any (pos_dir) && strcmp (MP, 'first'); fix_neg_dir = any (neg_dir) && strcmp (MP, 'last'); if (fix_pos_dir || fix_neg_dir) if (fix_pos_dir) col_idx = col(pos_dir); else # must be fix_neg_dir col_idx = - col(neg_dir); endif cdata = data(:,col_idx); is_nan = isnan (cdata); if (any (is_nan, 'all')) ## Convert missing values to -Inf so that they are placed first ## (ascending) or last (descending), overriding the default. cdata(is_nan) = -Inf; data(:,col_idx) = cdata; endif endif ## Sort the numeric proxy [~, index] = sortrows (data, col); ## Return sorted string array B = subset (A, index, ':'); endfunction ## -*- texinfo -*- ## @deftypefn {string} {@var{newstr} =} split (@var{str}) ## @deftypefnx {string} {@var{newstr} =} split (@var{str}, @var{delimiter}) ## @deftypefnx {string} {@var{newstr} =} split (@var{str}, @var{delimiter}, @var{dim}) ## @deftypefnx {string} {[@var{newstr}, @var{match}] =} split (@dots{}) ## ## Split string array at delimiters. ## ## @code{@var{newstr} = split (@var{str})} divides each element of @var{str} ## at whitespace characters and returns the pieces as a string array. The ## whitespace characters are the space, tab, newline, carriage return, form ## feed, and vertical tab. ## ## @code{@var{newstr} = split (@var{str}, @var{delimiter})} divides each ## element at the substrings specified by @var{delimiter}, which can be a ## string array, a character vector, or a cell array of character vectors. ## When @var{delimiter} contains several substrings they are all used; at a ## given position the substrings are tried in order and the first that ## matches is taken. Delimiters are not collapsed, so consecutive ## delimiters yield empty strings in @var{newstr}. ## ## The pieces of each element are laid out along a new dimension. For a ## string scalar that splits into @var{n} pieces, @var{newstr} is ## @code{@var{n}x1}; for an @code{Mx1} column it is @code{Mx@var{n}}; for a ## @code{1xM} row it is @code{1xMx@var{n}}; and in general the pieces extend ## the first trailing singleton dimension. Every element of @var{str} must ## split into the same number of pieces. Missing values in @var{str} are ## preserved and count as a single piece. ## ## @code{@var{newstr} = split (@var{str}, @var{delimiter}, @var{dim})} lays ## the pieces out along dimension @var{dim}, which must be a dimension along ## which @var{str} has size 1. ## ## @code{[@var{newstr}, @var{match}] = split (@dots{})} also returns the ## delimiters matched between the pieces. @var{match} has the same layout ## as @var{newstr} but with one fewer element along the split dimension. ## ## @end deftypefn function [newstr, matchstr] = split (this, varargin) if (numel (varargin) > 2) error ("string.split: too many input arguments."); endif ## Determine the delimiters (default: whitespace characters) if (numel (varargin) >= 1) delim = varargin{1}; if (isa (delim, 'string')) dlms = cellstr (delim); elseif (ischar (delim) || iscellstr (delim)) dlms = cellstr (string (delim)); else error (strcat ("string.split: DELIMITER must be a string array,", ... " a character vector, or a cell array of", ... " character vectors.")); endif dlms = dlms(:).'; else dlms = {" ", sprintf("\t"), sprintf("\n"), sprintf("\r"), ... sprintf("\f"), sprintf("\v")}; endif ## Validate an explicit split dimension (auto-selected otherwise) dim = []; if (numel (varargin) >= 2) dim = varargin{2}; if (! (isnumeric (dim) && isscalar (dim) && dim == fix (dim) ... && dim >= 1)) error ("string.split: DIM must be a positive integer scalar."); endif if (size (this.strs, dim) != 1) error ("string.split: STR must have size 1 along dimension DIM."); endif endif [newstr, matchstr, errmsg] = split_core (this, dlms, dim); if (! isempty (errmsg)) error ("string.split: %s", errmsg); endif endfunction ## -*- texinfo -*- ## @deftypefn {string} {@var{newstr} =} splitlines (@var{str}) ## ## Split string array at newline characters. ## ## @code{@var{newstr} = splitlines (@var{str})} divides each element of ## @var{str} at its newline characters and returns the lines as a string ## array. The recognized newline characters are the line feed, the carriage ## return, a carriage return followed by a line feed (treated as a single ## boundary), the vertical tab, the form feed, and the Unicode next-line ## (@code{U+0085}), line-separator (@code{U+2028}), and paragraph-separator ## (@code{U+2029}) characters. ## ## The lines are laid out along a new dimension exactly as for @code{split}: ## a string scalar with @var{n} lines yields an @code{@var{n}x1} array, and ## an @code{Mx1} column yields an @code{Mx@var{n}} array. Every element of ## @var{str} must contain the same number of newlines. Missing values in ## @var{str} are preserved and count as a single line. ## ## @end deftypefn function out = splitlines (this) cr = sprintf ("\r"); nl = sprintf ("\n"); ## A carriage return + line feed is tried first so it is consumed as a ## single boundary rather than as two separate ones. dlms = {[cr, nl], nl, cr, sprintf("\v"), sprintf("\f"), ... native2unicode(uint8 ([194, 133]), "UTF-8"), ... native2unicode(uint8 ([226, 128, 168]), "UTF-8"), ... native2unicode(uint8 ([226, 128, 169]), "UTF-8")}; [out, ~, errmsg] = split_core (this, dlms, []); if (! isempty (errmsg)) error ("string.splitlines: %s", errmsg); endif endfunction ## -*- texinfo -*- ## @deftypefn {string} {@var{newstr} =} strcat (@var{str1}, @var{str2}, @dots{}) ## ## Horizontal concatenation of texts in string array. ## ## @code{@var{newstr} = strcat (@var{str1}, @var{str2}, @dots{})} merges ## horizontally all the input arguments into a string array, as long as any ## of the input arguments is a string array. All inputs must be of common ## size or scalars. All inputs must be character vectors, cell arrays of ## character vectors, or string arrays. ## ## @end deftypefn function out = strcat (varargin) args = cell (size (varargin)); for i = 1:numel (args) if (ischar (varargin{i})) args{i} = cellstr (varargin{i}); elseif (isstring (varargin{i})) args{i} = cellstr (varargin{i}); elseif (iscellstr (varargin{i})) args{i} = varargin{i}; else error ("string.strcat: incompatible data type: '%s'", ... class (varargin{i})); endif endfor out = string (strcat (args{:})); endfunction ## -*- texinfo -*- ## @deftypefn {string} {@var{newstr} =} strip (@var{str}) ## @deftypefnx {string} {@var{newstr} =} strip (@var{str}, @var{side}) ## @deftypefnx {string} {@var{newstr} =} strip (@var{str}, @var{stripchar}) ## @deftypefnx {string} {@var{newstr} =} strip (@var{str}, @var{side}, @var{stripchar}) ## ## Remove leading and trailing characters from string array. ## ## @code{@var{newstr} = strip (@var{str})} removes all consecutive ## whitespace characters from the beginning and end of each element of ## @var{str}. The whitespace characters are the space, tab, newline, ## carriage return, form feed, and vertical tab. ## ## @code{@var{newstr} = strip (@var{str}, @var{side})} removes whitespace ## from the side given by @var{side}, which can be @qcode{"left"}, ## @qcode{"right"}, or @qcode{"both"} (the default). ## ## @code{@var{newstr} = strip (@var{str}, @var{stripchar})} removes the ## single character @var{stripchar} instead of whitespace. Because the side ## keywords are longer than one character, a single-character second ## argument is always treated as @var{stripchar}. ## ## @code{@var{newstr} = strip (@var{str}, @var{side}, @var{stripchar})} ## removes @var{stripchar} from the given @var{side}. ## ## @var{newstr} is a string array of the same size as @var{str}. Missing ## values in @var{str} are preserved. ## ## @end deftypefn function out = strip (this, varargin) if (numel (varargin) > 2) error ("string.strip: too many input arguments."); endif istext = @(x) ischar (x) || (isa (x, 'string') && isscalar (x)); side = 'both'; stripchar = ''; haveChar = false; if (numel (varargin) == 1) a = varargin{1}; if (! istext (a)) error (strcat ("string.strip: the second argument must be a side", ... " ('left', 'right', or 'both') or a single", ... " character.")); endif a = char (a); if (numel (str2cp (a)) == 1) stripchar = a; haveChar = true; elseif (any (strcmpi (a, {'left', 'right', 'both'}))) side = a; else error ("string.strip: SIDE must be 'left', 'right', or 'both'."); endif elseif (numel (varargin) == 2) s = varargin{1}; c = varargin{2}; if (! istext (s) || ... ! any (strcmpi (char (s), {'left', 'right', 'both'}))) error ("string.strip: SIDE must be 'left', 'right', or 'both'."); endif if (! istext (c)) error ("string.strip: STRIPCHAR must be a single character."); endif side = char (s); stripchar = char (c); haveChar = true; endif if (haveChar) cp = str2cp (stripchar); if (numel (cp) != 1) error ("string.strip: STRIPCHAR must be a single character."); endif stripcps = cp; else stripcps = uint32 ([9, 10, 11, 12, 13, 32]); endif sl = any (strcmpi (side, {'left', 'both'})); sr = any (strcmpi (side, {'right', 'both'})); out = this; cstr = this.strs; for k = 1:numel (cstr) if (this.isMissing(k)) continue; endif cstr{k} = strip_one (cstr{k}, sl, sr, stripcps); endfor out.strs = cstr; endfunction ## -*- texinfo -*- ## @deftypefn {string} {@var{newstr} =} pad (@var{str}) ## @deftypefnx {string} {@var{newstr} =} pad (@var{str}, @var{nchars}) ## @deftypefnx {string} {@var{newstr} =} pad (@var{str}, @var{side}) ## @deftypefnx {string} {@var{newstr} =} pad (@var{str}, @var{nchars}, @var{side}) ## @deftypefnx {string} {@var{newstr} =} pad (@dots{}, @var{padchar}) ## ## Add leading or trailing characters to string array. ## ## @code{@var{newstr} = pad (@var{str})} pads each element of @var{str} with ## trailing spaces so that every element is as long as the longest one. ## ## @code{@var{newstr} = pad (@var{str}, @var{nchars})} pads each element to ## @var{nchars} characters. Elements that already have more than ## @var{nchars} characters are left unchanged; @code{pad} never truncates. ## ## @code{@var{newstr} = pad (@var{str}, @var{side})} and ## @code{@var{newstr} = pad (@var{str}, @var{nchars}, @var{side})} add the ## padding on the side given by @var{side}, which can be @qcode{"left"}, ## @qcode{"right"}, or @qcode{"both"}. The default is @qcode{"right"}; for ## @qcode{"both"}, an odd number of pad characters places the extra one on ## the right. ## ## @code{@var{newstr} = pad (@dots{}, @var{padchar})} pads with the single ## character @var{padchar} instead of a space. As @var{padchar} is the last ## argument and a single character, it is told apart from @var{side} by its ## length. ## ## Length is measured in characters, not bytes. @var{newstr} is a string ## array of the same size as @var{str}, and missing values in @var{str} are ## preserved. ## ## @end deftypefn function out = pad (this, varargin) if (numel (varargin) > 3) error ("string.pad: too many input arguments."); endif istext = @(x) ischar (x) || (isa (x, 'string') && isscalar (x)); N = []; side = 'right'; padchar = ' '; args = varargin; i = 1; ## Optional NCHARS comes first when numeric if (i <= numel (args) && isnumeric (args{i})) N = args{i}; if (! (isscalar (N) && isreal (N) && N == fix (N) && N >= 0)) error (strcat ("string.pad: NCHARS must be a nonnegative integer", ... " scalar.")); endif i += 1; endif ## A multi-character text argument is the SIDE if (i <= numel (args) && istext (args{i}) ... && numel (str2cp (char (args{i}))) != 1) a = char (args{i}); if (! any (strcmpi (a, {'left', 'right', 'both'}))) error ("string.pad: SIDE must be 'left', 'right', or 'both'."); endif side = a; i += 1; endif ## A single-character text argument is the PADCHAR if (i <= numel (args) && istext (args{i})) padchar = char (args{i}); if (numel (str2cp (padchar)) != 1) error ("string.pad: PADCHAR must be a single character."); endif i += 1; endif if (i <= numel (args)) error ("string.pad: invalid combination of input arguments."); endif ## Measure element lengths in code points; default NCHARS is the longest ne = numel (this.strs); lens = zeros (ne, 1); for k = 1:ne if (! this.isMissing(k)) lens(k) = numel (str2cp (this.strs{k})); endif endfor if (isempty (N)) if (any (! this.isMissing(:))) N = max (lens(! this.isMissing(:))); else N = 0; endif endif padcp = str2cp (padchar); side = lower (side); out = this; cstr = this.strs; for k = 1:ne if (this.isMissing(k)) continue; endif cstr{k} = pad_one (cstr{k}, N, side, padcp); endfor out.strs = cstr; endfunction ## -*- texinfo -*- ## @deftypefn {string} {@var{newstr} =} join (@var{str}) ## @deftypefnx {string} {@var{newstr} =} join (@var{str}, @var{delimiter}) ## @deftypefnx {string} {@var{newstr} =} join (@var{str}, @var{dim}) ## @deftypefnx {string} {@var{newstr} =} join (@var{str}, @var{delimiter}, @var{dim}) ## ## Combine string array elements. ## ## @code{@var{newstr} = join (@var{str})} combines the elements of @var{str} ## along the last dimension whose size is not 1, placing a single space ## between consecutive elements. That dimension is reduced to size 1. ## ## @code{@var{newstr} = join (@var{str}, @var{delimiter})} uses ## @var{delimiter} instead of a space. @var{delimiter} can be a string ## array, a character vector, or a cell array of character vectors. A ## scalar @var{delimiter} is placed between every pair of elements. An ## array @var{delimiter} supplies the text placed between consecutive ## elements and must have one fewer element than @var{str} along the joined ## dimension; its other dimensions must be 1 or match @var{str}. ## ## @code{@var{newstr} = join (@var{str}, @var{dim})} and ## @code{@var{newstr} = join (@var{str}, @var{delimiter}, @var{dim})} ## combine the elements along dimension @var{dim}. ## ## @var{newstr} has the size of @var{str} with the joined dimension reduced ## to 1. If any element being combined, or any delimiter placed between ## them, is a missing value, the corresponding element of @var{newstr} is a ## missing value. ## ## @end deftypefn function newstr = join (this, varargin) if (numel (varargin) > 2) error ("string.join: too many input arguments."); endif istext = @(x) ischar (x) || iscellstr (x) || isa (x, 'string'); delim = []; dim = []; if (numel (varargin) == 1) if (isnumeric (varargin{1})) dim = varargin{1}; elseif (istext (varargin{1})) delim = varargin{1}; else error (strcat ("string.join: the second argument must be a", ... " delimiter or a dimension.")); endif elseif (numel (varargin) == 2) delim = varargin{1}; dim = varargin{2}; if (! istext (delim)) error (strcat ("string.join: DELIMITER must be a string array, a", ... " character vector, or a cell array of character", ... " vectors.")); endif endif sz = size (this.strs); if (isempty (dim)) d = find (sz != 1, 1, 'last'); if (isempty (d)) dim = 2; else dim = d; endif elseif (! (isscalar (dim) && isreal (dim) && dim == fix (dim) ... && dim >= 1)) error ("string.join: DIM must be a positive integer scalar."); endif N = size (this.strs, dim); ## Normalize the delimiter to a cell array DC + missing mask DM sized like ## STR but with N-1 elements along DIM (broadcasting on the other dims) tsz = sz; tsz(dim) = max (N - 1, 0); if (isempty (delim)) Dc = repmat ({' '}, tsz); Dm = false (tsz); else if (isa (delim, 'string')) dc0 = cellstr (delim); dm0 = ismissing (delim); else dc0 = cellstr (string (delim)); dm0 = false (size (dc0)); endif if (isscalar (dc0)) Dc = repmat (dc0, tsz); Dm = repmat (dm0, tsz); else [Dc, Dm, ok] = join_bcast (dc0, dm0, tsz, dim); if (! ok) error (strcat ("string.join: DELIMITER must have one fewer", ... " element than STR along the joined dimension.")); endif endif endif ## Permute the joined dimension to the front and join each column nd = max (numel (sz), dim); perm = [dim, setdiff(1:nd, dim)]; cperm = permute (this.strs, perm); mperm = permute (this.isMissing, perm); pszc = size (cperm); R = prod (pszc(2:end)); C = reshape (cperm, N, R); Mi = reshape (mperm, N, R); D = reshape (permute (Dc, perm), max (N - 1, 0), R); Dm = reshape (permute (Dm, perm), max (N - 1, 0), R); outc = cell (1, R); outm = false (1, R); for r = 1:R if (any (Mi(:,r)) || (N >= 2 && any (Dm(:,r)))) outm(r) = true; outc{r} = ''; elseif (N == 0) outc{r} = ''; else pieces = cell (1, 2 * N - 1); p = 0; for j = 1:N p += 1; pieces{p} = C{j,r}; if (j < N) p += 1; pieces{p} = D{j,r}; endif endfor outc{r} = [pieces{:}]; endif endfor opsz = pszc; opsz(1) = 1; newstr = this; newstr.strs = ipermute (reshape (outc, opsz), perm); newstr.isMissing = ipermute (reshape (outm, opsz), perm); endfunction ## -*- texinfo -*- ## @deftypefn {string} {@var{newstr} =} plus (@var{str1}, @var{str2}) ## ## Append strings. ## ## @code{@var{newstr} = lower (@var{str})} is the equivalent of the syntax ## @code{@var{newstr} = @var{str1} + @var{str2}} and appends @var{str2} to ## @var{str1}. Both input arguments must be string arrays of compatible ## size. ## ## @end deftypefn function out = plus (str1, str2) if (isa (str1, 'string') && isa (str2, 'string')) out = append (str1, str2); else error ("string.plus: both STR1 and STR2 must be string arrays."); endif endfunction ## -*- texinfo -*- ## @deftypefn {string} {@var{newstr} =} lower (@var{str}) ## ## Convert contents of string array to lower case. ## ## @code{@var{newstr} = lower (@var{str})} converts all upper case ## characters in every element of the string array @var{str} to lower case. ## @var{newstr} is a string array of the same size as @var{str}. ## ## @end deftypefn function out = lower (this) out = this; out.strs = lower (this.strs); endfunction ## -*- texinfo -*- ## @deftypefn {string} {@var{newstr} =} upper (@var{str}) ## ## Convert contents of string array to upper case. ## ## @code{@var{newstr} = upper (@var{str})} converts all lower case ## characters in every element of the string array @var{str} to upper case. ## @var{newstr} is a string array of the same size as @var{str}. ## ## @end deftypefn function out = upper (this) out = this; out.strs = upper (this.strs); endfunction ## -*- texinfo -*- ## @deftypefn {string} {@var{B} =} unique (@var{A}) ## @deftypefnx {string} {@var{B} =} unique (@var{A}, @qcode{'rows'}) ## @deftypefnx {string} {[@var{B}, @var{ixA}, @var{ixB}] =} unique (@dots{}) ## @deftypefnx {string} {@dots{} =} unique (@dots{}, @var{order}) ## @deftypefnx {string} {@dots{} =} unique (@dots{}, @var{occurrence}) ## ## Unique values in a string array. ## ## @code{@var{B} = unique (@var{A})} returns the unique values of the string ## array @var{A} in the string vector @var{B} sorted lexicographically. If ## If @var{A} is a column vector, then @var{B} is also a column vector, ## otherwise @code{unique} returns a row vector. ## ## @code{@var{B} = unique (@var{A}, @qcode{'rows'})} returns the unique rows ## of the string matrix @var{A} in the string matrix @var{B} sorted in ## lexicographical order. ## ## @code{[@var{B}, @var{ixA}, @var{ixB}] = unique (@dots{})} also returns ## index vectors @var{ixA} and @var{ixB} such that ## @code{@var{B} = @var{A}(@var{ixA})} and ## @code{@var{A} = @var{B}(@var{ixB})}, unless the @qcode{'rows'} optional ## argument is given, in which case @code{@var{B} = @var{A}(@var{ixA},:)} ## and @code{@var{A} = @var{B}(@var{ixB},:)}. ## ## @code{@dots{} = unique (@dots{}, @var{order})} also specifies the order ## of the returned unique values. @var{order} may be either ## @qcode{'sorted'}, which is the default behavior, or @qcode{'stable'}, in ## which case the unique values are returned in order of appearance. ## ## @code{@dots{} = unique (@dots{}, @var{occurrence})} also specifies the ## which index is returned in @var{ixA}, where there are repeated values or ## rows (if opted) in the input categorical array. @var{occurrence} may be ## either @qcode{'first'}, which is the default and returns the index of the ## first occurrence of each unique value, or @qcode{'last'}, in which case ## the last occurrence of each unique value is returned. ## ## @end deftypefn function [B, ixA, ixB] = unique (A, varargin) ## Handle 'rows' option do_rows = false; if (! isempty (varargin)) idx = strcmpi ('rows', varargin(:)); if (any (idx)) do_rows = true; varargin(idx) = []; if (ndims (A) != 2) error ("string.unique: 'rows' applies only to 2-D matrices."); endif endif endif ## Handle 'setOrder' and 'occurrence' options opt = "sorted"; if (! isempty (varargin)) if (any (strcmp (varargin{1}, {"sorted", "stable", "first", "last"}))) opt = varargin{1}; else error ("string.unique: invalid option '%s'.", varargin{1}); endif endif ## Find unique if (do_rows) is_nm = ! any (A.isMissing, 2); A = subset (A, is_nm, ':'); [~, ixA, ixB] = __unique__ (A.strs, 'rows', opt); B = subset (A, ixA, ':'); is_missing = ! is_nm; if (any (is_missing)) w = size (A, 2); B = [B; repmat(missing, sum (is_missing), w)]; endif else is_nm = ! A.isMissing; A = subset (A, is_nm); [~, ixA, ixB] = __unique__ (A.strs, opt); B = subset (A, ixA); is_missing = ! is_nm(:); if (any (is_missing)) if (isrow (A)) mstr = repmat (missing, 1, sum (is_missing)); B = [B, mstr]; else mstr = repmat (missing, sum (is_missing), 1); B = [B; mstr]; endif endif endif endfunction endmethods ################################################################################ ## ** Array Operations ** ## ################################################################################ ## Available Methods ## ## ## ## 'cat' 'horzcat' 'vertcat' 'repmat' ## ## 'repelem' 'repelems' 'reshape' 'circshift' ## ## 'permute' 'ipermute' 'transpose' 'ctranspose' ## ## ## ################################################################################ methods (Access = public) ## -*- texinfo -*- ## @deftypefn {string} {@var{C} =} cat (@var{dim}, @var{A}, @var{B}, @dots{}) ## ## Concatenate string arrays. ## ## @code{@var{C} = cat (@var{dim}, @var{A}, @var{B}, @dots{})} concatenates ## string arrays @var{A}, @var{B}, @dots{} along dimension @var{dim}. All ## input arrays must have the same size except along the operating dimension ## @var{dim}. Any of the input arrays may also be character matrixes, cell ## arrays of character vectors, numeric arrays, or logical arrays of ## compatible size. ## ## @end deftypefn function out = cat (dim, varargin) ## For categorical, datetime, and duration arrays being present in the ## input arguments, call their constructor for the first input array and ## forward all input to their respective concatenation method. is_datetime = cellfun ('isdatetime', varargin); is_duration = cellfun ('isduration', varargin); if (any (cellfun ('iscategorical', varargin))) varargin{1} = categorical (varargin{1}); out = cat (dim, varargin{:}); return; elseif (any (is_datetime)) idx = find (is_datetime, 1); tmp = varargin{idx}; varargin{1} = datetime (varargin{1}, 'Format', tmp.Format); out = cat (dim, varargin{:}); return; elseif (any (cellfun ('isduration', varargin))) idx = find (is_duration, 1); tmp = varargin{idx}; varargin{1} = duration (varargin{1}, 'Format', tmp.Format); out = cat (dim, varargin{:}); return; endif ## For everything else, try converting it a to string array idx = find (cellfun (@(x) ! isstring (x), varargin)); if (! isempty (idx)) for i = idx varargin{i} = string (varargin{i}); endfor endif ## Concatenate strings out = string; tmp = cellfun (@(obj) obj.strs, varargin, 'UniformOutput', false); out.strs = cat (dim, tmp{:}); tmp = cellfun (@(obj) obj.isMissing, varargin, 'UniformOutput', false); out.isMissing = cat (dim, tmp{:}); endfunction ## -*- texinfo -*- ## @deftypefn {string} {@var{C} =} horzcat (@var{A}, @var{B}, @dots{}) ## ## Horizontal concatenation of string arrays. ## ## @code{@var{C} = horzcat (@var{A}, @var{B}, @dots{})} is the equivalent of ## the syntax @code{@var{C} = [@var{A}, @var{B}, @dots{}]} and horizontally ## concatenates the string arrays @var{A}, @var{B}, @dots{}. All input ## arrays must have the same size except along the second dimension. Any of ## the input arrays may also be character matrixes, cell arrays of character ## vectors, numeric arrays, or logical arrays of compatible size. ## ## @end deftypefn function out = horzcat (varargin) out = cat (2, varargin{:}); endfunction ## -*- texinfo -*- ## @deftypefn {string} {@var{C} =} vertcat (@var{A}, @var{B}, @dots{}) ## ## Vertical concatenation of string arrays. ## ## @code{@var{C} = vertcat (@var{A}, @var{B}, @dots{})} is the equivalent of ## the syntax @code{@var{C} = [@var{A}; @var{B}; @dots{}]} and vertically ## concatenates the string arrays @var{A}, @var{B}, @dots{}. All input ## arrays must have the same size except along the first dimension. Any of ## the input arrays may also be character matrixes, cell arrays of character ## vectors, numeric arrays, or logical arrays of compatible size. ## ## @end deftypefn function out = vertcat (varargin) out = cat (1, varargin{:}); endfunction ## -*- texinfo -*- ## @deftypefn {string} {@var{B} =} repmat (@var{A}, @var{n}) ## @deftypefnx {string} {@var{B} =} repmat (@var{A}, @var{d1}, @dots{}, @var{dN}) ## @deftypefnx {string} {@var{B} =} repmat (@var{A}, @var{dimvec}) ## ## Repeat copies of a string array. ## ## @code{@var{B} = repmat (@var{A}, @var{n})} returns a string array @var{B} ## containing @var{n} copies of the input string array @var{A} along every ## dimension of @var{A}. ## ## @code{@var{B} = repmat (@var{A}, @var{d1}, @dots{}, @var{dN})} returns an ## array @var{B} containing copies of @var{A} along the dimensions specified ## by the list of scalar integer values @var{d1}, @dots{}, @var{dN}, which ## specify how many copies of @var{A} are made in each dimension. ## ## @code{@var{B} = repmat (@var{A}, @var{dimvec})} is equivalent to the ## previous syntax with @code{@var{dimvec} = [@var{d1}, @dots{}, @var{dN}]}. ## ## @end deftypefn function this = repmat (this, varargin) this.strs = repmat (this.strs, varargin{:}); this.isMissing = repmat (this.isMissing, varargin{:}); endfunction ## -*- texinfo -*- ## @deftypefn {string} {@var{B} =} repelem (@var{A}, @var{n}) ## @deftypefnx {string} {@var{B} =} repelem (@var{A}, @var{d1}, @dots{}, @var{dN}) ## ## Repeat copies of string array elements. ## ## @code{@var{B} = repelem (@var{A}, @var{n})} returns a string vector ## @var{B} containing repeated elements of the input @var{A}, which must be ## a string vector. If @var{n} is a scalar, each element of @var{A} is ## repeated @var{n} times along the non-singleton dimension of @var{A}. If ## @var{n} is a vector, it must have the same elements as @var{A}, in which ## case it specifies the number of times to repeat each corresponding ## element of @var{A}. ## ## @code{@var{B} = repelem (@var{A}, @var{d1}, @dots{}, @var{dN})} returns ## an array @var{B} with each element of @var{A} repeated according to the ## the list of input arguments @code{@var{d1}, @dots{}, @var{dN}} each ## corresponding to a different dimension @code{1:ndims (@var{A})} of the ## input array @var{A}. @var{d1}, @dots{}, @var{dN} must be either scalars ## or vectors with the same length as the corresponding dimension of ## @var{A} containing non-negative integer values specifying the number of ## repetitions of each element along the corresponding dimension. ## ## @end deftypefn function this = repelem (this, varargin) this.strs = repelem (this.strs, varargin{:}); this.isMissing = repelem (this.isMissing, varargin{:}); endfunction ## -*- texinfo -*- ## @deftypefn {string} {@var{B} =} repelems (@var{A}, @var{R}) ## ## Construct a vector of repeated elements from a string array. ## ## @code{@var{B} = repelems (@var{A}, @var{R})} returns a string vector ## @var{B} containing repeated elements of the input @var{A}, which must be ## a string vector. @var{R} must be a @math{2*N} matrix of integers. ## Entries in the first row of @var{R} correspond to the linear indexing of ## the elements in @var{A} to be repeated. The corresponding entries in the ## second row of @var{R} specify the repeat count of each element. ## ## @end deftypefn function this = repelems (this, R) this.strs = repelems (this.strs, R); this.isMissing = repelems (this.isMissing, R); endfunction ## -*- texinfo -*- ## @deftypefn {string} {@var{B} =} reshape (@var{A}, @var{d1}, @dots{}, @var{dN}) ## @deftypefnx {string} {@var{B} =} reshape (@var{A}, @dots{}, @qcode{[]}, @dots{}) ## @deftypefnx {string} {@var{B} =} reshape (@var{A}, @var{dimvec}) ## ## Reshape string array. ## ## @code{@var{B} = reshape (@var{A}, @var{d1}, @dots{}, @var{dN})} returns ## a string array @var{B} with specified dimensions @var{d1}, @dots{}, ## @var{dN}, whose elements are taken columnwise from the string array ## @var{A}. The product of @var{d1}, @dots{}, @var{dN} must equal the total ## number of elements in @var{A}. ## ## @code{@var{B} = reshape (@var{A}, @dots{}, @qcode{[]}, @dots{})} returns ## a string array @var{B} with one dimension unspecified which is calculated ## automatically so that the product of dimensions in @var{B} matches the ## total elements in @var{A}, which must be divisible the product of ## specified dimensions. An empty matrix @qcode{([])} is used to flag the ## unspecified dimension. ## ## @end deftypefn function this = reshape (this, varargin) this.strs = reshape (this.strs, varargin{:}); this.isMissing = reshape (this.isMissing, varargin{:}); endfunction ## -*- texinfo -*- ## @deftypefn {string} {@var{B} =} circshift (@var{A}, @var{n}) ## @deftypefnx {string} {@var{B} =} circshift (@var{A}, @var{n}, @var{dim}) ## ## Circularly shift the elements in a string array. ## ## @code{@var{B} = circshift (@var{A}, @var{n})} circularly shifts the ## elements of the string array @var{A} according to @var{n}. If @var{n} ## is a nonzero integer scalar, then the elements of @var{A} are shifted by ## @var{n} elements along the first non-singleton dimension of @var{A}. If ## @var{n} is a vector, it must not be longer that the number of dimensions ## of @var{A} with each value of @var{n} corresponding to a dimension in ## @var{A}. The sign of the value(s) in @var{n} specify the direction in ## the elements of @var{A} are shifted. ## ## @code{@var{B} = circshift (@var{A}, @var{n}, @var{dim})} circularly ## shifts the elements of the string array @var{A} along the dimension ## specified by @var{dim}. In this case, @var{n} must be a scalar integer ## value. ## ## @end deftypefn function this = circshift (this, varargin) this.strs = circshift (this.strs, varargin{:}); this.isMissing = circshift (this.isMissing, varargin{:}); endfunction ## -*- texinfo -*- ## @deftypefn {string} {@var{B} =} permute (@var{A}, @var{dims}) ## ## Generalized transpose for a string N-D array. ## ## @code{@var{B} = permute (@var{A}, @var{dims})} returns the generalized ## transpose of the string array @var{A} by rearranging its dimensions ## according to the permutation vector specified in @var{dims}. ## ## @var{dims} must index all the dimensions @code{1:ndims (@var{A})} of the ## input array @var{A}, in any order, but only once. The @var{N}th ## dimension of @var{A} gets remapped to the dimension in @var{B} specified ## by @code{@var{dims}(@var{N})}. ## ## @end deftypefn function this = permute (this, varargin) this.strs = permute (this.strs, varargin{:}); this.isMissing = permute (this.isMissing, varargin{:}); endfunction ## -*- texinfo -*- ## @deftypefn {string} {@var{A} =} ipermute (@var{B}, @var{dims}) ## ## Inverse of the generalized transpose for a string N-D array. ## ## @code{@var{A} = ipermute (@var{B}, @var{dims})} returns the inverse of ## the generalized transpose performed by the @code{permute} function. The ## expression @code{ipermute (permute (@var{A}, @var{dims}), @var{dims})} ## returns the original array @var{A}. ## ## @var{dims} must index all the dimensions @code{1:ndims (@var{B})} of the ## input array @var{B}, in any order, but only once. The dimension of ## @var{B} specified in @code{@var{dims}(@var{N})} gets remapped to the ## @var{N}th dimension of @var{A}. ## ## @end deftypefn function this = ipermute (this, varargin) this.strs = ipermute (this.strs, varargin{:}); this.isMissing = ipermute (this.isMissing, varargin{:}); endfunction ## -*- texinfo -*- ## @deftypefn {string} {@var{B} =} transpose (@var{A}) ## ## Transpose a string matrix. ## ## @code{@var{B} = transpose (@var{A})} is the equivalent of the syntax ## @code{@var{B} = @var{A}.'} and returns the transpose of the string ## matrix @var{A}. ## ## @end deftypefn function this = transpose (this, varargin) this.strs = transpose (this.strs, varargin{:}); this.isMissing = transpose (this.isMissing, varargin{:}); endfunction ## -*- texinfo -*- ## @deftypefn {string} {@var{B} =} ctranspose (@var{A}) ## ## Transpose a string matrix. ## ## @code{@var{B} = ctranspose (@var{A})} is the equivalent of the syntax ## @code{@var{B} = @var{A}'} and returns the transpose of the string matrix ## @var{A}. For string arrays, @code{ctranspose} is identical to ## @code{transpose}. ## ## @end deftypefn function this = ctranspose (this, varargin) this.strs = ctranspose (this.strs, varargin{:}); this.isMissing = ctranspose (this.isMissing, varargin{:}); endfunction endmethods ################################################################################ ## ** Reference and Assignment Operations ** ## ################################################################################ ## Available Methods ## ## ## ## 'end' 'subsref' 'subsasgn' ## ## ## ################################################################################ methods (Hidden) ## Overloaded end keyword function last_index = end (this, end_dim, ndim_obj) lastdim = ndims (this); if (end_dim == ndim_obj && ndim_obj == 1) last_index = prod (size (this)); elseif (end_dim == ndim_obj && end_dim < lastdim) last_index = prod (size (this)(end_dim:lastdim)); else last_index = size (this, end_dim); endif endfunction ## Class specific subscripted reference function varargout = subsref (this, s) chain_s = s(2:end); s = s(1); switch (s.type) case '()' out = this; out.strs = this.strs(s.subs{:}); out.isMissing = this.isMissing(s.subs{:}); case '{}' out = this.strs(s.subs{:}); case '.' error (strcat ("string.subsref: '.' invalid indexing", ... " for referencing values. Use '()' instead.")); endswitch ## Chained references if (! isempty (chain_s)) out = subsref (out, chain_s); endif varargout{1} = out; endfunction ## Class specific subscripted assignment function this = subsasgn (this, s, val) ## Chained subscripts chain_s = s(2:end); s = s(1); if (! isempty (chain_s)) rhs_in = subsref (this, s); rhs = subsasgn (rhs_in, chain_s, val); else rhs = val; endif switch (s.type) case '()' if (isempty (rhs)) this.strs(s.subs{:}) = []; this.isMissing(s.subs{:}) =[]; return; elseif (! isa (rhs, "string")) rhs = string (rhs); endif oldnum = numel (this.strs); this.strs(s.subs{:}) = rhs.strs; this.isMissing(s.subs{:}) = rhs.isMissing; ## Growing past the end auto-fills gap cells with [] (and isMissing ## false); MATLAB fills such gaps with , so mark them. A ## genuine missing element stores '' (char), so 'ischar' separates ## real entries from the numeric-empty gaps. if (numel (this.strs) > oldnum) gaps = ! cellfun ('ischar', this.strs); this.strs(gaps) = {''}; this.isMissing(gaps) = true; endif case '{}' if (! ischar (rhs) || ! isvector (rhs)) error (strcat ("string.subsasgn: '{}' indexed assignment", ... " requires a character vector.")); endif if (numel (this.strs(s.subs{:})) != 1) error (strcat ("string.subsasgn: '{}' indexing can", ... " only be used for simple assignment.")); endif this.strs(s.subs{:}) = {rhs}; this.isMissing(s.subs{:}) = false; case '.' error (strcat ("string.subsasgn: '.' invalid indexing for", ... " assigning values. Use '()' or '{}' instead.")); endswitch endfunction endmethods ################################################################################ ## ** Overloaded methods for duration and categorical classes ** ## ################################################################################ ## Available Methods ## ## ## ## 'colon' 'linspace' 'intersect' 'setdiff' ## ## 'setxor' 'union' 'isequal' 'isequaln' ## ## ## ################################################################################ methods (Hidden) ## Overload colon for duration support function R = colon (varargin) ## Get properties from first duration input idx = find (cellfun ('isduration', varargin), 1); if (isempty (idx)) error ("string.colon: unsupported input types."); endif A = varargin{idx}; ## Convert first input (string) to duration varargin{1} = duration (varargin{1}, 'Format', A.Format); ## Call duration overloaded method R = colon (varargin{:}); endfunction ## Overload linspace for duration support function R = linspace (A, B, n = 100) ## Check for duration input if (! isduration (B)) error ("string.linspace: unsupported input types."); endif ## Convert first input (string) to duration A = duration (A, 'Format', B.Format); ## Call duration overloaded method R = linspace (A, B, n); endfunction ## Overload intersect for duration or categorical support function [C, ixA, ixB] = intersect (A, B, varargin) ## Check for duration input if (! (isduration (B) || iscategorical (B))) error ("string.intersect: unsupported input types."); endif ## Convert first input (string) to cellstr A = cellstr (A); ## Call overloaded method [C, ixA, ixB] = intersect (A, B, varargin{:}); endfunction ## Overload setdiff for duration or categorical support function [C, index] = setdiff (A, B, varargin) ## Check for duration input if (! (isduration (B) || iscategorical (B))) error ("string.setdiff: unsupported input types."); endif ## Convert first input (string) to cellstr A = cellstr (A); ## Call duration overloaded method [C, index] = setdiff (A, B, varargin{:}); endfunction ## Overload setxor for duration or categorical support function [C, ixA, ixB] = setxor (A, B, varargin) ## Check for duration input if (! (isduration (B) || iscategorical (B))) error ("string.setxor: unsupported input types."); endif ## Convert first input (string) to cellstr A = cellstr (A); ## Call duration overloaded method [C, ixA, ixB] = setxor (A, B, varargin{:}); endfunction ## Overload union for duration or categorical support function [C, ixA, ixB] = union (A, B, varargin) ## Check for duration input if (! (isduration (B) || iscategorical (B))) error ("string.union: unsupported input types."); endif ## Convert first input (string) to cellstr A = cellstr (A); ## Call duration overloaded method [C, ixA, ixB] = union (A, B, varargin{:}); endfunction ## Overload isequal for categorical support function TF = isequal (varargin) ## Check for categorical input idx = find (cellfun ('iscategorical', varargin), 1); if (isempty (idx)) if (any (cellfun (@(x) ! isa (x, 'string'), varargin))) error ("string.isequal: unsupported input types."); endif tmp1 = cellfun (@(x) x.strs, varargin, 'UniformOutput', false); tmp2 = cellfun (@(x) x.isMissing, varargin, 'UniformOutput', false); if (any (cellfun (@(x) any (x, 'all'), tmp2))) TF = false; else TF = isequal (tmp1{:}) && isequal (tmp2{:}); endif else ## Convert first input (string) to categorical varargin{1} = categorical (varargin{1}); ## Call categorical overloaded method TF = isequal (varargin{:}); endif endfunction ## Overload isequaln for categorical support function TF = isequaln (varargin) ## Check for categorical input idx = find (cellfun ('iscategorical', varargin), 1); if (isempty (idx)) if (any (cellfun (@(x) ! isa (x, 'string'), varargin))) error ("string.isequaln: unsupported input types."); endif tmp1 = cellfun (@(x) x.strs, varargin, 'UniformOutput', false); tmp2 = cellfun (@(x) x.isMissing, varargin, 'UniformOutput', false); TF = isequal (tmp1{:}) && isequal (tmp2{:}); else ## Convert first input (string) to categorical varargin{1} = categorical (varargin{1}); ## Call categorical overloaded method TF = isequaln (varargin{:}); endif endfunction endmethods methods (Access = private) ## Return a subset of the array function this = subset (this, varargin) this = this; this.strs = this.strs(varargin{:}); this.isMissing = this.isMissing(varargin{:}); endfunction ## Shared implementation of extractAfter (AFTER true) and extractBefore ## (AFTER false). PAT may be numeric positions or text boundaries; an ## unmatched text boundary yields a missing value. On invalid input returns ## a non-empty ERRMSG describing the fault, leaving OUT unchanged, so the ## calling method can emit the error under its own name. function [out, errmsg] = extract_side (this, pat, after) out = this; errmsg = ''; cstr = this.strs; isMiss = this.isMissing; sz = size (cstr); if (isnumeric (pat)) [pos, errmsg] = eb_expand (pat, sz, 'POS'); if (! isempty (errmsg)) return; endif if (any (pos(:) != fix (pos(:))) || any (pos(:) < 1)) errmsg = "POS must be a positive integer."; return; endif sel = ! isMiss; cps = cell (size (cstr)); cps(sel) = cellfun (@str2cp, cstr(sel), 'UniformOutput', false); lens = cellfun (@numel, cps); # 0 for missing entries if (any (pos(sel) > lens(sel))) errmsg = "POS exceeds the length of the string."; return; endif if (after) cps(sel) = cellfun (@(cp, p) cp((p + 1):end), ... cps(sel), num2cell (pos(sel)), ... 'UniformOutput', false); else cps(sel) = cellfun (@(cp, p) cp(1:(p - 1)), ... cps(sel), num2cell (pos(sel)), ... 'UniformOutput', false); endif cstr(sel) = cellfun (@cp2str, cps(sel), 'UniformOutput', false); else if (isa (pat, 'string')) pat = cellstr (pat); elseif (ischar (pat) || iscellstr (pat)) ## Route char/cellstr through the constructor, which keeps trailing ## whitespace that bare 'cellstr' would deblank pat = cellstr (string (pat)); else errmsg = strcat ("PAT must be a string array, a character", ... " vector, or a cell array of character vectors."); return; endif [pat, errmsg] = eb_expand (pat, sz, 'PAT'); if (! isempty (errmsg)) return; endif for k = 1:numel (cstr) if (isMiss(k)) continue; endif s = cstr{k}; p = pat{k}; i = []; if (! isempty (p)) i = strfind (s, p); endif if (isempty (i)) cstr{k} = ''; # PAT not found -> missing isMiss(k) = true; continue; endif i = i(1); if (after) cstr{k} = s((i + numel (p)):end); else cstr{k} = s(1:(i - 1)); endif endfor endif out.strs = cstr; out.isMissing = isMiss; endfunction ## Shared implementation of insertAfter (AFTER true) and insertBefore ## (AFTER false). PAT may be numeric positions or text boundaries; text ## boundaries are matched at every non-overlapping occurrence (an unmatched ## boundary leaves the element unchanged). NEW is the text to insert, ## scalar or the same size as STR; a missing NEW makes the corresponding ## element missing. On invalid input returns a non-empty ERRMSG, leaving ## OUT unchanged, so the calling method can emit the error under its own ## name. function [out, errmsg] = insert_side (this, pat, new, after) out = this; cstr = this.strs; isMiss = this.isMissing; sz = size (cstr); [newc, newMiss, errmsg] = norm_new (new, sz); if (! isempty (errmsg)) return; endif if (isnumeric (pat)) [pos, errmsg] = eb_expand (pat, sz, 'POS'); if (! isempty (errmsg)) return; endif if (any (pos(:) != fix (pos(:))) || any (pos(:) < 1)) errmsg = "POS must be a positive integer."; return; endif proc = ! isMiss; # elements to act on gone = proc & newMiss; # missing NEW -> element missing do_ = proc & ! newMiss; # elements actually spliced cstr(gone) = {''}; isMiss(gone) = true; cps = cell (size (cstr)); cps(do_) = cellfun (@str2cp, cstr(do_), 'UniformOutput', false); lens = cellfun (@numel, cps); # 0 outside do_ if (any (pos(do_) > lens(do_))) errmsg = "POS exceeds the length of the string."; return; endif ncps = cellfun (@str2cp, newc(do_), 'UniformOutput', false); if (after) cps(do_) = cellfun (@(cp, ncp, p) [cp(1:p), ncp, cp((p + 1):end)], ... cps(do_), ncps, num2cell (pos(do_)), ... 'UniformOutput', false); else cps(do_) = cellfun (@(cp, ncp, p) [cp(1:(p - 1)), ncp, cp(p:end)], ... cps(do_), ncps, num2cell (pos(do_)), ... 'UniformOutput', false); endif cstr(do_) = cellfun (@cp2str, cps(do_), 'UniformOutput', false); else if (isa (pat, 'string')) pat = cellstr (pat); elseif (ischar (pat) || iscellstr (pat)) ## Route char/cellstr through the constructor, which keeps trailing ## whitespace that bare 'cellstr' would deblank pat = cellstr (string (pat)); else errmsg = strcat ("PAT must be a string array, a character", ... " vector, or a cell array of character vectors."); return; endif [pat, errmsg] = eb_expand (pat, sz, 'PAT'); if (! isempty (errmsg)) return; endif for k = 1:numel (cstr) if (isMiss(k)) continue; endif if (newMiss(k)) cstr{k} = ''; isMiss(k) = true; continue; endif p = pat{k}; if (isempty (p)) continue; # an empty boundary matches nothing endif if (after) cstr{k} = strrep (cstr{k}, p, [p, newc{k}]); else cstr{k} = strrep (cstr{k}, p, [newc{k}, p]); endif endfor endif out.strs = cstr; out.isMissing = isMiss; endfunction ## Shared engine for split/splitlines. Splits every element at the DLMS ## delimiters and lays the pieces out along dimension DIM (auto-selected ## when DIM is empty: the first trailing singleton dimension, or dimension 1 ## for a scalar). Returns the split array NEWSTR, the matched delimiters ## MATCHSTR (one fewer along DIM), and a non-empty ERRMSG if the elements do ## not all split into the same number of pieces; the caller emits the error ## under its own name. Missing elements count as a single (missing) piece. function [newstr, matchstr, errmsg] = split_core (this, dlms, dim) newstr = this; matchstr = this; errmsg = ''; sz = size (this.strs); ne = numel (this.strs); if (isempty (dim)) if (isscalar (this.strs)) dim = 1; else dim = 2; while (size (this.strs, dim) != 1) dim += 1; endwhile endif endif ## Split every element, requiring a common piece count pieces = cell (ne, 1); matches = cell (ne, 1); counts = zeros (ne, 1); for k = 1:ne if (this.isMissing(k)) pieces{k} = {''}; matches{k} = cell (1, 0); counts(k) = 1; else [pieces{k}, matches{k}] = split_one (this.strs{k}, dlms); counts(k) = numel (pieces{k}); endif endfor if (ne > 0 && any (counts != counts(1))) errmsg = strcat ("each element of STR must split into the same", ... " number of substrings."); return; endif N = 0; if (ne > 0) N = counts(1); endif ## Assemble the pieces (and matches) into element x count matrices if (ne > 0) pc = vertcat (pieces{:}); # ne x N mc = vertcat (matches{:}); # ne x (N - 1) else pc = cell (0, N); mc = cell (0, max (N - 1, 0)); endif pm = false (ne, N); if (ne > 0) pm(:,1) = this.isMissing(:); endif [newstr.strs, newstr.isMissing] = split_place (pc, pm, sz, dim, N); Nm = max (N - 1, 0); [matchstr.strs, matchstr.isMissing] = ... split_place (mc, false (ne, Nm), sz, dim, Nm); endfunction endmethods endclassdef ## Broadcast a scalar boundary argument to the size of STR, or verify that it ## already matches. Works for both numeric arrays and cell arrays of patterns. ## On a size mismatch returns a non-empty ERRMSG naming the offending argument; ## the calling method emits the error under its own name. function [arg, errmsg] = eb_expand (arg, sz, name) errmsg = ''; if (numel (arg) == 1) arg = repmat (arg, sz); elseif (! isequal (size (arg), sz)) errmsg = sprintf ("%s must be scalar or the same size as STR.", name); endif endfunction ## Erase the content of a single character vector S between two boundaries. ## When ISPOS is true, A and B are numeric character positions; otherwise they ## are the start and end boundary substrings. INCLUSIVE selects whether the ## boundaries themselves are erased. On an out-of-range position returns a ## non-empty ERRMSG; the calling method emits the error under its own name. function [s, errmsg] = eb_between (s, a, b, isPos, inclusive) errmsg = ''; if (isPos) cp = str2cp (s); n = numel (cp); if (a != fix (a) || b != fix (b) || a < 1 || a > b || b > n) errmsg = "position indices out of range."; return; endif if (inclusive) cp(a:b) = []; elseif (a + 1 <= b - 1) cp((a + 1):(b - 1)) = []; endif s = cp2str (cp); else if (isempty (a) || isempty (b)) return; # an empty boundary matches nothing endif i = strfind (s, a); if (isempty (i)) return; # start boundary not found endif i = i(1); aEnd = i + numel (a) - 1; j = strfind (s, b); j = j(j >= aEnd + 1); # end boundary must follow the start match if (isempty (j)) return; # end boundary not found endif j = j(1); if (inclusive) s(i:(j + numel (b) - 1)) = []; elseif (aEnd + 1 <= j - 1) s((aEnd + 1):(j - 1)) = []; endif endif endfunction ## Replace the content of a single character vector S between two boundaries ## with the text NEW. When ISPOS is true, A and B are numeric character ## positions; otherwise they are the start and end boundary substrings, matched ## at their first occurrence (an unmatched boundary leaves S unchanged). ## INCLUSIVE selects whether the boundaries themselves are replaced. The ## replaced span may be empty, in which case NEW is inserted between the ## boundaries. On an out-of-range position returns a non-empty ERRMSG; the ## calling method emits the error under its own name. function [s, errmsg] = rb_between (s, a, b, isPos, inclusive, new) errmsg = ''; if (isPos) cp = str2cp (s); n = numel (cp); if (a != fix (a) || b != fix (b) || a < 1 || a > b || b > n) errmsg = "position indices out of range."; return; endif ncp = str2cp (new); if (inclusive) cp = [cp(1:(a - 1)), ncp, cp((b + 1):end)]; else cp = [cp(1:a), ncp, cp(b:end)]; endif s = cp2str (cp); else if (isempty (a) || isempty (b)) return; # an empty boundary matches nothing endif i = strfind (s, a); if (isempty (i)) return; # start boundary not found endif i = i(1); aEnd = i + numel (a) - 1; j = strfind (s, b); j = j(j >= aEnd + 1); # end boundary must follow the start match if (isempty (j)) return; # end boundary not found endif j = j(1); if (inclusive) s = [s(1:(i - 1)), new, s((j + numel (b)):end)]; else s = [s(1:aEnd), new, s(j:end)]; endif endif endfunction ## Normalize the inserted-text argument NEW to a cell array of character vectors ## NEWC plus a logical missing mask NEWMISS, each broadcast to the size SZ of ## STR. Mirrors the boundary-broadcasting rules of the insert/extract family. ## On invalid input returns a non-empty ERRMSG; the calling method emits the ## error under its own name. function [newc, newMiss, errmsg] = norm_new (new, sz) newc = {}; newMiss = []; errmsg = ''; if (isa (new, 'string')) newc = cellstr (new); newMiss = ismissing (new); elseif (ischar (new) || iscellstr (new)) ## Route char/cellstr through the constructor, which keeps trailing ## whitespace that bare 'cellstr' would deblank newc = cellstr (string (new)); newMiss = false (size (newc)); else errmsg = strcat ("NEW must be a string array, a character", ... " vector, or a cell array of character vectors."); return; endif [newc, errmsg] = eb_expand (newc, sz, 'NEW'); if (! isempty (errmsg)) return; endif newMiss = eb_expand (newMiss, sz, 'NEW'); endfunction ## Extract a single span of S between positions A and B. INCLUSIVE selects ## whether the characters at A and B are kept. On an out-of-range position ## returns a non-empty ERRMSG; the calling method emits the error under its own ## name. function [s, errmsg] = eb_span (s, a, b, inclusive) errmsg = ''; cp = str2cp (s); n = numel (cp); if (a != fix (a) || b != fix (b) || a < 1 || a > b || b > n) errmsg = "position indices out of range."; return; endif if (inclusive) s = cp2str (cp(a:b)); elseif (a + 1 <= b - 1) s = cp2str (cp((a + 1):(b - 1))); else s = ''; endif endfunction ## Collect the non-overlapping substrings of S that lie between successive START ## and END boundary substrings, scanning left to right. INCLUSIVE selects ## whether the boundary substrings themselves are included. function m = extract_between_matches (s, sp, ep, inclusive) m = {}; if (isempty (sp) || isempty (ep)) return; endif i = 1; n = numel (s); while (i <= n) si = strfind (s(i:end), sp); if (isempty (si)) break; endif si = i + si(1) - 1; # absolute start of START match aEnd = si + numel (sp) - 1; ei = strfind (s((aEnd + 1):end), ep); if (isempty (ei)) break; endif ei = aEnd + ei(1); # absolute start of END match if (inclusive) m{end+1} = s(si:(ei + numel (ep) - 1)); elseif (aEnd + 1 <= ei - 1) m{end+1} = s((aEnd + 1):(ei - 1)); else m{end+1} = ''; endif i = ei + numel (ep); # resume past the END match endwhile endfunction ## Convert a character vector to/from a row of Unicode code points (uint32), so ## that character positions index whole characters rather than UTF-8 bytes. function cp = str2cp (s) cp = typecast (unicode2native (s, 'UTF-32LE'), 'uint32'); endfunction function s = cp2str (cp) if (isempty (cp)) s = ''; else s = native2unicode (typecast (uint32 (cp), 'uint8'), 'UTF-32LE'); endif endfunction ## Collect the non-overlapping matches of PATS in the character vector S, ## scanning left to right. At each position the patterns are tried in order and ## the first that matches is taken; the scan then resumes past that match. function m = extract_matches (s, pats) m = {}; i = 1; n = numel (s); while (i <= n) hit = 0; for p = 1:numel (pats) L = numel (pats{p}); if (L > 0 && i + L - 1 <= n && strncmp (s(i:i+L-1), pats{p}, L)) hit = L; m{end+1} = pats{p}; break; endif endfor if (hit > 0) i += hit; else i += 1; endif endwhile endfunction ## Replace, in the character vector S, every occurrence of the OLDS substrings ## with the corresponding NEWS substrings, in a single left to right pass. At ## each position the OLDS are tried in order and the first that matches is ## replaced; the scan resumes past the inserted NEWS text, so replacements are ## not re-scanned. Unmatched runs are flushed in bulk to avoid quadratic ## growth. function s = replace_pairs (s, olds, news) parts = {}; i = 1; n = numel (s); last = 1; # start of the current unmatched run while (i <= n) hit = 0; for p = 1:numel (olds) L = numel (olds{p}); if (L > 0 && i + L - 1 <= n && strncmp (s(i:i+L-1), olds{p}, L)) parts{end+1} = s(last:(i - 1)); parts{end+1} = news{p}; hit = L; break; endif endfor if (hit > 0) i += hit; last = i; else i += 1; endif endwhile parts{end+1} = s(last:end); s = [parts{:}]; endfunction ## Split the character vector S at the DLMS delimiters, scanning left to right. ## At each position the delimiters are tried in order and the first that matches ## is taken; the scan resumes past it. Delimiters are not collapsed. Returns ## the PIECES (a 1xK row) and the MATCHES between them (a 1x(K-1) row). function [pieces, matches] = split_one (s, dlms) pieces = {}; matches = {}; i = 1; n = numel (s); last = 1; # start of the current piece while (i <= n) hit = 0; for d = 1:numel (dlms) L = numel (dlms{d}); if (L > 0 && i + L - 1 <= n && strncmp (s(i:i+L-1), dlms{d}, L)) pieces{end+1} = s(last:(i - 1)); matches{end+1} = dlms{d}; i += L; last = i; hit = 1; break; endif endfor if (! hit) i += 1; endif endwhile pieces{end+1} = s(last:end); endfunction ## Lay out the per-element pieces (the columns of cell matrix PC, with logical ## missing mask PM) along dimension DIM of an array whose other dimensions match ## the input size SZ. N is the number of pieces. Returns the cellstr OC and ## logical missing mask OM of the resulting array. function [oc, om] = split_place (pc, pm, sz, dim, N) nd = max (numel (sz), dim); outsz = [sz, ones(1, nd - numel (sz))]; outsz(dim) = N; oc = cell (outsz); om = false (outsz); idx = repmat ({':'}, 1, nd); shp = outsz; shp(dim) = 1; for n = 1:N idx{dim} = n; oc(idx{:}) = reshape (pc(:,n), shp); om(idx{:}) = reshape (pm(:,n), shp); endfor endfunction ## Strip the leading (when SL is true) and trailing (when SR is true) characters ## of the character vector S whose code points are listed in STRIPCPS. Works in ## code-point space so a multibyte strip character is matched as a whole. function s = strip_one (s, sl, sr, stripcps) cp = str2cp (s); lo = 1; hi = numel (cp); if (sl) while (lo <= hi && any (cp(lo) == stripcps)) lo += 1; endwhile endif if (sr) while (hi >= lo && any (cp(hi) == stripcps)) hi -= 1; endwhile endif s = cp2str (cp(lo:hi)); endfunction ## Pad the character vector S to N code points with the PADCP code point, on the ## given SIDE ('left', 'right', or 'both'). S is returned unchanged when it is ## already at least N long. For 'both', an odd count puts the extra on the ## right. function s = pad_one (s, N, side, padcp) cp = str2cp (s); total = max (N, numel (cp)) - numel (cp); if (total <= 0) return; endif switch (side) case 'left' lpad = total; rpad = 0; case 'right' lpad = 0; rpad = total; otherwise lpad = floor (total / 2); rpad = total - lpad; endswitch s = cp2str ([repmat(padcp, 1, lpad), cp, repmat(padcp, 1, rpad)]); endfunction ## Broadcast a non-scalar delimiter (cell array DC0 with missing mask DM0) to ## the target size TSZ (the size of STR with N-1 elements along DIM). The delim ## must already have N-1 along DIM; other dimensions of size 1 are replicated to ## match. Returns the broadcast DC/DM and a logical OK (false on a size clash). function [Dc, Dm, ok] = join_bcast (dc0, dm0, tsz, dim) Dc = {}; Dm = []; ok = true; nd = numel (tsz); dsz = [size(dc0), ones(1, nd - ndims (dc0))]; if (dsz(dim) != tsz(dim)) ok = false; return; endif reps = ones (1, nd); for d = 1:nd if (d == dim) continue; elseif (dsz(d) == tsz(d)) ## already matches elseif (dsz(d) == 1) reps(d) = tsz(d); else ok = false; return; endif endfor Dc = repmat (dc0, reps); Dm = repmat (dm0, reps); endfunction function out = cmp_uint32 (Acode, Bcode) A_n = numel (Acode); B_n = numel (Bcode); len = min (A_n, B_n); A_d = double (Acode(1:len)); B_d = double (Bcode(1:len)); out = sign (A_d - B_d); out = out(find (out, 1)); if (isempty (out)) out = sign (A_n - B_n); endif endfunction function out = sign_strings (A, B) fcn = @(x) typecast (unicode2native (x, 'UTF-32LE')(1:4*numel (x)), 'uint32'); Acode = cellfun (fcn, A, "UniformOutput", false); Acode(cellfun ('isempty', Acode)) = 0; Bcode = cellfun (fcn, B, "UniformOutput", false); Bcode(cellfun ('isempty', Bcode)) = 0; out = cellfun (@cmp_uint32, Acode, Bcode); endfunction ## Split a format specifier into an ordered list of literal and operator tokens. ## Each token is a scalar struct with the fields: ## 'type' : 'lit' for literal text or 'op' for a conversion operator ## 'text' : the corresponding piece of text from the format specifier ## 'nval' : number of values an operator consumes (1, plus 1 per '*'), or 0 ## The '%%' literal is collapsed to a single '%' and kept as a 'lit' token. function tok = compose_tokenize (fmt) pat = '%%|%[-+ #0]*(?:\d+|\*)?(?:\.(?:\d+|\*))?[diouxXeEfgGcs]'; [mt, sp] = regexp (fmt, pat, 'match', 'split'); tok = {}; for k = 1:numel (mt) if (! isempty (sp{k})) tok{end+1} = struct ('type', 'lit', 'text', sp{k}, 'nval', 0); endif if (strcmp (mt{k}, '%%')) tok{end+1} = struct ('type', 'lit', 'text', '%', 'nval', 0); else nstar = numel (strfind (mt{k}, '*')); tok{end+1} = struct ('type', 'op', 'text', mt{k}, 'nval', 1 + nstar); endif endfor if (! isempty (sp{end})) tok{end+1} = struct ('type', 'lit', 'text', sp{end}, 'nval', 0); endif endfunction ## Format a single string from a token list and a cell of input values. Each ## operator consumes its values in order; operators left without a ## corresponding value are emitted unchanged. Literal text has its escape ## sequences translated following the same rules as the 'sprintf' function. function out = compose_apply (tok, vals) parts = cell (1, numel (tok)); vi = 1; nv = numel (vals); for t = 1:numel (tok) if (strcmp (tok{t}.type, 'op') && vi + tok{t}.nval - 1 <= nv) args = vals(vi:vi + tok{t}.nval - 1); parts{t} = sprintf (tok{t}.text, args{:}); vi += tok{t}.nval; elseif (strcmp (tok{t}.type, 'op')) parts{t} = tok{t}.text; elseif (strcmp (tok{t}.text, '%')) parts{t} = '%'; else parts{t} = do_string_escapes (tok{t}.text); endif endfor out = ['', parts{:}]; endfunction pr0m1th3as-datatypes-9c9a8d3/inst/struct2ods.m000066400000000000000000000175061522766574100213350ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the datatypes package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {datatypes} {} struct2ods (@var{filename}, @var{s}) ## ## Write a scalar structure of tables to a multi-sheet OpenDocument spreadsheet. ## ## @code{struct2ods (@var{filename}, @var{s})} writes each field of the scalar ## structure @var{s} to its own sheet in the OpenDocument spreadsheet named by ## @var{filename}. Every field of @var{s} must hold a @code{table}; the field ## name becomes the sheet name. Both the compressed @qcode{.ods} and the flat ## @qcode{.fods} formats are supported, selected by the file extension. ## ## Full type fidelity is preserved through a hidden, sectioned ## @qcode{__datatypes_meta__} sheet, exactly as for the single-table ## @code{table2ods} method; the workbook round-trips through @code{ods2struct}. ## ## A field whose table carries an @qcode{'ActualSheetName'} custom property ## (see @code{addprop}) uses that value as the sheet name instead of the field ## name, which lets a sheet name that is not a valid identifier (for example ## @qcode{'Sales 2024'}) round-trip. Sheet names must be non-empty and must not ## contain any of the characters @qcode{[ ] * ? : / @backslashchar{}}, and the ## resolved names must be unique. ## ## @seealso{ods2struct, table2ods, ods2table, writetable} ## @end deftypefn function struct2ods (filename, s) if (nargin != 2) print_usage (); endif if (! ((ischar (filename) && isvector (filename)) ... || iscellstr (filename) || isa (filename, 'string'))) error (strcat ("struct2ods: FILENAME must be a character vector,", ... " cellstr, or string.")); endif file = char (cellstr (filename)); [~, ~, ext] = fileparts (file); if (strcmpi (ext, '.fods')) is_flat = true; elseif (strcmpi (ext, '.ods')) is_flat = false; else error (strcat ("struct2ods: FILENAME must have a '.ods' or '.fods'", ... " extension.")); endif if (! (isstruct (s) && isscalar (s))) error ("struct2ods: S must be a scalar structure."); endif fields = fieldnames (s); if (isempty (fields)) error ("struct2ods: S must have at least one field."); endif K = numel (fields); names = cell (1, K); datas = cell (1, K); vtypes = cell (1, K); metablocks = cell (1, K); for k = 1:K T = s.(fields{k}); if (! isa (T, 'table')) error ("struct2ods: field '%s' is not a table.", fields{k}); endif ## Resolve the sheet name: an 'ActualSheetName' custom property wins over ## the field name, so non-identifier sheet names can round-trip. sheetName = fields{k}; cp = T.Properties.CustomProperties; if (isstruct (cp) && isfield (cp, 'ActualSheetName') ... && ! isempty (cp.ActualSheetName)) sheetName = char (cp.ActualSheetName); endif if (! (ischar (sheetName) && isrow (sheetName) && ! isempty (sheetName))) error (strcat ("struct2ods: sheet name for field '%s' must be a", ... " non-empty character vector."), fields{k}); endif if (any (ismember (sheetName, '[]*?:/\'))) error (strcat ("struct2ods: sheet name '%s' contains an invalid", ... " character ([ ] * ? : / \\)."), sheetName); endif if (any (strcmp (sheetName, names(1:k-1)))) error ("struct2ods: duplicate sheet name '%s'.", sheetName); endif names{k} = sheetName; [datas{k}, vtypes{k}, metablocks{k}] = __ods_parts__ (T, 'struct2ods'); endfor ## Assemble the sectioned metadata grid: each table's metadata block preceded ## by a "## Sheet: " marker row, all padded to a common width. sections = cell (1, K); maxcols = 0; for k = 1:K mb = metablocks{k}; marker = [{['## Sheet: ' names{k}]}, repmat({''}, 1, max (0, columns (mb) - 1))]; sections{k} = [marker; mb]; maxcols = max (maxcols, columns (sections{k})); endfor metagrid = cell (0, maxcols); for k = 1:K sec = sections{k}; if (columns (sec) < maxcols) sec = [sec, repmat({''}, rows (sec), maxcols - columns (sec))]; endif metagrid = [metagrid; sec]; endfor opts = struct (); opts.sheets = struct ('name', names, 'data', datas, 'vtype', vtypes); opts.meta = metagrid; msg = __table2ods__ (file, {}, {}, {}, is_flat, opts); if (! isequal (msg, 0)) error ("struct2ods: %s", msg); endif endfunction %!demo %! ## `struct2ods` writes a whole workbook at once: each field of a scalar struct %! ## of tables becomes its own sheet, and the field name becomes the sheet name. %! %! wb.Patients = table ({'Li'; 'Diaz'}, [38; 40], 'VariableNames', {'Name', 'Age'}); %! wb.Visits = table ([1; 2; 3], 'VariableNames', {'Visit'}); %! filename = fullfile (tempdir (), 'clinic.ods'); %! struct2ods (filename, wb); %! %! ## The two fields are now two sheets, recoverable with `ods2struct`. %! fieldnames (ods2struct (filename)) %! %! delete (filename); %!test # round-trip a two-table workbook through ods2struct %! s.alpha = table ([1; 2; 3], {'a'; 'b'; 'c'}, 'VariableNames', {'x', 'y'}); %! s.beta = table ([10.5; 20.5], 'VariableNames', {'v'}); %! fn = [tempname() '.ods']; %! unwind_protect %! struct2ods (fn, s); %! r = ods2struct (fn); %! assert_equal (sort (fieldnames (r)), {'alpha'; 'beta'}); %! assert_equal (r.alpha.x, [1; 2; 3]); %! assert_equal (r.alpha.y, {'a'; 'b'; 'c'}); %! assert_equal (r.beta.v, [10.5; 20.5]); %! unwind_protect_cleanup %! delete (fn); %! end_unwind_protect %!test # native types (datetime, duration, int) round-trip per sheet %! s.one = table (datetime (2024, 1, [1; 2]), int32 ([5; 6]), ... %! 'VariableNames', {'d', 'n'}); %! s.two = table (seconds ([30; 90; 120]), 'VariableNames', {'t'}); %! fn = [tempname() '.fods']; %! unwind_protect %! struct2ods (fn, s); %! r = ods2struct (fn); %! assert_equal (class (r.one.d), 'datetime'); %! assert_equal (class (r.one.n), 'int32'); %! assert_equal (r.one.n, int32 ([5; 6])); %! assert_equal (class (r.two.t), 'duration'); %! assert_equal (isequaln (seconds (r.two.t), [30; 90; 120]), true); %! unwind_protect_cleanup %! delete (fn); %! end_unwind_protect %!test # a non-identifier sheet name round-trips via ActualSheetName %! T = table ([1; 2], 'VariableNames', {'v'}); %! T = addprop (T, 'ActualSheetName', 'table'); %! T.Properties.CustomProperties.ActualSheetName = 'Sales 2024'; %! s.sheet1 = T; %! fn = [tempname() '.ods']; %! unwind_protect %! struct2ods (fn, s); %! names = __ods2table__ (fn); # smoke: file is readable %! r = ods2struct (fn); %! fn2 = fieldnames (r); %! assert_equal (numel (fn2), 1); %! cp = r.(fn2{1}).Properties.CustomProperties; %! assert_equal (cp.ActualSheetName, 'Sales 2024'); %! unwind_protect_cleanup %! delete (fn); %! end_unwind_protect %!error ... %! struct2ods ('bad.txt', struct ('a', table (1))) %!error ... %! struct2ods ([tempname() '.ods'], struct ('a', {table(1), table(2)})) %!error ... %! struct2ods ([tempname() '.ods'], struct ('a', table (1), 'b', 5)) %!error ... %! struct2ods ([tempname() '.ods'], struct ()) pr0m1th3as-datatypes-9c9a8d3/inst/struct2table.m000066400000000000000000000150011522766574100216230ustar00rootroot00000000000000## Copyright (C) 2024-2026 Andreas Bertsatos ## ## This file is part of the datatypes package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {datatypes} {@var{tbl} =} struct2table (@var{S}) ## @deftypefnx {datatypes} {@var{tbl} =} struct2table (@var{S}, @var{Name}, @var{Value}) ## ## Convert a structure array to a table. ## ## @code{@var{tbl} = struct2table (@var{S})} converts a structure array @var{S} ## to the table @var{tbl}, where each field of the input structure becomes a ## variable in the output table. For a scalar structure with @math{N} fields, ## all of which have @math{M} rows, or an @math{M*1} or @math{1*M} structure ## array with @math{N} fields, the output is an @math{M*N} table. ## ## @code{@var{tbl} = struct2table (@var{S}, @var{Name}, @var{Value})} specifies ## optional parameters for creating the table @var{tbl} with the following ## Name-Value paired arguments. ## ## @multitable @columnfractions 0.23 0.75 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'AsArray'} @tab A logical scalar specifying whether to ## treat a scalar input as a structure array, which allows the fields containing ## data of different sizes. ## ## @item @qcode{'RowNames'} @tab A cell array of character vectors or ## a string array defining the row names of @var{tbl}. The names must be unique ## but not necessarily valid variable names. ## ## @item @qcode{'DimensionNames'} @tab A cell array of character vectors or ## a string array defining the dimension names of @var{tbl}. The names must be ## unique and not in conflict with variable names. By default, dimension names ## are @qcode{'Row', 'Variables'}. ## @end multitable ## ## @seealso{array2table, cell2table, table} ## @end deftypefn function tbl = struct2table (S, varargin) ## Check input is a struct scalar of vector if (! isstruct (S)) error ("struct2table: input array must be a structure."); endif ## Handle default value for AsArray if (isscalar (S)) default = false; else default = true; endif ## Parse optional Name-Value paired arguments optNames = {'AsArray', 'RowNames', 'DimensionNames'}; dfValues = {default, {}, {"Row", "Variables"}}; [AsArray, rowNames, dimNames, args] = ... parsePairedArguments (optNames, dfValues, varargin); ## Get variable names from structure fields varNames = fieldnames (S); optArgs = {'VariableNames', varNames}; ## Get variable data from structure varN = numel (varNames); varValues = cell (varN, 1); if (AsArray) for i = 1:varN try varValues{i} = cat (1, S(:).(varNames{i})); catch varValues{i} = cat (1, {S(:).(varNames{i})})'; end_try_catch endfor nrows = cellfun (@rows, varValues); if (! all (ismember (nrows, numel (S)))) for i = 1:varN varValues{i} = cat (1, {S(:).(varNames{i})})'; endfor endif else vals = struct2cell (S); for i = 1:varN try varValues{i} = cat (1, vals{i,1,:}); catch varValues{i} = cat (1, vals(i,1,:)); end_try_catch endfor nrows = cellfun (@rows, varValues); if (! all (ismember (nrows, nrows(1)))) error ("struct2table: fields have different rows. Use 'AsArray' option."); endif endif ## Handle remaining paired arguments if (! isempty (rowNames)) if (numel (rowNames) != nrows) error (strcat ("struct2table: 'RowNames' must match the rows", ... " in input structure.")); endif optArgs = [optArgs {'RowNames', rowNames}]; endif if (! isempty (dimNames)) if (numel (dimNames) != 2) error ("struct2table: 'DimensionNames' must be a two-element vector."); endif optArgs = [optArgs {'DimensionNames', dimNames}]; endif ## Construct table tbl = table (varValues{:}, optArgs{:}); endfunction %!demo %! ## `struct2table` maps a struct to a table. A struct *array* becomes one row %! ## per element; a *scalar* struct whose fields are equal-length columns becomes %! ## one row per element of those columns. Both give the same table here. %! %! S = struct ('Name', {'Li'; 'Diaz'; 'Brown'}, 'Age', {38; 40; 49}); %! struct2table (S) %!demo %! ## When a scalar struct holds fields of *unequal* length, use `'AsArray', true` %! ## to wrap the whole struct as a single table row, each field one variable. %! %! S.Values = [1, 2, 3]; %! S.Label = 'demo'; %! struct2table (S, 'AsArray', true) %!test %! S(1).A = [1, 2]; %! S(2).A = [3, 4]; %! S(1).B = 5; %! S(2).B = 6; %! tbl = struct2table (S); %! assert_equal (tbl.A, [1, 2; 3, 4]); %! assert_equal (tbl.B, [5; 6]); %! assert_equal (numel (S), rows (tbl)); %!test %! S(1).A = [1, 2]; %! S(2).A = [3, 4, 5, 6]; %! S(1).B = 7; %! S(2).B = 8; %! tbl = struct2table (S); %! assert_equal (isa (tbl.A, 'cell'), true); %! assert_equal (isa (tbl.B, 'double'), true); %! assert_equal (numel (S), rows (tbl)); %! assert_equal (tbl.A(1){1}, [1, 2]); %! assert_equal (tbl.A(2){1}, [3, 4, 5, 6]); %! assert_equal (tbl.B, [7; 8]); %!test %! S.A = [1; 2; 3]; %! S.B = [4, 5; 6, 7; 8, 9]; %! tbl = struct2table (S); %! assert_equal (isa (tbl.A, 'double'), true); %! assert_equal (isa (tbl.B, 'double'), true); %! assert_equal (tbl.A, [1; 2; 3]); %! assert_equal (tbl.B, [4, 5; 6, 7; 8, 9]); %!test %! tbl = struct2table (struct ('A', 1, 'B', [1; 2]), 'AsArray', true); %! assert_equal (iscell (tbl.A), true); %! assert_equal (iscell (tbl.B), true); %! assert_equal (size (tbl.B{1}), [2, 1]); %!error struct2table ({1}); %!error ... %! struct2table (struct ('A', 1, 'B', [1; 2])); %!error ... %! struct2table (struct ('A', {'a';, 'b'}, 'B', [1; 2]), 'RowNames', 'q'); %!error ... %! struct2table (struct ('A', {'a';, 'b'}, 'B', [1; 2]), 'DimensionNames', 'q'); pr0m1th3as-datatypes-9c9a8d3/inst/struct2xlsx.m000066400000000000000000000143351522766574100215430ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the datatypes package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {datatypes} {} struct2xlsx (@var{filename}, @var{s}) ## ## Write a scalar structure of tables to a multi-sheet Excel workbook. ## ## @code{struct2xlsx (@var{filename}, @var{s})} writes each field of the scalar ## structure @var{s} to its own sheet in the Office Open XML workbook named by ## @var{filename} (@qcode{.xlsx} or @qcode{.xlsm}). Every field must hold a ## @code{table}; the field name becomes the sheet name. ## ## This is the Excel counterpart of @code{struct2ods}. Like @code{writetable}, ## it writes the MATLAB-interoperable format (a variable-name header row followed ## by the data, with no hidden type metadata); read it back with ## @code{xlsx2struct}. A field whose table carries an @qcode{'ActualSheetName'} ## custom property uses that value as the sheet name instead of the field name. ## Sheet names must be non-empty, at most 31 characters, and must not contain any ## of the characters @qcode{[ ] * ? : / @backslashchar{}}; the resolved names ## must be unique. ## ## @seealso{xlsx2struct, struct2ods, writetable, readtable} ## @end deftypefn function struct2xlsx (filename, s) if (nargin != 2) print_usage (); endif if (! ((ischar (filename) && isvector (filename)) ... || iscellstr (filename) || isa (filename, 'string'))) error (strcat ("struct2xlsx: FILENAME must be a character vector,", ... " cellstr, or string.")); endif file = char (cellstr (filename)); [~, ~, ext] = fileparts (file); if (! any (strcmpi (ext, {'.xlsx', '.xlsm'}))) error ("struct2xlsx: FILENAME must have a '.xlsx' or '.xlsm' extension."); endif if (! (isstruct (s) && isscalar (s))) error ("struct2xlsx: S must be a scalar structure."); endif fields = fieldnames (s); if (isempty (fields)) error ("struct2xlsx: S must have at least one field."); endif K = numel (fields); names = cell (1, K); datas = cell (1, K); vtypes = cell (1, K); headers = cell (1, K); for k = 1:K T = s.(fields{k}); if (! isa (T, 'table')) error ("struct2xlsx: field '%s' is not a table.", fields{k}); endif sheetName = fields{k}; cp = T.Properties.CustomProperties; if (isstruct (cp) && isfield (cp, 'ActualSheetName') ... && ! isempty (cp.ActualSheetName)) sheetName = char (cp.ActualSheetName); endif if (! (ischar (sheetName) && isrow (sheetName) && ! isempty (sheetName))) error (strcat ("struct2xlsx: sheet name for field '%s' must be a", ... " non-empty character vector."), fields{k}); endif if (numel (sheetName) > 31) error (strcat ("struct2xlsx: sheet name '%s' exceeds the 31-character", ... " Excel limit."), sheetName); endif if (any (ismember (sheetName, '[]*?:/\'))) error (strcat ("struct2xlsx: sheet name '%s' contains an invalid", ... " character ([ ] * ? : / \\)."), sheetName); endif if (any (strcmp (sheetName, names(1:k-1)))) error ("struct2xlsx: duplicate sheet name '%s'.", sheetName); endif names{k} = sheetName; [headers{k}, datas{k}, vtypes{k}] = __interop_parts__ (T, 'struct2xlsx'); endfor opts = struct (); opts.sheets = struct ('name', names, 'data', datas, 'vtype', vtypes, ... 'header', headers); opts.macro = strcmpi (ext, '.xlsm'); msg = __table2xlsx__ (file, {}, {}, opts); if (! isequal (msg, 0)) error ("struct2xlsx: %s", msg); endif endfunction %!demo %! ## `struct2xlsx` is the Excel counterpart of `struct2ods`: each field of a %! ## scalar struct of tables is written as its own worksheet in an `.xlsx` file. %! %! wb.Patients = table ({'Li'; 'Diaz'}, [38; 40], 'VariableNames', {'Name', 'Age'}); %! wb.Visits = table ([1; 2; 3], 'VariableNames', {'Visit'}); %! filename = fullfile (tempdir (), 'clinic.xlsx'); %! struct2xlsx (filename, wb); %! %! ## Read it back with `xlsx2struct` to recover the same field names. %! fieldnames (xlsx2struct (filename)) %! %! delete (filename); %!test # round-trip a two-table workbook through xlsx2struct %! s.alpha = table ([1; 2; 3], {'a'; 'b'; 'c'}, 'VariableNames', {'x', 'y'}); %! s.beta = table ([10.5; 20.5], 'VariableNames', {'v'}); %! fn = [tempname() '.xlsx']; %! unwind_protect %! struct2xlsx (fn, s); %! r = xlsx2struct (fn); %! assert_equal (fieldnames (r), {'alpha'; 'beta'}); %! assert_equal (r.alpha.x, [1; 2; 3]); %! assert_equal (r.alpha.y, {'a'; 'b'; 'c'}); %! assert_equal (r.beta.v, [10.5; 20.5]); %! unwind_protect_cleanup %! delete (fn); %! end_unwind_protect %!test # native datetime/duration round-trip per sheet %! s.one = table (datetime (2024, 1, [1; 2]), 'VariableNames', {'d'}); %! s.two = table (seconds ([30; 90]), 'VariableNames', {'t'}); %! fn = [tempname() '.xlsx']; %! unwind_protect %! struct2xlsx (fn, s); %! r = xlsx2struct (fn); %! assert_equal (class (r.one.d), 'datetime'); %! assert_equal (isequaln (datevec (r.one.d), datevec (s.one.d)), true); %! assert_equal (class (r.two.t), 'duration'); %! assert_equal (isequaln (seconds (r.two.t), [30; 90]), true); %! unwind_protect_cleanup %! delete (fn); %! end_unwind_protect %!error ... %! struct2xlsx ('bad.ods', struct ('a', table (1))) %!error ... %! struct2xlsx ([tempname() '.xlsx'], struct ('a', {table(1), table(2)})) %!error ... %! struct2xlsx ([tempname() '.xlsx'], struct ('a', table (1), 'b', 5)) pr0m1th3as-datatypes-9c9a8d3/inst/table.m000066400000000000000000016666331522766574100203240ustar00rootroot00000000000000## Copyright (C) 2024-2026 Andreas Bertsatos ## ## This file is part of the datatypes package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . classdef table ## -*- texinfo -*- ## @deftp {datatypes} table ## ## Array of tabular data containing multiple columnar variables. ## ## A table is a 2-dimensional data structure that collects heterogeneous data ## and metadata into a single container. Tables are suitable for storing ## columnar data much like spreadsheets but they can also be used for storing ## more complex data including multicolumnar variables and nested tables. ## ## Tables can be subscripted using parentheses like ordinary numeric arrays, ## but in addition to indexing with numeric and logical vectors, you can also ## use the table's variable or row names much like indexing a structure field ## as well as using a @qcode{vartype} class object to make a selection of ## variable types. While these methods will return a subset of the original ## table, you can also use curly brackets much like cell arrays to retrieve ## the contents of the table. In this case, the original data types of the ## selected variables are returned. ## ## Besides the @code{table} constructor, you can also use @code{array2table}, ## @code{cell2table}, and @code{struct2table} to create tables from the ## respective data types. ## ## Besides all numeric data types, other supported data types that can be ## stored in a table array are @qcode{logical}, @qcode{categorical}, ## @qcode{cell}, (including @qcode{cellstr}), @qcode{calendarDuration}, ## @qcode{duration}, @qcode{datetime}, @qcode{string}, and @qcode{struct} ## arrays, as well as @qcode{table} itself. ## ## @seealso{vartype, array2table, cell2table, struct2table} ## @end deftp properties ## -*- texinfo -*- ## @deftp {table} {property} Description ## ## Table description ## ## Table description specified as a character vector or a string scalar. ## If specified as a string scalar, it is converted and stored internally ## as a character vector. You can access the @qcode{Description} property ## of a table @var{tbl} with @qcode{@var{tbl}.Properties.Description}. ## ## @end deftp Description = '' ## -*- texinfo -*- ## @deftp {table} {property} UserData ## ## Additional table information ## ## Additional table information, specified as an array. Any type of data ## can be attached using this property. You can access the @qcode{UserData} ## property of a table @var{tbl} with @qcode{@var{tbl}.Properties.UserData}. ## ## @end deftp UserData = [] ## -*- texinfo -*- ## @deftp {table} {property} DimensionNames ## ## Dimension names ## ## Dimension names specified as a two-element cell array of character ## vectors or a two-element string array. If specified as a string array, ## it is converted and stored internally as a cell array of character ## vectors. You can access the @qcode{DimensionNames} property of a table ## @var{tbl} with @qcode{@var{tbl}.Properties.DimensionNames}. ## ## By default, @qcode{DimensionNames} is specified as ## @qcode{'Row', 'Variables'}. You can access table data per rows or per ## columns by using either one of the two dimension names, respectively. ## However, if the table contains row names, then the first element of the ## @qcode{DimensionNames} corresponds to the row names. ## ## @end deftp DimensionNames = {'Row', 'Variables'} ## -*- texinfo -*- ## @deftp {table} {property} VariableNames ## ## Variable names ## ## Variable names, specified as a cell array of character vectors or a ## string array. If specified as a string array, it is converted and stored ## internally as a cell array of character vectors. All elements must be ## nonempty and distinct, and their number must equal the number of ## variables. You can access the data type of a specific variable by using ## dot name assignment, as in @qcode{@var{tbl}.@var{varname}}, where ## @var{varname} is the name of the variable in table @var{tbl}. If the ## variable name does not exist, a new one is created. ## ## @end deftp VariableNames = {} ## -*- texinfo -*- ## @deftp {table} {property} VariableTypes ## ## Variable data types ## ## The class of the data of each variable, defined as a cell array of ## character vectors or a string array with the same number of elements as ## the number of variables in the table. If specified as a string array, ## it is converted and stored internally as a cell array of character ## vectors. You can access the @qcode{VariableTypes} property of a table ## @var{tbl} with @qcode{@var{tbl}.Properties.VariableTypes}. You can ## further index specific variables to access their data type. Modifying ## the elements of the @qcode{VariableTypes} property automatically converts ## the underlying data of the corresponding variable into the specified ## data types provided that a valid conversion is requested. ## ## @end deftp VariableTypes = {} ## -*- texinfo -*- ## @deftp {table} {property} VariableDescriptions ## ## Variable descriptions ## ## Variable descriptions, specified as a cell array of character vectors or ## a string array. If specified as a string array, it is converted and ## stored internally as a cell array of character vectors. If not empty ## (default), it must contain the same number of elements as the number of ## variables. If a specific variable does not have a description, this can ## be specified with an individual empty character vector or an empty ## string. You can access the @qcode{VariableDescriptions} property of a ## table @var{tbl} with @qcode{@var{tbl}.Properties.VariableDescriptions}. ## You can further index specific variables to access their description. ## ## @end deftp VariableDescriptions = {} ## -*- texinfo -*- ## @deftp {table} {property} VariableUnits ## ## Variable units ## ## Variable units, specified as a cell array of character vectors or a ## string array. If specified as a string array, it is converted and stored ## internally as a cell array of character vectors. If not empty (default), ## it must contain the same number of elements as the number of variables. ## If a specific variable does not have a unit, this can be specified with ## an individual empty character vector or an empty string. You can access ## the @qcode{VariableUnits} property of a table @var{tbl} with ## @qcode{@var{tbl}.Properties.VariableUnits}. You can further index ## specific variables to access their unit. ## ## @end deftp VariableUnits = {} ## -*- texinfo -*- ## @deftp {table} {property} RowNames ## ## Row names ## ## Row names, specified as a cell array of character vectors or a string ## array. If specified as a string array, it is converted and stored ## internally as a cell array of character vectors. If not empty (default), ## it must contain the same number of elements as the number of rows in the ## table. All elements must be nonempty and distinct. You can access the ## rows of the table @var{tbl} by specifying one or more row names within ## parentheses or curly braces. You can also set @qcode{RowNames} by ## dot name assignment to an existing variable. ## ## @end deftp RowNames = {} ## -*- texinfo -*- ## @deftp {table} {property} CustomProperties ## ## Customized metadata of table and its variables ## ## Custom properties that contain metadata of a table and its variables. ## By default, this is an empty container. Each custom property holds ## either table metadata or per-variable metadata, according to the property ## type (@qcode{'table'} or @qcode{'variable'}) specified when the property ## is created with the @code{addprop} method. A variable-scoped property ## holds one element per variable. ## ## You can add custom properties only by using the @code{addprop} method and ## you can only remove a custom property with the @code{rmprop} method. To ## access existing custom properties use dot name structure assignment as in ## @qcode{@var{tbl}.Properties.CustomProperties.@var{PropertyName}}, where ## @var{PropertyName} is the name used with the @code{addprop} method. ## ## @end deftp CustomProperties = [] endproperties properties (GetAccess = private, SetAccess = protected) CustomPropTypes = {} VariableValues = {} endproperties methods (Hidden) ## Custom display function display (this) in_name = inputname (1); if (! isempty (in_name)) fprintf ("%s =\n", in_name); endif disp (this); endfunction ## Custom display function disp (this) if (isempty (this)) fprintf (" %dx%d empty table\n\n", size (this)); else fprintf (" %dx%d table\n\n", height (this), width (this)); print_table (this); endif endfunction endmethods ################################################################################ ## ** Create Table and Convert Type ** ## ################################################################################ ## Available Methods ## ## ## ## 'table' 'table2array' 'table2cell' 'table2struct' ## ## ## ################################################################################ methods (Access = public) ## -*- texinfo -*- ## @deftypefn {table} {@var{tbl} =} table (@var{var1}, @var{var2}, @dots{}, @var{varN}) ## @deftypefnx {table} {@var{tbl} =} table (@qcode{'Size'}, @var{sz}, @qcode{'VariableTypes'}, @var{varTypes}) ## @deftypefnx {table} {@var{tbl} =} table (@dots{}, @qcode{'VariableNames'}, @var{varNames}) ## @deftypefnx {table} {@var{tbl} =} table (@dots{}, @qcode{'RowNames'}, @var{rowNames}) ## @deftypefnx {table} {@var{tbl} =} table (@dots{}, @qcode{'DimensionNames'}, @var{dimNames}) ## ## Create a new table. ## ## @code{@var{tbl} = table (@var{var1}, @var{var2}, @dots{}, @var{varN})} ## creates a new table with the given variables. The variables passed as ## input arguments become the variables of the table. Their names are ## automatically detected from the input variable names that you used. ## ## @code{@var{tbl} = table (@qcode{'Size'}, @var{sz}, ## @qcode{'VariableTypes'}, @var{varTypes})} creates a new table of the ## given size, @var{sz}, and with the given variable types, @var{varTypes}. ## @var{sz} must be a two-element numeric array, where @qcode{@var{sz}(1)} ## specifies the number of rows and @qcode{@var{sz}(2)} specifies the ## number of variables. The variables will contain the default value for ## elements of that type. ## ## @code{@var{tbl} = table (@dots{}, @qcode{'VariableNames'}, ## @var{varNames})} specifies the variable names to use in the constructed ## table. @var{varNames} must be either a cell array of character vectors ## or a string array with the same number of nonempty and unique elements as ## the number of table variables. ## ## @code{@var{tbl} = table (@dots{}, @qcode{'RowNames'}, @var{rowNames})} ## specifies the row names to use in the constructed table. @var{rowNames} ## must be either a cell array of character vectors or a string array with ## the same number of nonempty and unique elements as the number of rows in ## the table. ## ## @code{@var{tbl} = table (@dots{}, @qcode{'DimensionNames'}, ## @var{dimNames})} specifies the dimension names to use in the constructed ## table. @var{dimNames} must be either a two-element cell array of ## character vectors or a two-element string array with nonempty and unique ## elements. ## ## @code{@var{tbl} = table ()} returns an empty table with 0 rows and 0 ## variables. ## ## @end deftypefn function this = table (varargin) ## Return an empty table object if (nargin == 0) return endif ## Parse optional Name-Value paired arguments optNames = {'VariableNames', 'RowNames', 'DimensionNames'}; dfValues = {{}, {}, {'Row', 'Variables'}}; [VariableNames, RowNames, DimensionNames, args] = ... parsePairedArguments (optNames, dfValues, varargin(:)); ## Check optional Name-Value paired arguments if (! isempty (VariableNames)) if (! (iscellstr (VariableNames) || isa (VariableNames, 'string'))) error (strcat ("table: 'VariableNames' must be either a cell", ... " array of character vectors or a string array.")); endif VariableNames = cellstr (VariableNames); if (any (cellfun (@isempty, VariableNames))) error ("table: 'VariableNames' must contain nonempty names."); endif endif if (! isempty (RowNames)) if (! (iscellstr (RowNames) || isa (RowNames, 'string'))) error (strcat ("table: 'RowNames' must be either a cell array", ... " of character vectors or a string array.")); endif RowNames = cellstr (RowNames); endif if (! (iscellstr (DimensionNames) || isa (DimensionNames, 'string')) || numel (DimensionNames) != 2) error (strcat ("table: 'DimensionNames' must be either a", ... " two-element cell array of character vectors or", ... " a two-element string array.")); endif this.DimensionNames = cellstr (DimensionNames); ## Dimension names cannot match reserved table identifiers reserved = {'Properties', 'RowNames', 'VariableNames', ':'}; idr = ismember (this.DimensionNames, reserved); if (any (idr)) error (strcat ("table: 'DimensionNames' cannot include the", ... " reserved name: '%s'."), this.DimensionNames{idr}); endif ## Check for conflict between VariableNames and DimensionNames idx = ismember (this.DimensionNames, VariableNames); if (any (idx)) error ("table: duplicate dimension and variable name: '%s'", ... this.DimensionNames{idx}); endif ## Construct a preallocated table with default values if (numel (args) == 4 && strcmpi (args{1}, 'Size') && strcmpi (args{3}, 'VariableTypes')) ## Validate the size specifier if (! isnumeric (args{2}) || numel (args{2}) != 2) error ("table: 'Size' must be a two-element numeric vector."); endif ## Get number of rows and variables nr = args{2}(1); nv = args{2}(2); ## Get variable types varTypes = args{4}; if (! iscellstr (varTypes) || numel (varTypes) != nv) error (strcat ("table: 'VariableTypes' must be a cellstring", ... " array of the same number of elements as", ... " defined in SZ(2).")); endif ## Check optional arguments if (! isempty (VariableNames) && numel (VariableNames) != nv) error (strcat ("table: inconsistent number of 'VariableNames'", ... " and 'VariableTypes'.")); elseif (isempty (VariableNames)) VariableNames = cell (1, nv); for i = 1:nv VariableNames{i} = sprintf ("Var%d", i); endfor endif if (! isempty (RowNames) && numel (RowNames) != nr) error (strcat ("table: inconsistent number of 'RowNames' and", ... " rows defined in SZ(1).")); endif ## Populate variables with defaults VariableTypes = cell (1, nv); VariableValues = cell (1, nv); for i = 1:nv VariableTypes{i} = varTypes{i}; switch (varTypes{i}) case {'double', 'single', 'int8', 'uint8', 'int16', 'uint16', ... 'int32', 'uint32', 'int64', 'uint64'} VariableValues{i} = zeros (nr, 1, varTypes{i}); case {'doublenan', 'doubleNaN'} VariableValues{i} = NaN (nr, 1, 'double'); case {'singlenan', 'singleNaN'} VariableValues{i} = NaN (nr, 1, 'single'); case 'logical' VariableValues{i} = logical (zeros (nr, 1)); case 'categorical' VariableValues{i} = categorical (NaN (nr, 1)); case 'datetime' VariableValues{i} = NaT (nr, 1); case 'duration' VariableValues{i} = seconds (zeros (nr, 1)); case 'calendarDuration' VariableValues{i} = calendarDuration (zeros (nr, 3)); case 'string' VariableValues{i} = string (NaN (nr, 1)); case {'cellstr', 'char'} VariableValues{i} = repmat (cellstr (""), nr, 1); case 'cell' VariableValues{i} = cell (nr, 1); case 'struct' VariableValues{i} = repmat (struct, nr, 1); case 'table' VariableValues{i} = table([]); case 'timetable' error ("table: 'timetable' variable type not supported yet."); otherwise error ("table: unsupported variable type: '%s'.", varTypes{i}); endswitch endfor ## Construct a table with data from input arguments else ## Get variable names from input arguments if (isempty (VariableNames)) VariableNames = cell (size (args)); for i = 1:numel (args) VariableNames{i} = inputname (i); if (isempty (VariableNames{i})) VariableNames{i} = sprintf ("Var%d", i); endif endfor endif ## Check for unique names in input arguments [uqNames, ix] = __unique__ (VariableNames); if (numel (uqNames) < numel (VariableNames)) ixBad = 1:numel (VariableNames); ixBad(ix) = []; error ("table: duplicate variable names: %s", ... strjoin (VariableNames(ixBad), ", ")); endif ## Check number of variable names and input arguments if (numel (VariableNames) != numel (args)) error (strcat ("table: inconsistent number of variable names", ... " (%d) and variable values (%d)"), ... numel (VariableNames), numel (args)); endif ## Check size of input variables if (! isempty (args)) nrows = size (args{1}, 1); if (ndims (args{1}) > 2) error (strcat ("table: variable values must not have more", ... " than 2 dimensions: input 1 '%s' has %d."), ... VariableNames{1}, ndims (args{1})); endif for i = 2:numel (args) if (ndims (args{i}) > 2) error (strcat ("table: variable values must not have more", ... " than 2 dimensions: input %d '%s' has %d."), ... i, VariableNames{i}, ndims (args{i})); endif nrows2 = size (args{i}, 1); if (nrows != nrows2) error (strcat ("table: inconsistent sizes between", ... " variables: var '%s' has %d rows; var '%s'", ... " has %d rows."), ... VariableNames{1}, nrows, VariableNames{i}, nrows2); endif endfor endif VariableValues = args(:)'; endif ## Construction this.VariableDescriptions = repmat ({''}, [1, numel(VariableNames)]); this.VariableUnits = repmat ({''}, [1, numel(VariableNames)]); this.VariableNames = VariableNames(:)'; this.VariableValues = VariableValues; this.VariableTypes = cellfun ('class', VariableValues, ... 'UniformOutput', false); if (! isempty (RowNames)) if (isempty (VariableValues)) nrows = 0; else nrows = size (VariableValues{1}, 1); endif if (numel (RowNames) != nrows) error (strcat ("table: the number of 'RowNames' (%d) must", ... " equal the number of rows (%d)."), ... numel (RowNames), nrows); elseif (numel (__unique__ (RowNames)) != numel (RowNames)) error ("table: elements in 'RowNames' must be unique."); endif this.RowNames = RowNames(:); endif endfunction ## -*- texinfo -*- ## @deftypefn {table} {@var{A} =} table2array (@var{tbl}) ## ## Converts a table to a homogeneous array. ## ## @end deftypefn function A = table2array (this) ## Handle empty table if isempty (this) A = []; return endif ## A mix of cell and non-cell variables cannot form a homogeneous array. ## Octave would silently promote single-row pieces to a cell (MATLAB ## errors), so guard explicitly and report the first incompatible pair. pair = mixed_cell_pair (this.VariableValues); if (! isempty (pair)) error (strcat ("table.table2array: cannot concatenate the table", ... " variables '%s' and '%s', because their types are", ... " %s and %s."), this.VariableNames{pair(1)}, ... this.VariableNames{pair(2)}, ... class (this.VariableValues{pair(1)}), ... class (this.VariableValues{pair(2)})); endif ## Add a try...catch block instead of heuristics try A = cat (2, this.VariableValues{:}); catch error (strcat ("table.table2array: table cannot be concatenated", ... " into a matrix due to incompatible variable", ... " types.")); end_try_catch endfunction ## -*- texinfo -*- ## @deftypefn {table} {@var{C} =} table2cell (@var{tbl}) ## ## Converts a table to a cell array. ## ## Each variable in @var{tbl} becomes a column of cells in the output ## @var{C}. Multicolumnar variables are returned in a single column with ## each cell element containing a row vector. ## ## The size of the returned cell array, @var{C}, is the same as the input ## table, @var{tbl}. The output @var{C} does not include any of the table's ## properties. This also applies to row names. ## ## Compatibility Notes: ## ## Variables of types @qcode{categorical}, @qcode{calendarDuration}, ## @qcode{datetime}, @qcode{duration} and @qcode{string} are returned as ## in their printed representation as character vectors. To revert them to ## their original class type you can parse the cell elements to the ## respective object constructor. ## ## Nested tables are handled as multicolumnar variables only if they contain ## data types, which can be converted to homogeneous array, i.e. numerical ## logical values. Other data types will result to a warning due to ## implicit conversion from numeric to char and the returned values will ## not contain all values from the nested table. ## ## @end deftypefn function C = table2cell (this, varargin) C = cell (size (this)); for i = 1:width (this) varVal = this.VariableValues{i}; if (iscell (varVal)) C(:,i) = varVal; elseif (isnumeric (varVal) || islogical (varVal)) C(:,i) = num2cell (varVal, 2); elseif (any (isa (varVal, {'calendarDuration', 'categorical'}))) C(:,i) = dispstrings (varVal); elseif (any (isa (varVal, {'datetime', 'duration'}))) C(:,i) = dispstrings (varVal); elseif (isa (varVal, 'string')) C(:,i) = cellstr (varVal); elseif (isa (varVal, 'table')) tmpVal = table2cell (varVal); if (size (tmpVal, 2) > 1) C(:,i) = num2cell (cell2mat (tmpVal), 2); else C(:,i) = tmpVal; endif elseif (isa (varVal, 'struct')) C(:,i) = num2cell (varVal(:)); endif endfor endfunction ## -*- texinfo -*- ## @deftypefn {table} {@var{S} =} table2struct (@var{tbl}) ## @deftypefnx {table} {@var{S} =} table2struct (@var{tbl}, @qcode{'ToScalar'}, @qcode{true}) ## ## Converts a table to a scalar structure or structure array. ## ## @code{@var{S} = table2struct (@var{tbl})} returns a structure array with ## the same fields as the variables in @var{tbl}. The length of @var{S} is ## the same as the height of @var{tbl}. ## ## @code{@var{S} = table2struct (@var{tbl}, @qcode{'ToScalar'}, ## @qcode{true})} returns a scalar structure with the same fields as the ## variables in @var{tbl}. Each field has the same rows as the @var{tbl}. ## ## The output @var{S} does not include any of the table's properties. This ## also applies to row names. ## ## @end deftypefn function S = table2struct (this, varargin) ## Add defaults toScalar = false; ## Check optional input arguments if (nargin > 1) if (nargin != 3) error ("table.table2struct: wrong number of input arguments."); endif if (strcmpi (varargin{1}, 'ToScalar') && isequal (varargin{2}, 1)) toScalar = true; elseif (strcmpi (varargin{1}, 'ToScalar')) toScalar = false; else error ("table.table2struct: wrong optional input argument."); endif endif ## Do the conversion if (toScalar) S = struct; for i = 1:width (this) S.(this.VariableNames{i}) = this.VariableValues{i}; endfor else C = table2cell (this); ## 'table2cell' renders categorical, datetime, duration, ## calendarDuration, and string variables as character vectors; restore ## the original typed values so the structure array preserves the ## variable types, consistent with the 'ToScalar' output and MATLAB. for i = 1:width (this) vv = this.VariableValues{i}; if (any (isa (vv, {'categorical', 'datetime', 'duration', ... 'calendarDuration', 'string'}))) for r = 1:size (vv, 1) C{r,i} = vv(r,:); endfor endif endfor F = this.VariableNames(:); S = cell2struct (C, F, 2); endif endfunction endmethods ################################################################################ ## ** Save to Files ** ## ################################################################################ ## Available Methods ## ## ## ## 'table2csv' 'table2ods' 'writetable' ## ## ## ################################################################################ methods (Access = public) ## -*- texinfo -*- ## @deftypefn {table} {} table2csv (@var{tbl}, @var{file}) ## ## Write a table to a comma-separated-value (CSV) file. ## ## @code{table2csv (@var{tbl}, @var{file})} writes the table @var{tbl} to ## @var{file}, which may be a character vector, a cellstr, or a string ## scalar. The resulting file can be read back with @code{csv2table}. ## ## The file begins with a comment line reporting how many consecutive rows ## hold the variable types, names, descriptions, and units, in that order. ## Those header rows are followed by one row of data per table row. ## ## Variables are serialized as follows: ## ## @itemize ## @item ## Numeric and logical variables are written as numbers (logicals as ## @code{0}/@code{1}). Missing and infinite values are written as the ## tokens @qcode{NaN}, @qcode{NA}, @qcode{inf}, and @qcode{-inf}. ## ## @item ## Character, cellstr, and @code{string} variables are written as quoted ## text. ## ## @item ## @code{datetime}, @code{duration}, @code{calendarDuration}, and ## @code{categorical} variables are written as their display strings. ## ## @item ## A multicolumn variable is split into consecutive columns that share the ## same variable name. ## ## @item ## A nested table is split into columns tagged with both the outer and the ## nested variable name. A structure is split into one column per field, ## tagged with the variable name and the field name. ## @end itemize ## ## When @var{tbl} has row names they are written under a leading ## @qcode{RowNames} column. Variable descriptions and units are written ## whenever @emph{any} variable has a non-empty description or unit, ## respectively (the others are left empty). ## ## Note the following round-trip limitations when reading the file back ## with @code{csv2table}: @code{calendarDuration} and @code{categorical} ## variables are returned as cell arrays of character vectors (their values ## are not reconstructed), missing @code{string} values are read back as ## empty strings, and datetime and duration display formats are not ## preserved, although the values themselves are exact. ## ## @end deftypefn function table2csv (this, file) file = char (cellstr (file)); [V, N, T, D, U] = table2cellarrays (this); ## Get columns for final cell array Ccols = size (V, 2); ## Get rows for variable types, names, descriptions, and units Trows = cellfun (@(x) size (x, 1), T); Tmaxr = max (Trows); Nrows = cellfun (@(x) size (x, 1), N); Nmaxr = max (Nrows); isvar = cellfun (@(x) ! isempty (x), N(1,:)); ## Descriptions and units are written when any variable carries one (the ## rest are left empty); nested variables expand them to as many rows as ## varNames/varTypes. Drows = cellfun (@(x) size (x, 1), D); if (any (cellfun (@(x) ! isempty (x), D(isvar)))) Dmaxr = max (Drows(isvar)); else Dmaxr = 0; endif Urows = cellfun (@(x) size (x, 1), U); if (any (cellfun (@(x) ! isempty (x), U(isvar)))) Umaxr = max (Urows(isvar)); else Umaxr = 0; endif ## Initialize header Header = repmat ({''}, Nmaxr + Tmaxr + Dmaxr + Umaxr, Ccols); ## Populate header for c = 1:Ccols if (isvar(c)) # variable if (Trows(c) == 1) Header{1,c} = T{c}; else for tr = 1:Trows(c) Header{tr,c} = T{c}{tr}; endfor endif if (Nrows(c) == 1) Header{1 + Tmaxr,c} = N{c}; else for nr = 1:Nrows(c) Header{nr + Tmaxr,c} = N{c}{nr}; endfor endif if (Dmaxr) if (Drows(c) == 1) Header{1 + Tmaxr + Nmaxr,c} = D{c}; else for dr = 1:Drows(c) Header{dr + Tmaxr + Nmaxr,c} = D{c}{dr}; endfor endif endif if (Umaxr) if (Urows(c) == 1) Header{1 + Tmaxr + Nmaxr + Dmaxr,c} = U{c}; else for ur = 1:Urows(c) Header{ur + Tmaxr + Nmaxr + Dmaxr,c} = U{c}{ur}; endfor endif endif else # RowNames Header{1,c} = 'RowNames'; endif endfor ## Generate descriptive comment for header contents cmt = cell (1, Ccols); txt = strcat ("# varTypes %d rows; varNames %d rows;", ... " varDescriptions %d rows; varUnits %d rows."); cmt{1} = sprintf (txt, Tmaxr, Nmaxr, Dmaxr, Umaxr); ## Merge cell arrays into a single cell array for saving to csv file csv = [cmt; Header; V]; ## Write to file msg = __table2csv__ (file, csv); if (msg) error ("table.table2csv: %s", msg); endif endfunction ## -*- texinfo -*- ## @deftypefn {table} {} table2ods (@var{tbl}, @var{file}) ## @deftypefnx {table} {} table2ods (@var{tbl}, @var{file}, @var{Name}, @var{Value}) ## ## Write a table to an OpenDocument spreadsheet file. ## ## @code{table2ods (@var{tbl}, @var{file})} writes the table @var{tbl} to ## @var{file}, which may be a character vector, a cellstr, or a string ## scalar. When @var{file} ends in @qcode{.ods} a compressed (ZIP-packaged) ## OpenDocument spreadsheet is written; when it ends in @qcode{.fods} a flat ## (single-XML) OpenDocument spreadsheet is written instead. The resulting ## file can be read back with @code{ods2table}. ## ## The data sheet (named @qcode{Sheet1} by default) carries one natively ## typed cell per value, and a hidden @qcode{__datatypes_meta__} sheet ## carries the variable types, names, descriptions, and units needed to ## restore the exact Octave types on read-back. Variables map to ODS cell ## types as follows: ## ## @itemize ## @item ## Numeric variables become @code{float} cells and logical variables become ## @code{boolean} cells. Integers are written with their exact digits. ## ## @item ## @code{datetime} variables become native @code{date} cells and ## @code{duration} variables become native @code{time} cells, both encoded ## as ISO 8601 strings. ## ## @item ## Character, cellstr, @code{string}, @code{categorical}, and ## @code{calendarDuration} variables become @code{string} cells. ## ## @item ## A multicolumn variable is split into consecutive columns that share the ## same variable name. ## @end itemize ## ## Missing values (@code{NaN}, @code{NaT}, and missing strings) are written ## as empty cells. When @var{tbl} has row names they are written under a ## leading @qcode{RowNames} column. Variable descriptions and units are ## written whenever @emph{any} variable has a non-empty description or unit, ## respectively (the others are left empty). A zone-aware @code{datetime} ## variable keeps its @code{TimeZone} on read-back. ## ## @code{table2ods (@dots{}, @qcode{'Sheet'}, @var{name})} writes to a sheet ## named @var{name} (default @qcode{'Sheet1'}). When @var{file} already ## exists the named sheet is added or replaced while every other sheet is ## preserved, so a workbook can be built up one table at a time. ## @code{table2ods (@dots{}, @qcode{'WriteMode'}, @var{mode})} selects the ## behaviour: @qcode{'overwritesheet'} / @qcode{'inplace'} replace the sheet ## (the default when the sheet exists), @qcode{'append'} appends the table's ## rows to it, and @qcode{'replacefile'} discards any existing file. ## ## Nested tables and structures are not supported and raise an error. Note ## the following round-trip limitations when reading the file back with ## @code{ods2table}: @code{calendarDuration} and @code{categorical} ## variables are returned as cell arrays of character vectors (their values ## are not reconstructed), and datetime and duration display formats are not ## preserved, although the values themselves are exact. ## ## @end deftypefn function table2ods (this, file, varargin) file = char (cellstr (file)); ## A '.fods' file is written as flat XML, a '.ods' file as a ZIP package. [~, ~, ext] = fileparts (file); if (strcmpi (ext, '.fods')) is_flat = true; elseif (strcmpi (ext, '.ods')) is_flat = false; else error (strcat ("table.table2ods: FILE must have a '.ods' or", ... " '.fods' extension.")); endif optNames = {'Sheet', 'WriteMode'}; dfValues = {'Sheet1', ''}; [sheet, writeMode, args] = ... parsePairedArguments (optNames, dfValues, varargin(:)); if (! isempty (args)) error ("table.table2ods: unknown option '%s'.", args{1}); endif if (isa (sheet, 'string')) sheet = char (sheet); endif if (! (ischar (sheet) && isrow (sheet))) error ("table.table2ods: 'Sheet' must be a sheet name."); endif writeMode = lower (char (writeMode)); switch (writeMode) case {'', 'replacefile', 'overwritesheet', 'inplace', 'append'} ## supported write modes otherwise error ("table.table2ods: 'WriteMode' '%s' is not valid.", writeMode); endswitch ## Merge into an existing workbook (preserving other sheets) by reading it ## back, modifying the struct of tables, and rewriting the whole file. if (exist (file, 'file') && ! strcmp (writeMode, 'replacefile')) s = ods2struct (file); s = merge_table_into_struct (s, this, sheet, writeMode); struct2ods (file, s); return; endif ## Fresh single-sheet write. [V, vtype, meta] = __ods_parts__ (this, 'table.table2ods'); msg = __table2ods__ (file, V, vtype, meta, is_flat, ... struct ('sheetname', sheet)); if (! isequal (msg, 0)) error ("table.table2ods: %s", msg); endif endfunction ## -*- texinfo -*- ## @deftypefn {table} {} writetable (@var{tbl}, @var{filename}) ## @deftypefnx {table} {} writetable (@var{tbl}, @var{filename}, @var{Name}, @var{Value}) ## ## Write a table to a file in a MATLAB-compatible format. ## ## @code{writetable (@var{tbl}, @var{filename})} writes the table @var{tbl} ## to @var{filename}. The file type is inferred from the extension: ## @qcode{.txt}, @qcode{.csv}, and @qcode{.dat} are written as delimited ## text; @qcode{.ods} as an OpenDocument spreadsheet; and @qcode{.xlsx} and ## @qcode{.xlsm} as Excel spreadsheets. Use the @qcode{'FileType'} option ## to override the inferred type. ## ## Unlike @code{table2csv}/@code{table2ods}, no type metadata is written: ## the file holds only an optional variable-name header row followed by the ## data, so it can be read by other applications. Type information is ## recovered by @code{readtable} through automatic detection (text) or the ## native cell types (spreadsheet). The following options are supported: ## ## @multitable @columnfractions 0.28 0.72 ## @headitem @var{Name} @tab @var{Value} ## @item @qcode{'FileType'} @tab @qcode{'text'} or @qcode{'spreadsheet'}. ## @item @qcode{'WriteVariableNames'} @tab Logical; write the variable names ## as the first row (default @qcode{true}). ## @item @qcode{'WriteRowNames'} @tab Logical; write the row names as the ## first column (default @qcode{false}). ## @item @qcode{'Delimiter'} @tab Field delimiter for text files: a single ## character or one of @qcode{'comma'}, @qcode{'space'}, @qcode{'tab'}, ## @qcode{'semi'}, @qcode{'bar'} (default @qcode{','}). ## @item @qcode{'QuoteStrings'} @tab @qcode{'minimal'}, @qcode{'all'}, or ## @qcode{'none'} for text files (default @qcode{'minimal'}). ## @item @qcode{'Sheet'} @tab Spreadsheet only: the name of the sheet to ## write. The default is the first sheet of an existing workbook, or ## @qcode{'Sheet1'} for a new file. ## @item @qcode{'Range'} @tab Spreadsheet only: an A1-style anchor such as ## @qcode{'C5'} at which to place the top-left corner of the data (fresh ## writes only). ## @item @qcode{'WriteMode'} @tab For text: @qcode{'overwrite'} (default) or ## @qcode{'append'}. For spreadsheets: @qcode{'overwritesheet'} / ## @qcode{'inplace'} (replace the target sheet), @qcode{'append'} (append ## rows to it), or @qcode{'replacefile'} (overwrite the whole file). ## @end multitable ## ## When the target spreadsheet already exists, the sheet named by ## @qcode{'Sheet'} (defaulting to the first existing sheet) is added or ## replaced while every other sheet is preserved, unless @qcode{'WriteMode'} ## is @qcode{'replacefile'}. For ODS, existing foreign spreadsheets (for ## example those written by LibreOffice) are updated in place, keeping their ## other parts; for Excel (@qcode{.xlsx}, @qcode{.xlsm}) the workbook is ## read back and rewritten, so only its cell values are preserved. ## ## Nested tables and structures are not supported, and the legacy binary ## formats @qcode{.xls} and @qcode{.xlsb} are not supported either; use ## @qcode{.xlsx}, @qcode{.ods}, or a text format. ## ## @end deftypefn function writetable (this, filename, varargin) if (! ((ischar (filename) && isvector (filename)) ... || (isa (filename, 'string') && isscalar (filename)))) error (strcat ("table.writetable: FILENAME must be a character", ... " vector or string scalar.")); endif file = char (filename); optNames = {'FileType', 'WriteVariableNames', 'WriteRowNames', ... 'Delimiter', 'QuoteStrings', 'Sheet', 'Range', 'WriteMode'}; dfValues = {'', true, false, ',', 'minimal', '', '', ''}; [fileType, writeVarNames, writeRowNames, delim, quoteStrings, sheet, ... range, writeMode, args] = ... parsePairedArguments (optNames, dfValues, varargin(:)); if (! isempty (args)) error ("table.writetable: unknown option '%s'.", args{1}); endif if (isa (sheet, 'string')) sheet = char (sheet); endif if (isa (range, 'string')) range = char (range); endif writeMode = lower (char (writeMode)); ## Resolve the file type from the option or the extension [~, ~, ext] = fileparts (file); if (isempty (fileType)) switch (lower (ext)) case {'.txt', '.csv', '.dat'} fileType = 'text'; case {'.ods', '.fods', '.xlsx', '.xlsm'} fileType = 'spreadsheet'; case {'.xls', '.xlsb'} error (strcat ("table.writetable: '%s' Excel files are not", ... " supported; use '.xlsx', '.ods', or a text", ... " format."), ext); otherwise error (strcat ("table.writetable: cannot infer the file type", ... " from '%s'; specify 'FileType'."), ext); endswitch endif switch (lower (fileType)) case 'text' fmt = 'display'; case 'spreadsheet' fmt = 'iso'; otherwise error ("table.writetable: 'FileType' must be 'text' or 'spreadsheet'."); endswitch ## Office Open XML (.xlsx/.xlsm) uses a separate courier from ODS. isXlsx = any (strcmpi (ext, {'.xlsx', '.xlsm'})); ## Validate 'Sheet', 'Range', and 'WriteMode' against the resolved type. appendMode = false; if (strcmp (fmt, 'display')) # text if (! isempty (sheet) || ! isempty (range)) error (strcat ("table.writetable: 'Sheet' and 'Range' are not", ... " supported for text files.")); endif switch (writeMode) case {'', 'overwrite'} appendMode = false; case 'append' appendMode = true; otherwise error (strcat ("table.writetable: 'WriteMode' '%s' is not valid", ... " for text files; use 'overwrite' or 'append'."), ... writeMode); endswitch else # spreadsheet if (! isempty (sheet) && ! (ischar (sheet) && isrow (sheet))) error ("table.writetable: 'Sheet' must be a sheet name."); endif switch (writeMode) case {'', 'replacefile', 'overwritesheet', 'inplace', 'append'} ## supported spreadsheet write modes otherwise error (strcat ("table.writetable: 'WriteMode' '%s' is not valid", ... " for spreadsheet files."), writeMode); endswitch ## A 'Range' anchors a fresh write; it has no meaning when merging into ## an existing workbook. if (! isempty (range)) if (strcmp (writeMode, 'append')) error (strcat ("table.writetable: 'Range' is not supported with", ... " 'WriteMode' 'append'.")); endif if (exist (file, 'file') && ! strcmp (writeMode, 'replacefile')) error (strcat ("table.writetable: 'Range' is not supported when", ... " writing into an existing file.")); endif endif endif ## Flatten the table; nested tables and structs (multi-row type entries) ## are refused, as MATLAB does. [V, N, T] = table2cellarrays (this, fmt); if (any (cellfun (@iscell, T))) error (strcat ("table.writetable: writetable does not support", ... " writing nested tables. Use splitvars to split", ... " multicolumn variables into single-column variables", ... " before writing.")); endif [names, V, T] = writetable_prep (V, N, T, writeRowNames); if (strcmp (fmt, 'display')) ## In append mode MATLAB writes the data rows only, never a header. if (writeVarNames && ! appendMode) grid = [names; V]; else grid = V; endif d = wt_resolve_delimiter (delim); msg = __table2csv__ (file, grid, d, lower (quoteStrings), appendMode); if (msg) error ("table.writetable: %s", msg); endif else vtype = cell (1, numel (T)); for c = 1:numel (T) vtype{c} = ods_value_type (T{c}); endfor opts = struct (); ## Append mode writes data rows only, never a header. if (writeVarNames && ! strcmp (writeMode, 'append')) opts.header = names; else opts.header = {}; endif ## Writing into an existing workbook with no explicit 'Sheet' targets the ## first existing sheet (MATLAB behaviour), not a new 'Sheet1'. if (isempty (sheet) && exist (file, 'file') ... && ! strcmp (writeMode, 'replacefile')) if (isXlsx) [~, ~, ~, exNames] = __xlsx2table__ (file); else [~, ~, ~, exNames] = __ods2table__ (file); endif if (iscell (exNames) && ! isempty (exNames)) sheet = exNames{1}; endif endif if (! isempty (sheet)) opts.sheetname = sheet; endif if (isXlsx) if (exist (file, 'file') && ! strcmp (writeMode, 'replacefile')) ## Merge into an existing workbook by reading it back, modifying the ## struct of tables, and rewriting (interop re-encode, like the ## incremental table2ods path). s = xlsx2struct (file); s = merge_table_into_struct (s, this, sheet, writeMode); struct2xlsx (file, s); msg = 0; else ## A fresh single-sheet write. if (! isempty (range)) [r1, c1] = __a1ref__ (range); opts.roff = r1 - 1; opts.coff = c1 - 1; endif opts.macro = strcmpi (ext, '.xlsm'); msg = __table2xlsx__ (file, V, vtype, opts); endif else is_flat = strcmpi (ext, '.fods'); ## Merge into an existing workbook (preserving other sheets) unless the ## file is new or 'replacefile' asks to overwrite it outright. if (exist (file, 'file') && ! strcmp (writeMode, 'replacefile')) opts.merge = true; opts.writemode = writeMode; elseif (! isempty (range)) [r1, c1] = __a1ref__ (range); opts.roff = r1 - 1; opts.coff = c1 - 1; endif msg = __table2ods__ (file, V, vtype, {}, is_flat, opts); endif if (! isequal (msg, 0)) error ("table.writetable: %s", msg); endif endif endfunction endmethods ################################################################################ ## ** Summary Information ** ## ################################################################################ ## Available Methods ## ## ## ## 'summary' 'height' 'width' 'head' ## ## 'tail' ## ## ## ################################################################################ methods (Access = public) ## -*- texinfo -*- ## @deftypefn {table} {} summary (@var{tbl}) ## @deftypefnx {table} {@var{s} =} summary (@var{tbl}) ## ## Print a summary of a table. ## ## @code{summary (@var{tbl})} prints the description from ## @qcode{@var{tbl}.Properties.Description} followed by a summary of each ## table variable's values and their properties as defined in ## @qcode{@var{tbl}.Properties.VariableUnits} and ## @qcode{@var{tbl}.Properties.VariableDescriptions}. ## ## @code{@var{s} = summary (@var{tbl})} returns a structure, @var{s}, that ## contains a summary of the input table, @var{tbl}. Each field of @var{s} ## is a structure that summarizes the values in the corresponding variable ## of @var{tbl}. Where applicable, the number of missing values is reported ## in a @qcode{NumMissing} field and printed when it is greater than zero. ## ## @itemize ## @item For numerical variables of @qcode{double}, @qcode{single} or any ## @qcode{int} type, it prints the minimum, median, and maximum values. For ## multicolumnar numerical variables it prints the minimum, median, and ## maximum values for each column separately. ## ## @item For variables of @qcode{logical} type, it prints the occurrences ## of @qcode{True} and @qcode{False}. ## ## @item For variables of type @qcode{datetime} and @qcode{duration} it ## prints the minimum, median, and maximum values, computed after excluding ## any missing (@qcode{NaT} or @qcode{NaN}) elements. ## ## @item For variables of type @qcode{calendarDuration}, which are not ## totally ordered, only the size, the type, and the number of missing ## values are reported. ## ## @item For variables of type @qcode{cellstr}, @qcode{cell}, ## @qcode{string}, @qcode{categorical}, and @qcode{struct} it prints the ## size and the type of variable. ## @end itemize ## ## @end deftypefn function [varargout] = summary (this) ## Get summary for each variable into the returning structure s = summary_for_variables (this); ## Print summary if no output is requested if (nargout == 0) ## Print table description if (! isempty (this.Description)) fprintf ("Description: %s\n\n", this.Description); endif ## Print summary for each variable fprintf ("Variables:\n\n"); tab = " "; varNames = fieldnames (s); for i = 1:numel (varNames) var = s.(varNames{i}); ## Print variable name fprintf ("%s%s: %dx%d %s\n\n", tab, varNames{i}, var.Size(1), ... var.Size(2), var.Type); ## Print variable properties (if available) if (! isempty (var.Units) || ! isempty (var.Description)) fprintf ("%s Properties:\n", tab); if (! isempty (var.Units)) fprintf ("%s Units: %s\n", tab, var.Units); endif if (! isempty (var.Description)) fprintf ("%s Description: %s\n", tab, var.Description); endif endif ## Print custom properties (if available) if (isfield (var, 'CustomProperties')) if (! isempty (var.CustomProperties)) fprintf ("%s Custom Properties:\n", tab); cpNames = fieldnames (var.CustomProperties); for p = 1:numel (cpNames) fprintf ("%s %s: %s\n", tab, cpNames{p}, ... var.CustomProperties.(cpNames{p}){:}); endfor endif endif ## Print values (numeric/datetime/duration Min Median Max, logical ## True False) followed by the count of missing values, if any. if (isfield (var, 'Min') || isfield (var, 'True')) fprintf ("%s Values:\n", tab); isLogical = isfield (var, 'True'); isNumeric = isfield (var, 'Min') && isnumeric (var.Min); ## Check for multicolumnar variable if (var.Size(2) > 1) ## Find max element length for aligning the columns if (isNumeric) mLen = max (arrayfun (@(x) length (num2str (x)), ... [var.Min, var.Median, var.Max])); elseif (isLogical) mLen = max (arrayfun (@(x) length (num2str (x)), ... [var.True, var.False])); else strs = [dispstrings(var.Min), dispstrings(var.Median), ... dispstrings(var.Max)]; mLen = max (cellfun (@length, strs)); endif ## Create padding character vectors mLen = max (8, mLen); pad = repmat (' ', 1, mLen - 8); strhead = ''; strline = ''; for c = 1:var.Size(2) strhead = [strhead, sprintf("%sColumn %d ", pad, c)]; strline = [strline, sprintf("%s________ ", pad)]; endfor ## Print multicolumnar variable header fprintf ("%s %s\n", tab, strhead); fprintf ("%s %s\n", tab, strline); ## Print multicolumnar variable statistics if (isNumeric) template = ["%s%", sprintf("%d", mLen), "g "]; strMin = ''; strMed = ''; strMax = ''; for c = 1:numel (var.Min) strMin = [strMin, sprintf(template, pad, var.Min(c))]; strMed = [strMed, sprintf(template, pad, var.Median(c))]; strMax = [strMax, sprintf(template, pad, var.Max(c))]; endfor fprintf ("%s Min %s\n", tab, strMin); fprintf ("%s Median %s\n", tab, strMed); fprintf ("%s Max %s\n", tab, strMax); elseif (isLogical) template = ["%s%", sprintf("%d", mLen), "g "]; strTrue = ''; strFalse = ''; for c = 1:numel (var.True) strTrue = [strTrue, sprintf(template, pad, var.True(c))]; strFalse = [strFalse, sprintf(template, pad, var.False(c))]; endfor fprintf ("%s True %s\n", tab, strTrue); fprintf ("%s False %s\n", tab, strFalse); else ## datetime or duration template = ["%s%", sprintf("%d", mLen), "s "]; minStr = dispstrings (var.Min); medStr = dispstrings (var.Median); maxStr = dispstrings (var.Max); strMin = ''; strMed = ''; strMax = ''; for c = 1:numel (minStr) strMin = [strMin, sprintf(template, pad, minStr{c})]; strMed = [strMed, sprintf(template, pad, medStr{c})]; strMax = [strMax, sprintf(template, pad, maxStr{c})]; endfor fprintf ("%s Min %s\n", tab, strMin); fprintf ("%s Median %s\n", tab, strMed); fprintf ("%s Max %s\n", tab, strMax); endif ## Print single column variable else if (isNumeric) fprintf ("%s Min %g\n", tab, var.Min); fprintf ("%s Median %g\n", tab, var.Median); fprintf ("%s Max %g\n", tab, var.Max); elseif (isLogical) fprintf ("%s True %d\n", tab, var.True); fprintf ("%s False %d\n", tab, var.False); else ## datetime or duration fprintf ("%s Min %s\n", tab, ... dispstrings (var.Min){:}); fprintf ("%s Median %s\n", tab, ... dispstrings (var.Median){:}); fprintf ("%s Max %s\n", tab, ... dispstrings (var.Max){:}); endif endif ## Print number of missing values, if any if (isfield (var, 'NumMissing') && any (var.NumMissing(:) > 0)) fprintf ("%s NumMissing%s\n", tab, ... sprintf (" %d", var.NumMissing)); endif fprintf ("\n"); ## Variables carrying only a missing-value count (calendarDuration) elseif (isfield (var, 'NumMissing') && any (var.NumMissing(:) > 0)) fprintf ("%s Values:\n", tab); fprintf ("%s NumMissing%s\n\n", tab, ... sprintf (" %d", var.NumMissing)); endif endfor endif ## Return structure if requested if (nargout > 0) varargout{1} = s; endif endfunction ## -*- texinfo -*- ## @deftypefn {table} {@var{H} =} height (@var{tbl}) ## ## Number of rows in table. ## ## @code{@var{H} = height (@var{tbl})} returns the number of rows in the ## table @var{tbl} as a scalar. It is the equivalent of ## @qcode{size (@var{tbl}, 1)}. ## ## For an empty table, or a table created with zero rows, @code{height} ## returns 0. The presence of row names does not affect the result. ## ## @end deftypefn function out = height (this) if (isempty (this.VariableValues)) out = 0; else out = size (this.VariableValues{1}, 1); endif endfunction ## -*- texinfo -*- ## @deftypefn {table} {@var{W} =} width (@var{tbl}) ## ## Number of variables in table. ## ## @code{@var{W} = width (@var{tbl})} returns the number of variables in the ## table @var{tbl} as a scalar. It is the equivalent of ## @qcode{size (@var{tbl}, 2)}. ## ## Note that this is the number of table variables, not the total number of ## columns. A single variable may itself contain several columns (for ## example, a matrix-valued variable), but it still counts as one towards ## the table width. ## ## For a table with no variables, @code{width} returns 0. ## ## @end deftypefn function out = width (this) out = numel (this.VariableNames); endfunction ## -*- texinfo -*- ## @deftypefn {table} {} head (@var{tbl}) ## @deftypefnx {table} {} head (@var{tbl}, @var{k}) ## @deftypefnx {table} {@var{out} =} head (@var{tbl}, @var{k}) ## ## Display or return the first @var{k} rows of a table. ## ## @code{head (@var{tbl})} displays the first eight rows of the table ## @var{tbl}. If @var{tbl} has fewer than eight rows, then all rows are ## displayed. ## ## @code{head (@var{tbl}, @var{k})} displays the first @var{k} rows of the ## table @var{tbl}. @var{k} must be a positive integer scalar value. If ## @var{tbl} has fewer than @var{k} rows, then all rows are displayed. ## ## @code{@var{out} = head (@var{tbl}, @var{k})} returns the first @var{k} ## rows in a new table @var{out} instead of displaying them. If @var{k} is ## omitted or empty, then it defaults to eight. If @var{tbl} has fewer than ## @var{k} rows, then all available rows are returned. ## ## The returned table preserves the variable names, row names, and all other ## properties of @var{tbl}. ## ## @end deftypefn function [varargout] = head (this, k) if (nargin < 2 || isempty (k)) k = 8; endif if (! isscalar (k) || fix (k) != k || k <= 0) error ("table.head: K must be a positive integer scalar value."); endif nRows = height (this); if (nRows < k) out = this; else out = subsetrows (this, 1:k); endif if (nargout == 0) print_table (out); elseif (nargout == 1) varargout{1} = out; else error ("table.head: invalid number of output arguments."); endif endfunction ## -*- texinfo -*- ## @deftypefn {table} {} tail (@var{tbl}) ## @deftypefnx {table} {} tail (@var{tbl}, @var{k}) ## @deftypefnx {table} {@var{out} =} tail (@var{tbl}, @var{k}) ## ## Display or return the last @var{k} rows of a table. ## ## @code{tail (@var{tbl})} displays the last eight rows of the table ## @var{tbl}. If @var{tbl} has fewer than eight rows, then all rows are ## displayed. ## ## @code{tail (@var{tbl}, @var{k})} displays the last @var{k} rows of the ## table @var{tbl}. @var{k} must be a positive integer scalar value. If ## @var{tbl} has fewer than @var{k} rows, then all rows are displayed. ## ## @code{@var{out} = tail (@var{tbl}, @var{k})} returns the last @var{k} ## rows in a new table @var{out} instead of displaying them. If @var{k} is ## omitted or empty, then it defaults to eight. If @var{tbl} has fewer than ## @var{k} rows, then all available rows are returned. ## ## The returned table preserves the variable names, row names, and all other ## properties of @var{tbl}. ## ## @end deftypefn function [varargout] = tail (this, k) if (nargin < 2 || isempty (k)) k = 8; endif if (! isscalar (k) || fix (k) != k || k <= 0) error ("table.tail: K must be a positive integer scalar value."); endif nRows = height (this); if (nRows < k) out = this; else out = subsetrows (this, [(nRows - (k - 1)):nRows]); endif if (nargout == 0) print_table (out); elseif (nargout == 1) varargout{1} = out; else error ("table.tail: invalid number of output arguments."); endif endfunction endmethods ################################################################################ ## ** Sort, Filter, and Rearrange ** ## ################################################################################ ## Available Methods ## ## ## ## 'sortrows' 'unique' 'issortedrows' 'topkrows' ## ## 'addvars' 'renamevars' 'movevars' 'removevars' ## ## 'splitvars' 'mergevars' 'convertvars' 'rows2vars' ## ## 'stack' 'unstack' 'inner2outer' 'addprop' ## ## 'rmprop' ## ## ## ################################################################################ methods (Access = public) ## -*- texinfo -*- ## @deftypefn {table} {@var{tblB} =} sortrows (@var{tblA}) ## @deftypefnx {table} {@var{tblB} =} sortrows (@var{tblA}, @qcode{'RowNames'}) ## @deftypefnx {table} {@var{tblB} =} sortrows (@var{tblA}, @var{rowDimName}) ## @deftypefnx {table} {@var{tblB} =} sortrows (@var{tblA}, @var{vars}) ## @deftypefnx {table} {@var{tblB} =} sortrows (@var{tblA}, @dots{}, @var{direction}) ## @deftypefnx {table} {@var{tblB} =} sortrows (@dots{}, @var{Name}, @var{Value}) ## @deftypefnx {table} {[@var{tblB}, @var{index}] =} sortrows (@dots{}) ## ## Sort the rows of a table. ## ## @code{@var{tblB} = sortrows (@var{tblA})} sorts the rows in @var{tblA} in ## ascending order based on the values in the first variable. If elements ## in the first variable are repeated, then @code{sortrows} sorts by the ## elements in the second variable, and so on. ## ## @code{@var{tblB} = sortrows (@var{tblA}, 'RowNames')} sorts the ## table @var{tblA} according to its row names. If @var{tblA} does not ## have row names, i.e. @qcode{tblA.Properties.RowNames} is empty, then it ## returns @var{tblA}. ## ## @code{@var{tblB} = sortrows (@var{tblA}, @var{rowDimName})} also sorts ## the table @var{tblA} along the first dimension, @var{rowDimName}, which ## is the equivalent to the previous syntax, i.e. according to its row ## names. If @var{tblA} does not have row names, that is ## @qcode{tblA.Properties.RowNames} is empty, then it returns @var{tblA}. ## For this syntax to work, @var{rowDimName} must match the first element in ## @qcode{tblA.Properties.DimensionNames}, otherwise @var{rowDimName} is ## considered a variable name, as in the following syntax. ## ## @code{@var{tblB} = sortrows (@var{tblA}, @var{vars})} sorts the rows in ## table @var{tblA} by the elements in the variables specified by ## @var{vars}, which can be a character vector (for a single variable) or a ## cell array of character vectors or a string array (specifying a single or ## multiple variables). If @var{tblA} has row names, then @var{vars} can ## include the row names. Alternatively, @var{vars} can be a logical vector ## or a numeric vector of real integers indexing the desired variables. ## Positive integers specify an ascending order, whereas negative integers ## specify a descending order for the referenced variables. You can also ## index all available variables in @var{tblA} by passing a semicolon ## character argument. This Octave-specific syntax facilitates the use of ## the @var{direction} input argument when no particular variable needs to ## be selected to sort on. Additionally, @var{vars} can be a ## @qcode{vartype} object used to create a subscript that selects variables ## of a specified type. ## ## @code{@var{tblB} = sortrows (@var{tblA}, @dots{}, @var{direction})} sorts ## the rows in table @var{tblA} in the order specified by @var{direction} ## for any of the previous syntaxes. @var{direction} can be ## @qcode{'ascend'} or @qcode{'descend'}, which is applied to all specified ## variables or row names that @code{sortrows} operates on. @var{direction} ## can also be a cell array of character vectors, whose elements are ## @qcode{'ascend'} and @qcode{'descend'}, where each element corresponds to ## the specified variables and/or row names used for sorting the table. ## The order specified by @var{direction} always takes precedence over the ## order defined by a numerical vector of integers in @var{vars}. ## @var{direction} must always be the 3rd input argument. If you want to ## omit passing selected variables and allow @code{sortrows} to work on ## consecutive variables until all ties are resolved, then you can leave the ## second input argument empty, as in ## @code{sortrows (@var{tblA}, @{[]@}, @var{direction})} or pass a ## colon argument for @var{vars} as in ## @code{sortrows (@var{tblA}, @{':'@}, @var{direction})}. ## ## @code{@var{tblB} = sortrows (@dots{}, @var{Name}, @var{Value})} specifies ## additional parameters for sorting rows of a table with the following ## Name-Value paired arguments. ## ## @itemize ## @item @qcode{'MissingPlacement'} specifies the placement of missing ## values with one of the following options: @qcode{'auto'} places the ## missing elements at the bottom for ascending order and at the top for ## descending order; @qcode{'first'} places missing elements at the top; ## @qcode{'last'} places missing elements at the bottom. ## @item @qcode{'ComparisonMethod'} specifies the element comparison method ## with one of the following options: @qcode{'auto'} sorts rows using the ## real part for real numbers and the magnitude for complex numbers; ## @qcode{'real'} sorts rows using the real part for both real and complex ## numbers; @qcode{'abs'} sorts rows using the magnitude for both real and ## complex numbers. For complex numbers with equal magnitude, the phase ## angle in the interval @math{(-π, π]} is further used to break ties. ## @end itemize ## ## @code{[@var{tblB}, @var{index}] = sortrows (@dots{})} also returns an ## index vector such that @qcode{@var{tblB} = @var{tblA}(@var{index},:)}. ## ## @end deftypefn function [tbl, index] = sortrows (this, varargin) ## Add defaults varRef = ':'; doRowNames = false; inRowNames = 0; direction = {'ascend'}; dir_given = false; ## Parse optional Name-Value paired arguments optNames = {'MissingPlacement', 'ComparisonMethod'}; dfValues = {'auto', 'auto'}; [MP, CM, args] = parsePairedArguments (optNames, dfValues, varargin(:)); ## Check optional Name-Value paired arguments if (! ismember (MP, {'auto', 'first', 'last'})) error (strcat ("table.sortrows: 'MissingPlacement' parameter can", ... " be either 'auto', 'first', or 'last'.")); endif if (! ismember (CM, {'auto', 'real', 'abs'})) error (strcat ("table.sortrows: 'ComparisonMethod' parameter can", ... " be either 'auto', 'real', or 'abs'.")); endif ## Parse extra arguments nargs = numel (args); if (nargs > 2) error ("table.sortrows: invalid number of input arguments."); endif if (nargs > 1) direction = cellstr (args{2}); dir_given = true; if (! all (ismember (direction, {'ascend', 'descend'}))) error ("table.sortrows: invalid value for DIRECTION argument."); endif endif if (nargs > 0) ## RowNames and rowDimName take precedence over variable names arg1 = args{1}; if (ischar (arg1) && isvector (arg1) && ismember (arg1, {'RowNames', this.DimensionNames{1}})) ## Check user's direction is scalar if (dir_given && numel (direction) != 1) error (strcat ("table.sortrows: DIRECTION must be a scalar", ... " input when 'RowNames' or 'rowDimNames' are", ... " selected.")); endif ## Handle special case here if (isempty (this.RowNames)) tbl = this; index = [1:height(this)]'; return else [~, index] = sort (this.RowNames, direction{:}); tbl = subsetrows (this, index); return endif endif ## At this point, VARS must be variable name(s) if (islogical (arg1)) varRef = arg1; if (! (isvector (varRef) && numel (varRef) == width (this))) error (strcat ("table.sortrows: logical indexing vector does", ... " not match table width.")); endif ## Check user's direction matches selected variables if (! isscalar (direction)) if (dir_given && sum (varRef) != numel (direction)) error ("table.sortrows: invalid size for DIRECTION argument."); endif endif elseif (isnumeric (arg1)) if (isempty (arg1)) arg1 = [1:width(this)]; endif if (! isvector (arg1) || any (fix (arg1) != arg1) || any (arg1 == 0)) error (strcat ("table.sortrows: numerical indexing must be a", ... " vector of nonzero integers.")); endif if (any (abs (arg1) > width (this))) error ("table.sortrows: numerical index exceeds table dimensions."); endif varRef = arg1; ## If direction was given, ignore sign of numerical indexing if (dir_given) varRef = abs (varRef); ## Check user's direction matches selected variables if (! isscalar (direction)) if (! isequal (size (varRef), size (direction))) error ("table.sortrows: invalid size for DIRECTION argument."); endif endif else direction = cell (1, numel (varRef)); direction(sign (varRef) > 0) = 'ascend'; direction(sign (varRef) < 0) = 'descend'; varRef = abs (varRef); endif elseif (ischar (arg1) || iscellstr (arg1) || isa (arg1, 'string')) varRef = cellstr (arg1); if (isscalar (varRef) && strcmp (varRef, ':')) varRef = ':'; elseif (! all (ismember (varRef, [this.VariableNames, {'RowNames'}]))) error ("table.sortrows: VARS indexes non-existing variable names."); endif ## Check user's direction matches selected variables if (! isscalar (direction)) if (strcmp (varRef, ':') && numel (direction) != width (this)) error ("table.sortrows: invalid size for DIRECTION argument."); elseif (! isequal (size (varRef), size (direction))) error ("table.sortrows: invalid size for DIRECTION argument."); endif endif ## Check whether 'RowNames' are included in the indexed variables if (any (ismember (varRef, 'RowNames'))) inRowNames = find (strcmp (varRef, 'RowNames')); varRef(inRowNames) = []; endif elseif (isa (arg1, 'vartype')) varRef = arg1; ## Check user's direction is scalar if (dir_given && numel (direction) != 1) error (strcat ("table.sortrows: DIRECTION must be a scalar", ... " input when variables are indexed with a", ... " 'vartype' object.")); endif endif endif ## Resolve varRef to variables' indices ixVars = resolveVarRef (this, varRef); ## Build a cell array for the selected variables to be used in sorting if (inRowNames == 0) varVal = cell (1, numel (ixVars)); else varVal = cell (1, numel (ixVars) + 1); endif ## Expand direction if it is a scalar if (isscalar (direction)) direction = repmat (direction, 1, numel (varVal)); endif ## Populate cell array for sorting offset = 0; for ix = 1:numel (varVal) if (inRowNames == ix) varVal(ix) = {this.RowNames}; offset = 1; else varVal(ix) = this.VariableValues(ixVars(ix - offset)); endif endfor ## Prepare a proxy array by converting all variable to numeric proxies varValIdx = []; varValDir = []; for ix = 1:numel (varVal) tmpVal = varVal{ix}; if (strcmpi (direction{ix}, 'ascend')) tmpDir = 1; else tmpDir = -1; endif if (isa (tmpVal, 'categorical')) tmpVal = double (tmpVal); varValIdx = [varValIdx, tmpVal]; elseif (isa (tmpVal, 'calendarDuration')) tmpVal = tmpVal.proxyArray; varValIdx = [varValIdx, tmpVal]; elseif (isa (tmpVal, 'datetime')) tmpVal = datetime_to_datenum (tmpVal); varValIdx = [varValIdx, tmpVal]; elseif (isa (tmpVal, 'duration')) tmpVal = days (tmpVal); varValIdx = [varValIdx, tmpVal]; elseif (isa (tmpVal, 'string')) tmpVal = cellstr (tmpVal); [~, ~, idx] = __unique__ (tmpVal, 'rows'); varValIdx = [varValIdx, idx]; elseif (iscellstr (tmpVal)) [~, ~, idx] = __unique__ (tmpVal, 'rows'); varValIdx = [varValIdx, idx]; elseif (iscell (tmpVal)) ## Sorting mixed cell data is not supported error ("table.sortrows: cannot sort variables of 'cell' type."); elseif (isnumeric (tmpVal)) if (strcmpi (CM, 'real') && iscomplex (tmpVal)) tmpVal = real (tmpVal); elseif (strcmpi (CM, 'abs') && isreal (tmpVal)) tmpVal = abs (tmpVal); endif varValIdx = [varValIdx, tmpVal]; elseif (isstruct (tmpVal)) ## Sorting structure data is not supported error ("table.sortrows: cannot sort variables of 'struct' type."); elseif (isa (tmpVal, 'table')) try tmpVal = table2array (varVal{ix}); varValIdx = [varValIdx, tmpVal]; catch error (strcat ("table.sortrows: cannot sort nested tables", ... " with mixed data types.")); end_try_catch endif tmpDir = repmat (tmpDir, 1, size (tmpVal, 2)); varValDir = [varValDir, tmpDir]; endfor ## Fix direction vector varValDir = [1:numel(varValDir)] .* varValDir; ## Do the actual sorting here [~, index] = sortrows (varValIdx, varValDir); tbl = subsetrows (this, index); index = index(:); ## Fix missing placement TF = ismissing (tbl); TFvec = TF(:,ixVars(1)); if (any (TFvec) && ! all (TFvec)) is_nan = index(TFvec); no_nan = index(! TFvec); if (any (find (TFvec) == 1) && strcmpi (MP, 'last')) index = [no_nan; is_nan]; elseif (any (find (TFvec) == numel (index)) && strcmpi (MP, 'first')) index = [is_nan; no_nan]; endif tbl = subsetrows (this, index); endif endfunction ## -*- texinfo -*- ## @deftypefn {table} {@var{tblB} =} unique (@var{tblA}) ## @deftypefnx {table} {@var{tblB} =} unique (@var{tblA}, @var{setOrder}) ## @deftypefnx {table} {@var{tblB} =} unique (@var{tblA}, @var{occurrence}) ## @deftypefnx {table} {[@var{tblB}, @var{ixA}, @var{ixB}] =} unique (@dots{}) ## ## Unique rows in a table. ## ## @code{@var{tblB} = unique (@var{tblA})} returns the unique rows of table ## @var{tblA} in sorted order. ## ## @code{@var{tblB} = unique (@var{tblA}, @var{setOrder})} returns the ## unique rows of table @var{tblA} in a specified order. @var{setOrder} can ## be either @qcode{'sorted'} (default) or @qcode{'stable'}. ## ## @itemize ## @item @qcode{'sorted'} returns the unique rows sorted in ascending order. ## @item @qcode{'stable'} returns the unique rows according to their order ## of occurrence. ## @end itemize ## ## @code{@var{tblB} = unique (@var{tblA}, @var{occurrence})} returns the ## unique rows of table @var{tblA} according to their order of occurrence. ## @var{occurrence} can be either @qcode{'first'} (default) or ## @qcode{'last'}. ## ## @itemize ## @item @qcode{'first'} returns the first occurrence of each unique row, ## i.e. the lowest possible indices are returned. ## @item @qcode{'last'} returns the last occurrence of each unique row, i.e. ## the highest possible indices are returned. ## @end itemize ## ## @code{[@var{tblB}, @var{ixA}, @var{ixB}] = unique (@dots{})} also returns ## index vectors @var{ixA} and @var{ixB} using any of the previous syntaxes. ## @var{ixA} and @var{ixB} map the tables @var{tblA} and @var{tblB} to one ## another such that @qcode{@var{tblB} = @var{tblA}(@var{ixA},:)} and ## @qcode{@var{tblA} = @var{tblB}(@var{ixB},:)}. ## ## @end deftypefn function [tbl, ia, ic] = unique (this, varargin) ## Check max number of input arguments if (nargin > 2) error ("table.unique: too many input arguments."); endif ## Handle 'setOrder' and 'occurrence' options opt = 'sorted'; if (! isempty (varargin)) if (any (strcmp (varargin{1}, {'sorted', 'stable', 'first', 'last'}))) opt = varargin{1}; else error ("table.unique: invalid option '%s'.", varargin{1}); endif endif ## Prepare a proxy array by converting all variables to numeric proxies varProxy = []; for ix = 1:width (this) varVal = this.VariableValues{ix}; if (isa (varVal, 'categorical')) varVal = double (varVal); varProxy = [varProxy, varVal]; elseif (isa (varVal, 'calendarDuration')) varVal = varVal.proxyArray; varProxy = [varProxy, varVal]; elseif (isa (varVal, 'datetime')) varVal = datetime_to_datenum (varVal); varProxy = [varProxy, varVal]; elseif (isa (varVal, 'duration')) varVal = days (varVal); varProxy = [varProxy, varVal]; elseif (isa (varVal, 'string')) varVal = cellstr (varVal); [~, ~, idx] = __unique__ (varVal, 'rows'); varProxy = [varProxy, idx]; elseif (iscellstr (varVal)) [~, ~, idx] = __unique__ (varVal, 'rows'); varProxy = [varProxy, idx]; elseif (iscell (varVal)) ## Mixed cell data is not supported error (strcat ("table.unique: cannot find unique rows for", ... " variables of 'cell' type.")); elseif (isnumeric (varVal) || islogical (varVal)) varProxy = [varProxy, varVal]; elseif (isstruct (varVal)) ## Structure data is not supported error (strcat ("table.unique: cannot find unique rows for", ... " variables of 'struct' type.")); elseif (isa (varVal, 'table')) try varVal = table2array (varVal); varProxy = [varProxy, varVal]; catch error (strcat ("table.unique: cannot find unique rows for", ... " nested tables with mixed data types.")); end_try_catch endif endfor ## Find unique rows in proxy table [~, ia, ic] = __unique__ (varProxy, opt, 'rows'); ## Return unique table tbl = subsetrows (this, ia); endfunction ## -*- texinfo -*- ## @deftypefn {table} {@var{TF} =} issortedrows (@var{tblA}) ## @deftypefnx {table} {@var{TF} =} issortedrows (@var{tblA}, @qcode{'RowNames'}) ## @deftypefnx {table} {@var{TF} =} issortedrows (@var{tblA}, @var{rowDimName}) ## @deftypefnx {table} {@var{TF} =} issortedrows (@var{tblA}, @var{vars}) ## @deftypefnx {table} {@var{TF} =} issortedrows (@var{tblA}, @dots{}, @var{direction}) ## @deftypefnx {table} {@var{TF} =} issortedrows (@dots{}, @var{Name}, @var{Value}) ## ## Check if table rows are sorted accordingly. ## ## @code{@var{TF} = issortedrows (@var{tblA})} determines if the rows in ## @var{tblA} are sorted in ascending order based on the values in the first ## variable or subsequent variables if elements of the former are repeated. ## @var{TF} is a logical scalar and it is @qcode{true} when ## @code{@var{tblA} == sortrows (@var{tblA})} or @qcode{false} otherwise. ## ## @code{@var{TF} = issortedrows (@var{tblA}, 'RowNames')} ## determines if the rows in @var{tblA} are sorted according to its row ## names. @var{TF} is @qcode{true} when @code{@var{tblA} == sortrows ## (@var{tblA}, 'RowNames')} or @qcode{false} otherwise. If ## @var{tblA} does not have row names, i.e. @qcode{tblA.Properties.RowNames} ## is empty, then @var{TF} is @qcode{true}. ## ## @code{@var{TF} = issortedrows (@var{tblA}, @var{rowDimName})} determines ## if the rows in table @var{tblA} are sorted along the first dimension, ## @var{rowDimName}, which is the equivalent to the previous syntax, i.e. ## according to its row names. For this syntax to work, @var{rowDimName} ## must match the first element in @qcode{tblA.Properties.DimensionNames}, ## otherwise @var{rowDimName} is considered a variable name, as in the ## following syntax. @var{TF} is @qcode{true} when @code{@var{tblA} == ## sortrows (@var{tblA}, @var{rowDimName})} or @qcode{false} otherwise. If ## @var{tblA} does not have row names, i.e. @qcode{tblA.Properties.RowNames} ## is empty, then @var{TF} is @qcode{true}. ## ## @code{@var{TF} = issortedrows (@var{tblA}, @var{vars})} determines if the ## rows in @var{tblA} are sorted by the elements in the variables specified ## by @var{vars}, which can be a character vector (for a single variable) or ## a cell array of character vectors or a string array (specifying a single ## or multiple variables). If @var{tblA} has row names, then @var{vars} can ## include the row names. Alternatively, @var{vars} can be a logical vector ## or a numeric vector of real integers indexing the desired variables. ## Positive integers specify an ascending order, whereas negative integers ## specify a descending order for the referenced variables. You can also ## index all available variables in @var{tblA} by passing a semicolon ## character argument. This Octave-specific syntax facilitates the use of ## the @var{direction} input argument when no particular variable needs to ## be selected to sort on. Additionally, @var{vars} can be a ## @qcode{vartype} object used to create a subscript that selects variables ## of a specified type. ## ## @code{@var{TF} = issortedrows (@var{tblA}, @dots{}, @var{direction})} ## determines if the rows in @var{tblA} are sorted in the order specified by ## @var{direction} for any of the previous syntaxes. @var{direction} can be ## @qcode{'ascend'} or @qcode{'descend'}, which is applied to all specified ## variables or row names that @code{sortrows} operates on. @var{direction} ## can also be a cell array of character vectors, whose elements are ## @qcode{'ascend'} and @qcode{'descend'}, where each element corresponds to ## the specified variables and/or row names used for sorting the table. ## The order specified by @var{direction} always takes precedence over the ## order defined by a numerical vector of integers in @var{vars}. ## @var{direction} must always be the 3rd input argument. If you want to ## omit passing selected variables and allow @code{sortrows} to work on ## consecutive variables until all ties are resolved, then you can leave the ## second input argument empty, as in ## @code{sortrows (@var{tblA}, @{[]@}, @var{direction})} or pass a ## colon argument for @var{vars} as in ## @code{sortrows (@var{tblA}, @{':'@}, @var{direction})}. ## ## @code{@var{TF} = issortedrows (@dots{}, @var{Name}, @var{Value})} ## determines if the rows in @var{tblA} are sorted according the additional ## parameters specifying the sorting of rows of a table with the following ## Name-Value paired arguments. ## ## @itemize ## @item @qcode{'MissingPlacement'} specifies the placement of missing ## values with one of the following options: @qcode{'auto'} places the ## missing elements at the bottom for ascending order and at the top for ## descending order; @qcode{'first'} places missing elements at the top; ## @qcode{'last'} places missing elements at the bottom. ## @item @qcode{'ComparisonMethod'} specifies the element comparison method ## with one of the following options: @qcode{'auto'} sorts rows using the ## real part for real numbers and the magnitude for complex numbers; ## @qcode{'real'} sorts rows using the real part for both real and complex ## numbers; @qcode{'abs'} sorts rows using the magnitude for both real and ## complex numbers. For complex numbers with equal magnitude, the phase ## angle in the interval @math{(-π, π]} is further used to break ties. ## @end itemize ## ## @end deftypefn function TF = issortedrows (this, varargin) ## Get indices of sorted table according to the user's options [~, ix] = sortrows (this, varargin{:}); ## Check that indices match the current order TF = isequal (ix', 1:height (this)); endfunction ## -*- texinfo -*- ## @deftypefn {table} {@var{tblB} =} topkrows (@var{tblA}, @var{k}) ## @deftypefnx {table} {@var{tblB} =} topkrows (@var{tblA}, @var{k}, @qcode{'RowNames'}) ## @deftypefnx {table} {@var{tblB} =} topkrows (@var{tblA}, @var{k}, @var{rowDimName}) ## @deftypefnx {table} {@var{tblB} =} topkrows (@var{tblA}, @var{k}, @var{vars}) ## @deftypefnx {table} {@var{tblB} =} topkrows (@var{tblA}, @var{k}, @dots{}, @var{direction}) ## @deftypefnx {table} {@var{tblB} =} topkrows (@dots{}, @var{Name}, @var{Value}) ## @deftypefnx {table} {[@var{tblB}, @var{index}] =} topkrows (@dots{}) ## ## Return the top rows of a table. ## ## @code{@var{tblB} = topkrows (@var{tblA}, @var{k})} returns the top ## @var{k} rows from table @var{tblA} sorted in descending order based on ## all of its variables. If elements in the first variable are repeated, ## then @code{topkrows} sorts by the elements in the second variable, and so ## on. ## ## @code{@var{tblB} = topkrows (@var{tblA}, @var{k}, 'RowNames')} returns ## the top @var{k} rows from table @var{tblA} sorted according to its row ## names. If @var{tblA} does not have row names, i.e. ## @qcode{tblA.Properties.RowNames} is empty, then it returns @var{tblA}. ## ## @code{@var{tblB} = topkrows (@var{tblA}, @var{k}, @var{rowDimName})} also ## returns the top @var{k} rows from table @var{tblA} sorted along its first ## dimension, @var{rowDimName}, which is the equivalent to the previous ## syntax, i.e. according to its row names. If @var{tblA} does not have row ## names, i.e. @qcode{tblA.Properties.RowNames} is empty, then it returns ## @var{tblA}. For this syntax to work, @var{rowDimName} must match the ## first element in @qcode{tblA.Properties.DimensionNames}, otherwise ## @var{rowDimName} is considered a variable name, as in the following ## syntax. ## ## @code{@var{tblB} = topkrows (@var{tblA}, @var{k}, @var{vars})} returns ## the top @var{k} rows from table @var{tblA} sorted by the elements in the ## variables specified by @var{vars}, which can be a character vector (for a ## single variable) or a cell array of character vectors or a string array ## (specifying a single or multiple variables). If @var{tblA} has row ## names, then @var{vars} can include the row names. Alternatively, ## @var{vars} can be a logical vector or a numeric vector of real integers ## indexing the desired variables. Unlike @code{sortrows}, positive ## integers specify a descending order, whereas negative integers specify an ## ascending order for the referenced variables, consistent with the ## descending default of @code{topkrows}. You can also index all available ## variables in @var{tblA} by passing a semicolon character argument. This ## Octave-specific syntax facilitates the use of the @var{direction} input ## argument when no particular variable needs to be selected to sort on. ## Additionally, @var{vars} can be a @qcode{vartype} object used to create a ## subscript that selects variables of a specified type. ## ## @code{@var{tblB} = topkrows (@var{tblA}, @var{k}, @dots{}, ## @var{direction})} returns the top @var{k} rows from table @var{tblA} ## sorted in the order specified by @var{direction} for any of the previous ## syntaxes. @var{direction} can be @qcode{'ascend'} or @qcode{'descend'}, ## which is applied to all specified variables or row names that ## @code{sortrows} operates on. @var{direction} can also be a cell array of ## character vectors, whose elements are @qcode{'ascend'} and ## @qcode{'descend'}, where each element corresponds to the specified ## variables and/or row names used for sorting the table. The order ## specified by @var{direction} always takes precedence over the order ## defined by a numerical vector of integers in @var{vars}. @var{direction} ## must always be the 3rd input argument. If you want to omit passing ## selected variables and allow @code{sortrows} to work on consecutive ## variables until all ties are resolved, then you can leave the second ## input argument empty, as in ## @code{sortrows (@var{tblA}, @{[]@}, @var{direction})} or pass a ## colon argument for @var{vars} as in ## @code{sortrows (@var{tblA}, @{':'@}, @var{direction})}. ## ## @code{@var{tblB} = topkrows (@dots{}, @var{k}, @var{Name}, @var{Value})} ## returns the top @var{k} rows from table @var{tblA} sorted with any of the ## previous syntaxes and further specified by additional parameters for ## sorting rows of a table with the following Name-Value paired arguments. ## ## @itemize ## @item @qcode{'MissingPlacement'} specifies the placement of missing ## values with one of the following options: @qcode{'auto'} places the ## missing elements at the bottom for ascending order and at the top for ## descending order; @qcode{'first'} places missing elements at the top; ## @qcode{'last'} places missing elements at the bottom. ## @item @qcode{'ComparisonMethod'} specifies the element comparison method ## with one of the following options: @qcode{'auto'} sorts rows using the ## real part for real numbers and the magnitude for complex numbers; ## @qcode{'real'} sorts rows using the real part for both real and complex ## numbers; @qcode{'abs'} sorts rows using the magnitude for both real and ## complex numbers. For complex numbers with equal magnitude, the phase ## angle in the interval @math{(-π, π]} is further used to break ties. ## @end itemize ## ## @code{[@var{tblB}, @var{index}] = topkrows (@dots{})} also returns an ## index vector such that @qcode{@var{tblB} = @var{tblA}(@var{index},:)}. ## ## @end deftypefn function [tbl, ix] = topkrows (this, k, varargin) ## Check for valid k if (! isscalar (k) || k < 0 || fix (k) != k) error ("table.topkrows: K must be a nonnegative integer scalar."); endif ## Unlike 'sortrows', 'topkrows' sorts in descending order by default ## (MATLAB compatibility). Split off any trailing Name-Value pairs, then ## adjust the positional (VARS, DIRECTION) arguments so that the delegated ## 'sortrows' call yields descending order whenever the caller did not ## specify an explicit DIRECTION. optNames = {'MissingPlacement', 'ComparisonMethod'}; nvStart = numel (varargin) + 1; for ii = 1:numel (varargin) if (ischar (varargin{ii}) && isrow (varargin{ii}) && ... any (strcmp (varargin{ii}, optNames))) nvStart = ii; break; endif endfor pos = varargin(1:nvStart-1); nv = varargin(nvStart:end); ## With no explicit DIRECTION (i.e. fewer than two positional arguments) ## enforce the descending default. if (numel (pos) < 2) if (numel (pos) == 0) ## No VARS: sort by all variables in descending order. pos = {':', 'descend'}; elseif (isnumeric (pos{1}) && ! isempty (pos{1})) ## Signed numeric index: flip the sign convention relative to ## 'sortrows' so that a positive index sorts descending and a ## negative index ascending. pos = {-pos{1}}; else ## Named / logical / vartype / ':' / [] selection: descending default. pos = [pos, {'descend'}]; endif endif ## Sort the table and retain the indices [tbl, ix] = sortrows (this, pos{:}, nv{:}); if (k < height (tbl)) tbl = subsetrows (tbl, 1:k); ix = ix(1:k); endif endfunction ## -*- texinfo -*- ## @deftypefn {table} {@var{tblB} =} addvars (@var{tblA}, @var{var1}, @dots{}, @var{varN}) ## @deftypefnx {table} {@var{tblB} =} addvars (@dots{}, @qcode{'After'}, @var{location}) ## @deftypefnx {table} {@var{tblB} =} addvars (@dots{}, @qcode{'Before'}, @var{location}) ## @deftypefnx {table} {@var{tblB} =} addvars (@dots{}, @qcode{'NewVariableNames'}, @var{newNames}) ## ## Add new variables to a table. ## ## @code{@var{tblB} = addvars (@var{tblA}, @var{var1}, @dots{}, @var{varN})} ## adds new variables to the right of the last variable in table @var{tblA}. ## Each of the arrays specified by the input arguments @qcode{@var{var1}, ## @dots{}, @var{varN}} becomes a new variable and its name is derived from ## the input argument's variable name or a default is created if the input ## argument is not a variable itself. The input arrays can be of any data ## type including a table as long as they have the same number of rows as ## @var{tblA}. ## ## @code{@var{tblB} = addvars (@dots{}, @code{'After'}, @var{location})} ## adds the new variables after (i.e. to the right of) the table variable ## specified in @var{location}, which can be a character vector, a string ## scalar, a scalar integer value, or even a logical vector with ## @qcode{width (@var{tblA})} elements, as long as it indexes a single ## variable in @var{tblA}. ## ## @code{@var{tblB} = addvars (@dots{}, @code{'Before'}, @var{location})} ## adds the new variables before (i.e. to the left of) the table variable ## specified in @var{location}, which can be a character vector, a string ## scalar, a scalar integer value, or even a logical vector with ## @qcode{width (@var{tblA})} elements, as long as it indexes a single ## variable in @var{tblA}. ## ## @code{@var{tblB} = addvars (@dots{}, @code{'NewVariableNames'}, ## @var{newNames})} renames the new variables added from the previous ## syntaxes according to the names specified by @var{newNames}, which can be ## a character vector, a cell array of character vectors or a string array. ## The number of names in @var{newNames} must be the same as the number of ## added variables. ## ## @end deftypefn function tbl = addvars (this, varargin) ## Add defaults tbl_width = width (this); ix_insert = tbl_width; AB_insert = true; # after by default ## Parse optional Name-Value paired arguments optNames = {'After', 'Before', 'NewVariableNames'}; dfValues = {[], [], []}; [After, Before, newVarNames, args] = ... parsePairedArguments (optNames, dfValues, varargin(:)); ## Check optional Name-Value paired arguments if (! isempty (After) && ! isempty (Before)) error ("table.addvars: cannot use both 'After' and 'Before' options."); endif ## All other errors will be handled by 'resolveVarRef' for invalid input msg_error1 = "table.addvars: LOCATION must index a single variable."; msg_error2 = strcat ("table.addvars: LOCATION must be either a", ... " scalar integer, a character vector, or a", ... " logical vector indexing a single table variable."); if (! isempty (After)) if ((isnumeric (After) && isscalar (After)) || ischar (After) || ... (isa (After, 'string') && isscalar (After))) ix_insert = resolveVarRef (this, After); elseif (isvector (After) && islogical (After)) ix_insert = resolveVarRef (this, After); if (numel (ix_insert) > 1) error (msg_error1); endif else error (msg_error2); endif elseif (! isempty (Before)) if ((isnumeric (Before) && isscalar (Before)) || ischar (Before) || ... (isa (Before, 'string') && isscalar (Before))) ix_insert = resolveVarRef (this, Before); AB_insert = false; elseif (isvector (Before) && islogical (Before)) ix_insert = resolveVarRef (this, Before); AB_insert = false; if (numel (ix_insert) > 1) error (msg_error1); endif else error (msg_error2); endif endif if (isempty (newVarNames)) ## Create names for new variables offset = width (this); # for incrementing automatic variable naming newVarNames = cell (size (args)); for i = 1:numel (args) newVarNames{i} = inputname (i+1); if (isempty (newVarNames{i})) newVarNames{i} = sprintf ("Var%d", i + offset); ## Catch case that Var1 ... already exists while (ismember (newVarNames{i}, this.VariableNames)) newVarNames{i} = sprintf ("Var%d", i + offset); offset++; endwhile endif endfor else ## Force to cellstr (in case of string array) newVarNames = cellstr (newVarNames); if (numel (args) != numel (newVarNames)) error (strcat ("table.addvars: NEWNAMES does not match the", ... " number of new variables.")); endif if (numel (__unique__ (newVarNames)) != numel (newVarNames)) error ("table.addvars: NEWNAMES contains duplicate names."); endif idx = ismember (newVarNames, this.VariableNames); if (any (idx)) if (sum (idx) == 1) error ("table.addvars: new variable name '%s' already exists.", ... newVarNames{idx}); else msg_error3 = sprintf ("'%s', ", newVarNames{idx}); msg_error3(end-1:end) = []; error ("table.addvars: new variable names %s already exist.", ... msg_error3); endif endif endif ## Append the new variables tbl = this; for i = 1:numel (args) tbl = setvar (tbl, newVarNames{i}, args{i}); endfor ## Relocate new variables (if necessary) if (AB_insert) # after if (ix_insert < tbl_width) ix_L = [1:ix_insert]; ix_M = [tbl_width+1:tbl_width+numel(args)]; ix_R = [ix_insert+1:tbl_width]; ixVars = [ix_L, ix_M, ix_R]; tbl = subsetvars (tbl, ixVars); endif else # before if (ix_insert > 1) ix_L = [1:ix_insert-1]; ix_M = [tbl_width+1:tbl_width+numel(args)]; ix_R = [ix_insert:tbl_width]; ixVars = [ix_L, ix_M, ix_R]; else ixVars = [tbl_width+1:tbl_width+numel(args), 1:tbl_width]; endif tbl = subsetvars (tbl, ixVars); endif endfunction ## -*- texinfo -*- ## @deftypefn {table} {@var{tblB} =} renamevars (@var{tblA}, @var{vars}, @var{newNames}) ## ## Rename variables in a table. ## ## @code{@var{tblB} = renamevars (@var{tblA}, @var{vars}, @var{newNames})} ## renames the selected variables in the table @var{tblA} specified by ## @var{vars} using the names in @var{newNames}. ## ## @var{vars} can be any of the following types. ## @itemize ## @item a character vector specifying a single variable. ## @item a cell array of character vectors specifying a single or multiple ## variables. ## @item a string array specifying a single or multiple variables. ## @item a numeric array of integer values indexing the variables to be ## renamed. ## @item a logical vector of the same length as the width of the table ## @var{tblA} indexing as @qcode{true} the variables to be renamed. ## @item a @qcode{vartype} object used to create a subscript that selects ## variables of a specified type. ## @end itemize ## ## @var{newNames} can either be a character vector (when renaming a single ## variable) or a cell array of character vectors or a string array. The ## number of names specified by @var{newNames} must match the number of ## variables specified by @var{vars}. ## ## @end deftypefn function tbl = renamevars (this, vars, newNames) ## Check input arguments if (nargin < 3 || isempty (vars) || isempty (newNames)) error ("table.renamevars: too few input arguments."); endif if (! iscellstr (newNames) && ! isa (newNames, 'string') && ! (ischar (newNames) && isvector (newNames))) error (strcat ("table.renamevars: NEWNAMES must be either a", ... " character vector, a cell array of character", ... " vectors, or a string array.")); endif ## Force to cellstring and get indices newNames = cellstr (newNames); if (numel (__unique__ (newNames)) != numel (newNames)) error ("table.renamevars: NEWNAMES contains duplicate names."); endif ixVars = resolveVarRef (this, vars, 'lenient'); ## Check selected variables if (any (ixVars == 0)) error ("table.renamevars: cannot index non-existing variable: '%s'.",... vars{find (ixVars == 0)}); elseif (numel (ixVars) != numel (newNames)) error (strcat ("table.renamevars: number of names in NEWNAMES do", ... " not match the selected variables specified by", ... " VARS.")); endif ## Rename the indexed variables tbl = this; tbl.VariableNames(ixVars) = newNames; ## Check for duplicate names if (numel (__unique__ (tbl.VariableNames)) != numel (tbl.VariableNames)) error (strcat ("table.renamevars: newly assigned variable name", ... " already exists.")); endif endfunction ## -*- texinfo -*- ## @deftypefn {table} {@var{tblB} =} movevars (@var{tblA}, @var{vars}) ## @deftypefnx {table} {@var{tblB} =} movevars (@dots{}, @qcode{'After'}, @var{location}) ## @deftypefnx {table} {@var{tblB} =} movevars (@dots{}, @qcode{'Before'}, @var{location}) ## ## Move variables in a table. ## ## @code{@var{tblB} = movevars (@var{tblA}, @var{vars})} moves the variables ## specified by @var{vars} to the end of the input table @var{tblA}. ## ## @var{vars} can be any of the following types. ## @itemize ## @item a character vector specifying a single variable. ## @item a cell array of character vectors specifying a single or multiple ## variables. ## @item a string array specifying a single or multiple variables. ## @item a numeric array of integer values indexing the variables to be ## moved. ## @item a logical vector of the same length as the width of the table ## @var{tblA} indexing as @qcode{true} the variables to be moved. ## @item a @qcode{vartype} object used to create a subscript that selects ## variables of a specified type. ## @end itemize ## ## @code{@var{tblB} = movevars (@dots{}, @code{'After'}, @var{location})} ## moves the selected variables after (i.e. to the right of) the table ## variable specified in @var{location}, which can be a character vector, a ## string scalar, a scalar integer value, or even a logical vector with ## @qcode{width (@var{tblA})} elements, as long as it indexes a single ## variable in @var{tblA} which is not selected by @var{vars}. ## ## @code{@var{tblB} = movevars (@dots{}, @code{'Before'}, @var{location})} ## moves the selected variables before (i.e. to the left of) the table ## variable specified in @var{location}, which can be a character vector, a ## string scalar, a scalar integer value, or even a logical vector with ## @qcode{width (@var{tblA})} elements, as long as it indexes a single ## variable in @var{tblA} which is not selected by @var{vars}. ## ## @end deftypefn function tbl = movevars (this, vars, varargin) ## Check input argument if (nargin < 2 || isempty (vars)) error ("table.movevars: too few input arguments."); endif ## Add defaults tbl_width = width (this); ix_insert = tbl_width; AB_insert = true; # after by default ## Parse optional Name-Value paired arguments optNames = {'After', 'Before'}; dfValues = {[], []}; [After, Before] = parsePairedArguments (optNames, dfValues, varargin(:)); ## Check optional Name-Value paired arguments if (! isempty (After) && ! isempty (Before)) error ("table.movevars: cannot use both 'After' and 'Before' options."); endif ## All other errors will be handled by 'resolveVarRef' for invalid input msg_error1 = "table.movevars: LOCATION must index a single variable."; msg_error2 = strcat ("table.movevars: LOCATION must be either a", ... " scalar integer, a character vector, or a", ... " logical vector indexing a single table variable."); msg_error3 = strcat ("table.movevars: LOCATION does not index an", ... " existing variable."); if (! isempty (After) || ! isempty (Before)) if (! isempty (Before)) AB_insert = false; After = Before; endif if ((isnumeric (After) && isscalar (After)) || ischar (After) || ... (isa (After, 'string') && isscalar (After))) ix_insert = resolveVarRef (this, After, 'lenient'); elseif (isvector (After) && islogical (After)) ix_insert = resolveVarRef (this, After, 'lenient'); if (numel (ix_insert) > 1) error (msg_error1); endif else error (msg_error2); endif ## Grab silent errors returned by 'resolveVarRef' if (any (ix_insert == 0)) error (msg_error3); endif endif ## Get variables to be moved mvVar = resolveVarRef (this, vars, 'lenient'); if (any (mvVar == 0)) vars = cellstr (vars); error ("table.movevars: cannot index non-existing variable: '%s'.", ... vars{find (mvVar == 0)}); endif ## Get variables that remain static stVar = 1:tbl_width; stVar(mvVar) = []; ## Construct remapping vector if (AB_insert) # after if (ix_insert < tbl_width) ## Check LOCATION variable is a static one if (ismember (ix_insert, mvVar)) error ("table.movevars: LOCATION variable cannot be moved."); endif ix_L = stVar(stVar <= ix_insert); ix_R = stVar(stVar > ix_insert); ixVars = [ix_L, mvVar, ix_R]; else ixVars = [stVar, mvVar]; endif else # before if (ix_insert > 1) ## Check LOCATION variable is a static one if (ismember (ix_insert, mvVar)) error ("table.movevars: LOCATION variable cannot be moved."); endif ix_L = stVar(stVar < ix_insert); ix_R = stVar(stVar >= ix_insert); ixVars = [ix_L, mvVar, ix_R]; else ixVars = [mvVar, stVar]; endif endif ## Return remapped table tbl = subsetvars (this, ixVars); endfunction ## -*- texinfo -*- ## @deftypefn {table} {@var{tblB} =} removevars (@var{tblA}, @var{vars}) ## ## Remove variables from a table. ## ## @code{@var{tblB} = removevars (@var{tblA}, @var{vars})} removes the ## variables specified by @var{vars} from the input table @var{tblA}. ## ## @var{vars} can be any of the following types. ## @itemize ## @item a character vector specifying a single variable. ## @item a cell array of character vectors specifying a single or multiple ## variables. ## @item a string array specifying a single or multiple variables. ## @item a numeric array of integer values indexing the variables to be ## removed. ## @item a logical vector of the same length as the width of the table ## @var{tblA} indexing as @qcode{true} the variables to be removed. ## @item a @qcode{vartype} object used to create a subscript that selects ## variables of a specified type. ## @end itemize ## ## @end deftypefn function tbl = removevars (this, vars) ## Check input argument if (nargin < 2 || isempty (vars)) error ("table.removevars: too few input arguments."); endif ## Resolve variables to be removed ixVar = resolveVarRef (this, vars); ## Remove selected variables tbl = this; tbl.VariableTypes(ixVar) = []; tbl.VariableNames(ixVar) = []; tbl.VariableValues(ixVar) = []; tbl.VariableDescriptions(ixVar) = []; tbl.VariableUnits(ixVar) = []; ## Check for custom variable properties and remove accordingly if (! isempty (this.CustomProperties)) cpIdx = strcmp (this.CustomPropTypes, "variable"); if (any (cpIdx)) ## Get the fieldnames of custom variable properties cpNames = fieldnames (this.CustomProperties); cpNames = cpNames(cpIdx); ## Remove referenced variable values from custom variable properties for i = 1:numel (cpNames) tmp = this.CustomProperties.(cpNames{i}); if (! isempty (tmp)) tmp(ixVar) = []; tbl.CustomProperties.(cpNames{i}) = tmp; endif endfor endif endif endfunction ## -*- texinfo -*- ## @deftypefn {table} {@var{tblB} =} splitvars (@var{tblA}) ## @deftypefnx {table} {@var{tblB} =} splitvars (@var{tblA}, @var{vars}) ## @deftypefnx {table} {@var{tblB} =} splitvars (@dots{}, @qcode{'NewVariableNames'}, @var{NewNames}) ## ## Split multicolumn variables in a table. ## ## @code{@var{tblB} = splitvars (@var{tblA})} splits multicolumn variables ## in @var{tblA} so that they are single-column variables in @var{tblB}, ## while all single-column variables in @var{tblA} are copied to @var{tblB} ## unaltered. Each newly created single-column variable in @var{tblB} is ## uniquely named by joining the name of its parent multicolumn variable in ## @var{tblA} and the corresponding column number. If a variable in ## @var{tblA} contains a table, then each variable of this nested table is ## returned as a newly created variable in @var{tblB}. By default, these ## variables retain their original name in the nested table, unless there ## are duplicate names, in which case the name of the nested table is also ## used. If the nested table in @var{tblA} contains a multicolumn variable, ## then the newly created variable in @var{tblB} is also multicolumnar. ## ## @code{@var{tblB} = splitvars (@var{tblA}, @var{vars})} splits only the ## variables in @var{tblA} specified by @var{vars}. If left empty, it ## defaults to all variables that can be split. Single-column variables ## specified in @var{vars} are copied unaltered. ## ## @var{vars} can be any of the following types. ## @itemize ## @item a character vector specifying a single variable. ## @item a cell array of character vectors specifying a single or multiple ## variables. ## @item a string array specifying a single or multiple variables. ## @item a numeric array of integer values indexing the variables to be ## split. ## @item a logical vector of the same length as the width of the table ## @var{tblA} indexing as @qcode{true} the variables to be split. ## @item a @qcode{vartype} object used to create a subscript that selects ## variables of a specified type. ## @end itemize ## ## @code{@var{tblB} = splitvars (@dots{}, @qcode{'NewVariableNames'}, ## @var{NewNames})} assigns new names to the variables that are split out of ## @var{tblA} and copied to @var{tblB}. @var{NewNames} can be specified as ## a cell array of character vectors and/or string arrays. ## ## @end deftypefn function tbl = splitvars (this, varargin) ## Check max number of input arguments if (nargin > 4) error ("table.splitvars: too many input arguments."); endif ## Parse optional Name-Value paired arguments optNames = {'NewVariableNames'}; dfValues = {[]}; [newNames, vars] = parsePairedArguments (optNames, dfValues, varargin(:)); ## Get vars to actually split if (isempty (vars)) vars_to_split = []; for ix = 1:width (this) if (size (this.VariableValues{ix}, 2) > 1) # multicolumn variable vars_to_split(end+1) = ix; elseif (istable (this.VariableValues{ix})) # nested table vars_to_split(end+1) = ix; endif endfor [ixVars, oldNames] = resolveVarRef (this, vars_to_split); else [ixVars, oldNames] = resolveVarRef (this, vars{1}); [ixVars, ixSorted] = sort (ixVars); oldNames = oldNames(ixSorted); ## Ignore referenced variables that cannot be split for ix = numel (ixVars):-1:1 if (size (this.VariableValues{ixVars(ix)}, 2) == 1) ixVars(ix) = []; oldNames(ix) = []; endif endfor endif ## Return input table if there's nothing to split if (isempty (ixVars)) tbl = this; return; endif ## Create a remapping vector along with the corresponding variable names ixCols = []; ix_remap = []; ix_names = {}; for ix = 1:width (this) if (ismember (ix, ixVars)) tmp = this.VariableValues{ix}; col = size (tmp, 2); ix_remap = [ix_remap, repmat(ix, 1, col)]; if (istable (tmp)) ix_names = [ix_names, tmp.VariableNames]; else fcn = @(x) sprintf ("%s_%d", this.VariableNames{ix}, x); newnames = arrayfun (fcn, 1:col, 'UniformOutput', false); ix_names = [ix_names, newnames]; endif ixCols(end+1) = col; else ix_remap(end+1) = ix; ix_names{end+1} = this.VariableNames{ix}; endif endfor ## If there are duplicate variable names, this means that there are ## nested tables with identical variable names. Switch to optional ## 'nestedTableName_varName' naming convention applied only on tables ## with duplicated variable names. if (numel (__unique__ (ix_names)) != numel (ix_names)) dup_N = arrayfun (@(k) sum (arrayfun (@(j) isequal (ix_names{k}, ... ix_names{j}), 1:numel (ix_names))), ... 1:numel (ix_names)); dup_names = ix_names (dup_N > 1); ixCols = 0; ix_remap = []; ix_names = {}; for ix = 1:width (this) if (ismember (ix, ixVars)) tmp = this.VariableValues{ix}; col = size (tmp, 2); ix_remap = [ix_remap, repmat(ix, 1, col)]; if (istable (tmp)) if (any (ismember (dup_names, tmp.VariableNames))) fcn = @(x) sprintf ("%s_%s", this.VariableNames{ix}, x); newnames = cellfun (fcn, tmp.VariableNames, ... 'UniformOutput', false); ix_names = [ix_names, newnames]; else ix_names = [ix_names, tmp.VariableNames]; endif else fcn = @(x) sprintf ("%s_%d", this.VariableNames{ix}, x); newnames = arrayfun (fcn, 1:col, 'UniformOutput', false); ix_names = [ix_names, newnames]; endif ixCols(end+1) = col; else ix_remap(end+1) = ix; ix_names{end+1} = this.VariableNames{ix}; endif endfor endif ## Create the new table by duplicating splitable variables ## and set new variable names tbl = subsetvars (this, ix_remap); tbl.VariableNames = ix_names; ## Split the multicolumn data into separate variables idx = 1; # variable index idc = 1; # new name index for ix = 1:width (this) if (ismember (ix, ixVars)) tmp = this.VariableValues{ix}; col = size (tmp, 2); ## Check for user defined new variable names if (! isempty (newNames)) if (! iscellstr (newNames) && iscell (newNames)) if (iscellstr (newNames{idc})) varNames = newNames{idc}; elseif (isa (newNames{idc}, 'string')) varNames = cellstr (newNames{idc}); else error (strcat ("table.splitvars: invalid input for", ... " 'NewVariableNames'.")); endif idc += 1; elseif (iscellstr (newNames) && idc == 1) varNames = newNames; elseif error ("table.splitvars: invalid input for 'NewVariableNames'."); endif if (numel (varNames) != col) error ("table.splitvars: wrong number of 'NewVariableNames'."); endif change_newNames = true; else change_newNames = false; endif ## Change variable data here if (istable (tmp)) for i = 1:col ## Copy new variable name if given if (change_newNames) tbl.VariableNames{idx} = varNames{i}; endif ## Copy data from each separate column tbl.VariableValues{idx} = tmp.VariableValues{i}; ## Copy variable properties from nested table tbl.VariableTypes{idx} = tmp.VariableTypes{i}; if (! isempty (tmp.VariableDescriptions{i})) tbl.VariableDescriptions{idx} = tmp.VariableDescriptions{i}; endif if (! isempty (tmp.VariableUnits{i})) tbl.VariableUnits{idx} = tmp.VariableUnits{i}; endif ## Variable-scoped custom properties are already replicated to the ## split columns by subsetvars via the repeated indices in ## 'ix_remap', so no further handling is needed here. idx += 1; endfor else for i = 1:col ## Copy new variable name if given if (change_newNames) tbl.VariableNames{idx} = varNames{i}; endif ## Copy data from each separate column tbl.VariableValues{idx} = tmp(:,i); tbl.VariableTypes{idx} = class (tmp(:,1)); idx += 1; endfor endif else idx += 1; endif endfor endfunction ## -*- texinfo -*- ## @deftypefn {table} {@var{tblB} =} mergevars (@var{tblA}, @var{vars}) ## @deftypefnx {table} {@var{tblB} =} mergevars (@var{tblA}, @var{vars}, @var{Name}, @var{Value}) ## ## Merge table variables into a single multicolumn variable. ## ## @code{@var{tblB} = mergevars (@var{tblA}, @var{vars})} combines the table ## variables in @var{tblA} specified by @var{vars} to create a new ## multicolumn variable in @var{tblB}. All other variables in @var{tblA} ## are copied to @var{tblB} unaltered. By default, the name of the merged ## variable in @var{tblB} takes the form @math{VarN}, where @math{N} is the ## position of the first variable in @var{tblA} among those to be merged, ## which is also the location of the merged variable in @var{tblB}. ## ## Note that merging variables with a @qcode{'string'} data type variable ## will result in a multicolumn variable of @qcode{'string'} data type, by ## initially converting all other to-be-merged variables into ## @qcode{'string'} data type. ## ## @var{vars} can be any of the following types. ## @itemize ## @item a character vector specifying a single variable. ## @item a cell array of character vectors specifying a single or multiple ## variables. ## @item a string array specifying a single or multiple variables. ## @item a numeric array of integer values indexing the variables to be ## merged. ## @item a logical vector of the same length as the width of the table ## @var{tblA} indexing as @qcode{true} the variables to be merged. ## @item a @qcode{vartype} object used to create a subscript that selects ## variables of a specified type. ## @end itemize ## ## @code{@var{tblB} = mergevars (@dots{}, @var{Name}, @var{Value})} further ## specifies additional parameters for merging table variables with the ## following Name-Value paired arguments. ## ## @itemize ## @item @qcode{'NewVariableName'} specifies the name of the merged variable ## in @var{tblB}, which must be unique. @qcode{'NewVariableName'} must be ## either a cellstr or string scalar or a character vector. ## @item @qcode{'MergeAsTable'} specifies whether the selected variables ## should be merged into a multicolumn variable (default) or into a table ## nested into a variable, which is useful for variables that cannot be ## concatenated due to incompatible variable types. @qcode{'MergeAsTable'} ## must be either a boolean scalar or a numeric scalar value of @qcode{1} ## (@qcode{true}) or @qcode{0} (@qcode{false}). ## @end itemize ## ## @end deftypefn function tbl = mergevars (this, vars, varargin) ## Check input argument if (nargin < 2 || isempty (vars)) error ("table.mergevars: too few input arguments."); endif ## Parse optional Name-Value paired arguments optNames = {'NewVariableName', 'MergeAsTable'}; dfValues = {[], false}; [newVarName, mergeAsTable] = parsePairedArguments (optNames, dfValues, ... varargin(:)); ## Check user input for 'MergeAsTable' if (! isscalar (mergeAsTable)) error ("table.mergevars: invalid input for 'MergeAsTable'."); endif if (! (isbool (mergeAsTable) || ismember (mergeAsTable, [0, 1]))) error ("table.mergevars: invalid input for 'MergeAsTable'."); endif ## Resolve variables to be removed [ixVars, varNames] = resolveVarRef (this, vars); if (isscalar (ixVars)) tbl = this; return; endif ## Get name and location for new variable [ixVars, ixSorted] = sort (ixVars); varNames = varNames(ixSorted); location = ixVars(1); if (isempty (newVarName)) newVarName = cellstr (sprintf ("Var%d", location)); else ## Check user input for 'NewVariableName' if (isa (newVarName, 'string') && isscalar (newVarName)) newVarName = cellstr (newVarName); elseif (ischar (newVarName) && isvector (newVarName)) newVarName = cellstr (newVarName); elseif (! (iscellstr (newVarName) && isscalar (newVarName))) error ("table.mergevars: invalid input for 'NewVariableName'."); endif endif ## Gather remaining variables to be copied unaltered ixRem = 1:width (this); ixRem(ixVars) = []; tbl = subsetvars (this, ixRem); ## Check that new variable name does not conflict any existing variable if (ismember (newVarName, tbl.VariableNames)) error ("table.mergevars: assigned 'NewVariableName' already exists."); endif ## Merge as a table (easy, custom properties are handled by 'subsetvars') if (mergeAsTable) newVarTable = subsetvars (this, ixVars); tbl = addvars (tbl, newVarTable, 'Before', location, ... 'NewVariableNames', newVarName); return; endif ## Merge into multicolumn variable. (keep the custom properties of the ## first variable that is to be merged) if (! mergeAsTable) ## Use the first to-be-merged variable for copying custom properties ixRem = 1:width (this); ixRem(ixVars(2:end)) = []; tbl = subsetvars (this, ixRem); ## Add a try...catch block instead of heuristics to check how ## selected variables can be merged try newVarValue = cat (2, this.VariableValues{ixVars}); catch error (strcat ("table.mergevars: selected variables cannot be", ... " merged into a multicolumn variable due to", ... " incompatible variable types.")); end_try_catch tbl.VariableTypes{location} = class (newVarValue); tbl.VariableValues{location} = newVarValue; tbl.VariableNames(location) = newVarName; endif endfunction ## -*- texinfo -*- ## @deftypefn {table} {@var{tblB} =} convertvars (@var{tblA}, @var{vars}, @var{dataType}) ## ## Convert table variables to specified data type. ## ## @code{@var{tblB} = convertvars (@var{tblA}, @var{vars}, @var{dataType})} ## converts the variables in @var{tblA} specified by @var{vars} to the ## specified data type. ## ## @var{vars} can be any of the following types. ## @itemize ## @item a character vector specifying a single variable. ## @item a cell array of character vectors specifying a single or multiple ## variables. ## @item a string array specifying a single or multiple variables. ## @item a numeric array of integer values indexing the variables to be ## converted. ## @item a logical vector of the same length as the width of the table ## @var{tblA} indexing as @qcode{true} the variables to be converted. ## @item a @qcode{vartype} object used to create a subscript that selects ## variables of a specified type. ## @end itemize ## ## @var{dataType} specifies the data type to convert those variables to. It ## can either be a character vector defining the name of the data type to ## convert to or a function handle, which will perform the conversion. ## When specifying a name for data type conversion, ## it can either be a one-argument constructor for the specified data type, ## which must accept the selected variables' current data types as input, or ## an available method, which can be applied on selected variables' current ## data types. When specifying a function handle for applying a conversion ## on selected variables, this function handle must accept a single input ## argument and return in its output the same rows as the input argument. ## ## Either way, each resulting variable must have the same number of rows as ## the respective variable selected for conversion. However, depending on ## the chosen type of conversion, the columns of the converted variable(s) ## might differ. It is up to the user to ensure that the appropriate type ## of conversion is performed. @code{convertvars} only checks the custom ## function handles for returning the correct number of rows, which must ## equal the number of rows of the input table, @var{tblA}. ## ## @end deftypefn function tbl = convertvars (this, vars, dataType) ## Check input arguments if (nargin < 3 || isempty (vars) || isempty (dataType)) error ("table.convertvars: too few input arguments."); endif if (ischar (dataType)) if (! isvector (dataType)) error ("table.convertvars: DATATYPE must be a character vector."); endif elseif (! isa (dataType, 'function_handle')) error (strcat ("table.convertvars: DATATYPE must be either a", ... " character vector or a function handle; got a", ... " '%s'."), ... class (dataType)); endif ## Get variables to convert (input validation is done by 'resolveVarRef') [ixVars, varNames] = resolveVarRef (this, vars); tbl = this; ## Apply conversion for i = 1:numel (ixVars) try newVarValue = feval (dataType, this.VariableValues{ixVars(i)}); catch error (strcat ("table.convertvars: specified DATATYPE", ... " conversion cannot be applied on selected", ... " variable '%s'."), ... varNames{i}); end_try_catch if (size (newVarValue, 1) != height (this)) error (strcat ("table.convertvars: specified DATATYPE", ... " conversion on '%s' does not return the", ... " appropriate amount of rows."), ... varNames{i}); endif ## Write output tbl.VariableTypes{ixVars(i)} = class (newVarValue); tbl.VariableValues{ixVars(i)} = newVarValue; endfor endfunction ## -*- texinfo -*- ## @deftypefn {table} {@var{tblB} =} rows2vars (@var{tblA}) ## @deftypefnx {table} {@var{tblB} =} rows2vars (@var{tblA}, @var{Name}, @var{Value}) ## ## Reorient table by swapping rows into variables. ## ## @code{@var{tblB} = rows2vars (@var{tblA})} reorients the input table ## @var{tblA} so that its rows become variables in the output table ## @var{tblB} and the variables are swapped into rows and their names ## are stored into a new variable at the beginning of the output table. If ## the contents of @var{tblA} can be concatenated, then the corresponding ## variables of @var{tblB} are arrays, otherwise they are cell arrays. If ## the input table @var{tblA} contains @qcode{RowNames}, then those names ## become the variable names of the output table @var{tblB}, otherwise the ## variable names of @var{tblB} are generated automatically. ## @code{rows2vars} cannot handle multicolumn variables or nested tables. ## ## @code{@var{tblB} = rows2vars (@dots{}, @var{Name}, @var{Value})} further ## specifies additional parameters for reorienting the table with the ## following Name-Value paired arguments. ## ## @itemize ## @item @qcode{'DataVariables'} specifies the variables from input table ## @var{tblA} which will be reoriented. @qcode{'DataVariables'} can be any ## of the following types: a character vector specifying a single variable; ## a cell array of character vectors or a string array specifying a single ## or multiple variables; a numeric array of integer values specifying a ## single or multiple variables; a logical vector of the same length as the ## width of the input table specifying a single or multiple variables. ## @item @qcode{'VariableNamesSource'} specifies a single variable that ## contains the variable names for the output table. The values of the ## selected variable must have a data type which can be converted to strings ## and the number of unique names in the selected variable must match the ## number of rows of the input table. @qcode{'VariableNamesSource'} accepts ## the same data types supported by @qcode{'DataVariables'} as long as they ## index a single variable, which, however, must not be specified by the ## @qcode{'DataVariables'} Name-Value paired argument. ## @item @qcode{'VariableNamingRule'} must be a character vector specifying ## the rule for naming variables in the output table @var{tblB}. When set ## to @qcode{'modify'} (default), the variable names are modified so that ## they are valid variable identifiers. When set to @qcode{'preserve'}, the ## original names are preserved. ## @end itemize ## ## @end deftypefn function tbl = rows2vars (this, varargin) ## Parse optional Name-Value paired arguments optNames = {'DataVariables', 'VariableNamesSource', 'VariableNamingRule'}; dfValues = {[], [], 'modify'}; [varRef, source, rule] = parsePairedArguments (optNames, dfValues, ... varargin(:)); ## Check user input for 'DataVariables' if (! isempty (varRef)) ixVar = resolveVarRef (this, varRef, 'lenient'); if (any (ixVar == 0)) varRef = cellstr (varRef); error (strcat ("table.rows2vars: 'DataVariables' index a", ... " non-existing variable: '%s'."), ... varRef{find (ixVar == 0)}); endif tbl = subsetvars (this, ixVar); else tbl = this; endif ## Check user input for 'VariableNamesSource' if (! isempty (source)) srcVar = resolveVarRef (this, source, 'lenient'); if (! isscalar (srcVar)) error (strcat ("table.rows2vars: 'VariableNamesSource' must", ... " index a single variable.")); elseif (any (srcVar == 0)) source = cellstr (source); error (strcat ("table.rows2vars: 'VariableNamesSource' indexes", ... " a non-existing variable: '%s'."), ... source{find (srcVar == 0)}); endif ## The number of names taken from the specified table variable ## must match the number of rows of the input table. newVarNames = this.VariableValues{srcVar}; if (! iscellstr (newVarNames)) newVarNames = cellstr (string (newVarNames)); endif if (numel (__unique__ (newVarNames)) != height (this)) error (strcat ("table.rows2vars: the number of names taken", ... " from the variable specified in", ... " 'VariableNamesSource' does not match the", ... " number of rows in input table.")); endif ## Check that 'VariableNamesSource' does not specify a variable ## that is specified by 'DataVariables', otherwise remove it from ## returning table if (! isempty (varRef)) if (ismember (srcVar, ixVar)) error (strcat ("table.rows2vars: 'VariableNamesSource'", ... " cannot specify a variable that is also", ... " specified by 'DataVariables'.")); endif else tbl = removevars (tbl, srcVar); endif ## If input table has RowNames remove them if (! isempty (tbl.RowNames)) tbl.RowNames = {}; endif elseif (! isempty (tbl.RowNames)) newVarNames = tbl.RowNames; tbl.RowNames = {}; else rows = height (tbl); newVarNames = cell (1, rows); for i = 1:rows newVarNames{i} = sprintf ("Var%d", i); endfor endif ## Handle variable naming rule if (strcmpi (rule, 'modify')) for i = 1:numel (newVarNames) if (! isvarname (newVarNames{i})) newVarNames{i} = matlab.lang.makeValidName (newVarNames{i}); endif endfor elseif (! strcmpi (rule, 'preserve')) error ("table.rows2vars: invalid input for 'VariableNamingRule'."); endif ## Check for multicolumn variables and nested tables for i = 1:width (tbl) if (isa (tbl.VariableValues{i}, 'table')) error (strcat ("table.rows2vars: input table must not contain", ... " nested tables.")); elseif (size (tbl.VariableValues{i}, 2) > 1) error (strcat ("table.rows2vars: input table must not contain", ... " multicolumn variables.")); endif endfor ## Check column types to decide whether to return arrays or cell arrays col_types = cellfun (@(x) class (x), tbl.VariableValues, ... 'UniformOutput', false); if (isscalar (__unique__ (col_types))) matrix = cat (2, tbl.VariableValues{:})'; new_var_values = num2cell (matrix, 1); out = table (new_var_values{:}, 'VariableNames', newVarNames); else cols_as_cells = cell (1, width (tbl)); for i = 1:width (tbl) if (iscellstr (tbl.VariableValues{i})) cols_as_cells{i} = tbl.VariableValues{i}; elseif (iscell (tbl.VariableValues{i})) cols_as_cells{i} = tbl.VariableValues{i}; else cols_as_cells{i} = num2cell (tbl.VariableValues{i}); endif endfor matrix = cat (2, cols_as_cells{:})'; out = table (); for i = 1:height (tbl) tmp = table (matrix(:,i), 'VariableNames', newVarNames(i)); out = [out tmp]; endfor endif ## Merge original variable names into the table OriginalVariableNames = tbl.VariableNames(:); OriginalVariableNames = table (OriginalVariableNames); tbl = [OriginalVariableNames, out]; ## Fix lengths of VariableDescriptions and VariableUnits tbl.VariableDescriptions = repmat ({''}, 1, size (tbl, 2)); tbl.VariableUnits = repmat ({''}, 1, size (tbl, 2)); ## Assign variable types in the new table new_types = cellfun ('class', tbl.VariableValues, 'UniformOutput', false); tbl.VariableTypes = new_types; ## Remove any custom variable properties if (! isempty (tbl.CustomProperties)) cp_names = fieldnames (this.CustomProperties); cp_types = this.CustomPropTypes; idx = find (strcmpi (cp_types, "variable")); ## Remove custom variable properties only if (! isempty (idx)) for i = idx tbl = rmprop (tbl, cp_names{i}); endfor endif endif endfunction ## -*- texinfo -*- ## @deftypefn {table} {@var{tblB} =} stack (@var{tblA}, @var{vars}) ## @deftypefnx {table} {@var{tblB} =} stack (@var{tblA}, @{@var{vars1}, @dots{}, @var{varsN}@}) ## @deftypefnx {table} {@var{tblB} =} stack (@dots{}, @var{Name}, @var{Value}) ## @deftypefnx {table} {[@var{tblB}, @var{idxA}] =} stack (@dots{}) ## ## Stack multiple table variables into a single table variable. ## ## @code{@var{tblB} = stack (@var{tblA}, @var{vars})} stacks the values from ## the variables @var{vars} in input @var{tblA} into a single variable in ## output table @var{tblB}. By default, the stacked variable in @var{tblB} ## is named by joining the names of the variables in @var{tblA} as defined ## by @var{vars}, and it inherits the units and description of the first ## variable in @var{vars}. Additionally, a new categorical variable is ## included in @var{tblB} that indicates which variable in @var{tblA} the ## stacked data in each row of @var{tblB} comes from. By default, this ## categorical variable is named by appending @qcode{'_Indicator'} to the ## name of the stacked variable. Variables in @var{tblA} that are not ## defined in @var{vars} for stacking are replicated in @var{tblB}. If ## @var{tblA} contains @qcode{RowNames}, these are not stacked. ## ## @code{@var{tblB} = stack (@var{tblA}, @{@var{vars1}, @dots{}, ## @var{varsN}@})} stacks multiple groups of variables, given as a cell ## array of variable references, producing one stacked data variable in ## @var{tblB} per group (each named and metadata-inherited from its own ## group). All groups must contain the same number of variables. In this ## case a single indicator variable, named @qcode{'Indicator'} by default, ## holds the numeric position within each group of the source variable for ## each stacked value. ## ## @var{vars} can be any of the following types. ## @itemize ## @item a character vector specifying a single variable. ## @item a cell array of character vectors specifying a single or multiple ## variables. ## @item a string array specifying a single or multiple variables. ## @item a numeric array of integer values indexing the variables to be ## stacked. ## @item a logical vector of the same length as the width of the table ## @var{tblA} indexing as @qcode{true} the variables to be stacked. ## @item a @qcode{vartype} object used to create a subscript that selects ## variables of a specified type. ## @end itemize ## ## @code{@var{tblB} = stack (@dots{}, @var{Name}, @var{Value})} further ## specifies additional parameters for stacking table variables with the ## following Name-Value paired arguments. ## ## @itemize ## @item @qcode{'ConstantVariables'} specifies the variables other than ## @var{vars} to include in the output table. By default, all remaining ## variables not specified by @var{vars} are included in the output table. ## Specifying @qcode{'ConstantVariables'} allows you to select specific ## variables to replicate in @var{tblB}. Row names in @var{tblA} are always ## replicated in @var{tblB}. You can specify @qcode{'ConstantVariables'} in ## the same manner as with @var{vars}. ## @item @qcode{'NewDataVariableName'} specifies the name for the new data ## variable in the output table @var{tblB}. It can be a character vector, ## a string scalar, or a cellstring scalar. ## @item @qcode{'IndexVariableName'} specifies the name for the new ## indicator variable in the output table @var{tblB}. It can be a character ## vector, a string scalar, or a cellstring scalar. ## @end itemize ## ## @code{[@var{tblB}, @var{idxA}] = stack (@dots{})} also returns an index ## vector, @var{idxA}, indicating the correspondence between the rows in ## @var{tblB} and the rows in @var{tblA}. ## ## @end deftypefn function [tbl, idxA] = stack (this, vars, varargin) ## Check input argument if (nargin < 2 || isempty (vars)) error ("table.stack: too few input arguments."); endif ## Parse optional Name-Value paired arguments optNames = {'ConstantVariables', 'NewDataVariableName', ... 'IndexVariableName'}; dfValues = {[], [], []}; [constVars, newVarName, idxVarName] = ... parsePairedArguments (optNames, dfValues, varargin(:)); ## Determine single- vs multi-group stacking. Multiple groups of ## variables to stack are passed as a cell array of variable references ## (each a cellstr, string, numeric, or logical index), producing one ## stacked data variable per group; a single group is any other valid ## variable reference. isMulti = iscell (vars) && ! iscellstr (vars); if (isMulti) groups = vars; else groups = {vars}; endif nGroup = numel (groups); ## Resolve each group of variables to stack grpIx = cell (1, nGroup); grpNames = cell (1, nGroup); for g = 1:nGroup [ix, nm] = resolveVarRef (this, groups{g}, 'lenient'); if (any (ix == 0)) gv = cellstr (groups{g}); error ("table.stack: VARS index a non-existing variable: '%s'.", ... gv{find (ix == 0)(1)}); endif grpIx{g} = ix(:)'; grpNames{g} = nm; endfor ## All groups must contain the same number of variables grpSize = numel (grpIx{1}); if (any (cellfun (@numel, grpIx) != grpSize)) error (strcat ("table.stack: all groups of variables to stack", ... " must be the same size.")); endif allStackIx = [grpIx{:}]; ## Get constant variables to include if (isempty (constVars)) cIxVars = setdiff (1:width (this), allStackIx); else cIxVars = resolveVarRef (this, constVars, 'lenient'); if (any (cIxVars == 0)) constVars = cellstr (constVars); error (strcat ("table.stack: 'ConstantVariables' index a", ... " non-existing variable: '%s'."), ... constVars{find (cIxVars == 0)(1)}); endif if (any (ismember (cIxVars, allStackIx))) error (strcat ("table.stack: 'ConstantVariables' cannot", ... " contain any variables to be stacked as", ... " specified by VARS.")); endif endif ## Get new data variable name(s), one per group if (isempty (newVarName)) newVarName = cellfun (@(nm) strjoin (nm, '_'), grpNames, ... 'UniformOutput', false); else if (! ((ischar (newVarName) && isvector (newVarName)) || ((iscellstr (newVarName) || isa (newVarName, 'string')) && ! isempty (newVarName)))) error (strcat ("table.stack: 'NewDataVariableName' must be a", ... " character vector, or a cellstring or string", ... " array.")); endif newVarName = cellstr (newVarName); if (numel (newVarName) != nGroup) error (strcat ("table.stack: the number of 'NewDataVariableName'", ... " names must equal the number of variable groups", ... " to stack.")); endif endif ## Get index (indicator) variable name if (isempty (idxVarName)) if (isMulti) idxVarName = 'Indicator'; else idxVarName = strcat (newVarName{1}, '_Indicator'); endif else if (! ((ischar (idxVarName) && isvector (idxVarName)) || ((iscellstr (idxVarName) || isa (idxVarName, 'string')) && isscalar (idxVarName)))) error (strcat ("table.stack: 'IndexVariableName' must be", ... " either a character vector, or a cellstring or", ... " string scalar.")); endif idxVarName = char (idxVarName); endif ## Handle constant variables first (and RowNames if present) constTable = subsetvars (this, cIxVars); if (! isempty (this.RowNames)) constTable.RowNames = this.RowNames; endif constTable = repelem (constTable, grpSize, 1); ## Build the indicator variable values. For a single group these are the ## categorical names of the stacked variables; for multiple groups they ## are the numeric position within each group, since the variable names ## differ between groups. nRow = height (this); if (isMulti) idVarValues = repmat ((1:grpSize)', nRow, 1); else idVarValues = repmat (categorical (grpNames{1})', nRow, 1); endif ## Build one stacked data column per group ndCols = cell (1, nGroup); for g = 1:nGroup gvals = this.VariableValues(grpIx{g}); ndCols{g} = vec (cat (2, gvals{:})'); endfor ## Assemble the stacked table (indicator followed by the data columns) stackVals = [{idVarValues}, ndCols]; stackNames = [{idxVarName}, newVarName]; stackedTable = table (stackVals{:}, 'VariableNames', stackNames); ## Inherit units and descriptions for the new data variables from the ## first variable of each group; the indicator carries a fixed ## description and no units. ndUnits = cell (1, nGroup); ndDescr = cell (1, nGroup); for g = 1:nGroup ndUnits{g} = this.VariableUnits{grpIx{g}(1)}; ndDescr{g} = this.VariableDescriptions{grpIx{g}(1)}; endfor stackedTable.VariableUnits = [{''}, ndUnits]; stackedTable.VariableDescriptions = [{'Data indicator'}, ndDescr]; ## Merge tables tbl = [constTable, stackedTable]; ## Assign variable types in the new table new_types = cellfun ('class', tbl.VariableValues, 'UniformOutput', false); tbl.VariableTypes = new_types; ## Return index vector (if requested) if (nargout > 1) idxA = repelem ((1:nRow)', grpSize, 1); endif endfunction ## -*- texinfo -*- ## @deftypefn {table} {@var{tblB} =} unstack (@var{tblA}, @var{vars}, @var{ivar}) ## @deftypefnx {table} {@var{tblB} =} unstack (@dots{}, @var{Name}, @var{Value}) ## @deftypefnx {table} {[@var{tblB}, @var{idxA}] =} unstack (@dots{}) ## ## Unstack a single table variable into multiple table variables. ## ## @code{@var{tblB} = unstack (@var{tblA}, @var{vars}, @var{ivar})} unstacks ## the values from the variables @var{vars} according to the indicator ## variable @var{ivar} in input @var{tblA} into multiple variables in output ## table @var{tblB}. The new (unstacked) variables in @var{tblB} are named ## according to the unique values of the indicator variable and the rows ## with matching indicator values are aggregated into the new (unstacked) ## variables. By default, @qcode{numeric} and @qcode{duration} data types ## are aggregated by summation, whereas from other data types the first ## unique element of each group is returned. ## ## @var{vars} may specify one or more variables of any data type supported ## by the @qcode{table} class except for nested tables, whereas @var{ivar} ## must only specify a single variable, which must be numeric, logical, ## categorical, string, or cellstring. Both @var{vars} and @var{ivar} can ## be specified as follows: ## @itemize ## @item a character vector specifying a single variable. ## @item a cell array of character vectors specifying a single or multiple ## variables. ## @item a string array specifying a single or multiple variables. ## @item a numeric array of integer values indexing the variables to be ## unstacked. ## @item a logical vector of the same length as the width of the table ## @var{tblA} indexing as @qcode{true} the variables to be unstacked. ## @item a @qcode{vartype} object used to create a subscript that selects ## variables of a specified type. ## @end itemize ## ## By default, all remaining variables in @var{tblA} which are not specified ## by @var{vars} and @var{ivar} are treated as grouping variables, in which ## case each unique combination of values in the grouping variables ## identifies a group of rows in @var{tblA} that is unstacked into one row ## of @var{tblB}. ## ## @code{@var{tblB} = unstack (@dots{}, @var{Name}, @var{Value})} further ## specifies additional parameters for unstacking table variables with the ## following Name-Value paired arguments. ## ## @itemize ## @item @qcode{'GroupingVariables'} specifies the variables that should be ## used as grouping variables. All valid schemes for indexing a table ## variable can be used. If grouping variables have missing values, the ## data from corresponding rows are not aggregated in the output table. ## Table row names cannot be assigned as a grouping variable, since these ## must be unique for each row, which would defeat the purpose of unstacking ## a table onto itself. ## @item @qcode{'ConstantVariables'} specifies the variables that are ## constant within each group. All valid schemes for indexing a table ## variable can be used. The values for these variables in the output are ## taken from the first row in each group in the input. By default, no ## variable is treated as constant unless specified. However, if the input ## table has row names, these effectively are treated as constant variables. ## @item @qcode{'NewDataVariableNames'} specifies the names for the new data ## variables in the output table @var{tblB}. It can be a character vector, ## a string scalar, or a cellstring scalar. By default, the names of the ## new unstacked data variables are based on the string representation of ## the unique values in the indicator variable @var{ivar}. If multiple ## variables are unstacked, then @code{unstack} generates composite names ## using both the values from the indicator variable and the name of the ## variable being unstacked. The number of names must match the number of ## unique values in the indicator variable. ## @item @qcode{'AggregationFunction'} specifies a function handle used to ## aggregate each group's data into a single value. By default, ## @code{@@sum} is applied on numeric data, whereas @code{@@unique} is ## applied on all other supported data types, including @code{duration} and ## @code{calendarDuration}. In the latter case, if a group contains more ## than one distinct value for the same indicator value, the default ## aggregation errors, and an explicit @qcode{'AggregationFunction'} that ## returns a scalar must be specified. ## @item @qcode{'VariableNamingRule'}, specified as either @qcode{'modify'} ## or @qcode{'preserve'}, defines the rule for naming the new unstacked ## variables in the output table @var{tblB}. @qcode{'modify'} (default) ## forces all variable names to be valid Octave variable names. ## @qcode{'preserve'} preserves the original names taken from the input ## table, which can have any Unicode characters, including spaces and ## non-ASCII characters. ## @end itemize ## ## @code{[@var{tblB}, @var{idxA}] = unstack (@dots{})} also returns an index ## vector, @var{idxA}, indicating the correspondence between the rows in ## @var{tblB} and the rows in @var{tblA}. ## ## @end deftypefn function [tbl, idxA] = unstack (this, vars, ivar, varargin) ## Check input argument if (nargin < 3 || isempty (vars)) error ("table.unstack: too few input arguments."); endif ## Define allowed vartypes (cellstr + numeric are checked in place) allowed = {'logical', 'string', 'categorical'}; ## Parse optional Name-Value paired arguments optNames = {'GroupingVariables', 'ConstantVariables', ... 'NewDataVariableNames', 'AggregationFunction', ... 'VariableNamingRule'}; dfValues = {[], [], [], [], 'modify'}; [groupVars, constVars, newVarNames, aggrFcn, rule] = ... parsePairedArguments (optNames, dfValues, varargin(:)); ## Get variables to unstack [ixVars, ~] = resolveVarRef (this, vars, 'lenient'); if (any (ixVars == 0)) vars = cellstr (vars); error ("table.unstack: VARS index a non-existing variable: '%s'.", ... vars{find (ixVars == 0)}); endif ## Check that variables to unstack do not contain nested tables for i = ixVars if (isa (this.VariableValues{i}, 'table')) error ("table.unstack: VARS must not index nested tables."); endif endfor ## Move variables to unstack into a new table VarsTable = subsetvars (this, ixVars); ## Get indicator variable [ixIvar, ~] = resolveVarRef (this, ivar, 'lenient'); if (! isscalar (ixIvar)) error ("table.unstack: IVAR must index a single variable."); elseif (ixIvar == 0) ivar = cellstr (ivar); error ("table.unstack: IVAR indexes a non-existing variable: '%s'.", ... ivar{find (ixIvar == 0)}); endif ## Check indicator variable is not a multicolumn variable ## or member of the variables to be unstacked IvarValues = this.VariableValues{ixIvar}; if (! isvector (IvarValues)) error ("table.unstack: IVAR must index a single column variable."); endif if (ismember (ixIvar, ixVars)) error (strcat ("table.unstack: IVAR cannot be any of the", ... " variables to be unstacked as specified by VARS.")); endif ## Check indicator variable is of a valid type if (! (iscellstr (IvarValues) || isnumeric (IvarValues))) if (! ismember (class (IvarValues), allowed)) error (strcat ("table.unstack: IVAR indexes a variable of", ... " invalid type: '%s'."), ... class (IvarValues)); endif IvarValues = cellstr (string (IvarValues)); endif ## Get default names for new unstacked variables IvarNames = __unique__ (IvarValues); ## Force both names and values to cellstr if (! iscellstr (IvarNames)) IvarNames = cellstr (string (IvarNames)); IvarValues = cellstr (string (IvarValues)); endif ## Get constant variables if (! isempty (constVars)) cIxVars = resolveVarRef (this, constVars, 'lenient'); if (any (cIxVars == 0)) constVars = cellstr (constVars); error (strcat ("table.unstack: 'ConstantVariables' index a", ... " non-existing variable: '%s'."), ... constVars{find (cIxVars == 0)}); endif if (any (ismember (cIxVars, ixVars))) error (strcat ("table.unstack: 'ConstantVariables' cannot", ... " contain any variables to be unstacked as", ... " specified by VARS.")); endif if (any (ismember (cIxVars, ixIvar))) error (strcat ("table.unstack: 'ConstantVariables' cannot", ... " contain the indicator variable as specified", ... " by IVAR.")); endif else cIxVars = []; endif ## Get grouping variables if (isempty (groupVars)) gIxVars = setdiff (1:width (this), [ixVars, ixIvar, cIxVars]); else gIxVars = resolveVarRef (this, groupVars, 'lenient'); if (any (gIxVars == 0)) groupVars = cellstr (groupVars); error (strcat ("table.unstack: 'GroupingVariables' index a", ... " non-existing variable: '%s'."), ... groupVars{find (gIxVars == 0)}); endif if (any (ismember (gIxVars, ixVars))) error (strcat ("table.unstack: 'GroupingVariables' cannot", ... " contain any variables to be unstacked as", ... " specified by VARS.")); endif if (any (ismember (gIxVars, ixIvar))) error (strcat ("table.unstack: 'GroupingVariables' cannot", ... " contain the indicator variable as specified", ... " by IVAR.")); endif endif ## Exclude variables of invalid type as grouping variables (emit warning) for i = numel (gIxVars):-1:1 GvarValues = this.VariableValues{gIxVars(i)}; if (! (iscellstr (GvarValues) || isnumeric (GvarValues))) if (! ismember (class (GvarValues), allowed)) invalid = this.VariableNames{gIxVars(i)}; gIxVars(i) = []; warning (["table.unstack: 'GroupingVariables' index a variable", ... " of invalid type: '%s', which is ignored."], invalid); endif endif endfor ## Move grouping variables into a new table removeVar = setdiff (1:width (this), gIxVars); GvarTable = removevars (this, removeVar); ## Move constant variables into a new table if (! isempty (cIxVars)) if (any (ismember (cIxVars, gIxVars)) && ! isempty (groupVars)) error (strcat ("table.unstack: 'ConstantVariables' cannot", ... " contain any grouping variables as specified", ... " by 'GroupingVariables'.")); endif CvarTable = subsetvars (this, cIxVars); else CvarTable = table; endif ## Get new data variable names if (isempty (newVarNames)) newVarNames = IvarNames'; else if (! (iscellstr (newVarNames) && ! (isa (newVarNames, 'string')))) error (strcat ("table.unstack: 'NewDataVariableNames' must be", ... " either a cell array of character vectors, or", ... " a string array.")); endif if (numel (newVarNames) != numel (IvarNames)) error (strcat ("table.unstack: 'NewDataVariableNames' do not", ... " match the number of unique values in the", ... " indicator variable.")); endif endif ## Check user-defined aggregation function if (! isempty (aggrFcn)) if (! is_function_handle (aggrFcn)) error (strcat ("table.unstack: 'AggregationFunction' must be a", ... " function handle.")); endif endif ## Create table containing unique instances of grouping variables, ## otherwise use unique instances of the indicator variable. Rows whose ## grouping variables contain missing values are excluded from unstacking, ## together with the corresponding indicator, data, and constant values, ## while the original row indices are retained for the returned index. if (! isempty (GvarTable)) [GvarTable, rmRows] = rmmissing (GvarTable); validRows = ! rmRows; origIdx = find (validRows); IvarValues = IvarValues(validRows); VarsTable = subsetrows (VarsTable, origIdx); if (! isempty (CvarTable)) CvarTable = subsetrows (CvarTable, origIdx); endif [GvarTable, I, J] = unique (GvarTable, 'stable'); nrows = numel (I); rowIdx = origIdx(I); else [~, I, J] = __unique__ (IvarValues, 'stable', 'rows'); nrows = 1; rowIdx = 1; endif ## Start unstacking here if (isscalar (ixVars)) # single variable to unstack ## Handle variable naming rule ncols = numel (newVarNames); if (strcmpi (rule, 'modify')) for i = 1:ncols if (! isvarname (newVarNames{i})) newVarNames{i} = matlab.lang.makeValidName (newVarNames{i}); endif endfor elseif (! strcmpi (rule, 'preserve')) error ("table.unstack: invalid input for 'VariableNamingRule'."); endif ## Create table with unstacked variables vvals = VarsTable.VariableValues{:,:}; if (iscellstr (vvals)) vtype = 'cellstr'; else vtype = class (vvals); endif vtype = repmat ({vtype}, 1, ncols); UvarTable = table ('Size', [nrows, ncols], 'VariableTypes', vtype, ... 'VariableNames', newVarNames); ## Copy descriptions and units to unstacked variables vd = this.VariableDescriptions{ixVars}; UvarTable.VariableDescriptions = repmat ({vd}, 1, ncols); vu = this.VariableUnits{ixVars}; UvarTable.VariableUnits = repmat ({vu}, 1, ncols); ## Replicate the unstacked variable's variable-scoped custom properties ## onto each new column (MATLAB copies them); table-scoped properties are ## carried by the constant and grouping variables through the final ## horzcat that assembles the output. if (! isempty (this.CustomProperties)) cpNames = fieldnames (this.CustomProperties); for ci = 1:numel (cpNames) if (strcmp (this.CustomPropTypes{ci}, 'variable')) srcval = this.CustomProperties.(cpNames{ci})(ixVars); UvarTable.CustomProperties.(cpNames{ci}) = repmat (srcval, 1, ncols); UvarTable.CustomPropTypes{end+1} = 'variable'; endif endfor endif ## Add type-specific NaN values and handle multicolumn variables ## Check that aggregation function returns suitable output [mcvec, aggrFcn] = get_default_aggrFcn (vvals, nrows, aggrFcn); if (ischar (aggrFcn)) error (aggrFcn); endif ## Process each unstacked variable for i = 1:ncols UvarTable.VariableValues{i} = mcvec; ix = strcmp (IvarNames{i}, IvarValues); if (nrows == 1) aggrVal = aggrFcn (vvals(ix, :)); UvarTable.VariableValues{i} = aggrVal; CixRows = 1; else CixRows = []; for j = 1:nrows tmpIvarNames = IvarValues(J == j); ix = strcmp (IvarNames{i}, tmpIvarNames); if (any (ix)) aggrVec = ismember (tmpIvarNames, IvarNames{i}); aggrVal = aggrFcn (vvals(J == j, :)(aggrVec,:)); UvarTable.VariableValues{i}(j,:) = aggrVal; endif CixRows = [CixRows, find(J == j, 1)]; endfor endif endfor ## Keep corresponding rows from ConstantVariables if (! isempty (CvarTable)) CvarTable = subsetrows (CvarTable, CixRows); endif else # multiple variables to unstack nvars = numel (ixVars); ncols = numel (newVarNames); expVarNames = cell (1, nvars * ncols); expVarTypes = expVarNames; ## Create composite variable names and get vartypes ij = 1; for i = 1:nvars vvals = VarsTable.VariableValues{i}; if (iscellstr (vvals)) vtype = 'cellstr'; else vtype = class (vvals); endif for j = 1:ncols expVarNames{ij} = sprintf ('%s_%s', VarsTable.VariableNames{i}, ... newVarNames{j}); expVarTypes{ij} = vtype; ij++; endfor endfor ## Handle variable naming rule if (strcmpi (rule, 'modify')) for i = 1:numel (expVarNames) if (! isvarname (expVarNames{i})) expVarNames{i} = matlab.lang.makeValidName (expVarNames{i}); endif endfor elseif (! strcmpi (rule, 'preserve')) error ("table.unstack: invalid input for 'VariableNamingRule'."); endif ## Create table for each unstacked variable UvarTable = table ('Size', [nrows, ncols*nvars], ... 'VariableTypes', expVarTypes, ... 'VariableNames', expVarNames); ## Copy descriptions and units to unstacked variables VD = {}; VU = {}; for i = 1:nvars vd = this.VariableDescriptions{ixVars(i)}; VD = [VD, repmat({vd}, 1, ncols)]; vu = this.VariableUnits{ixVars(i)}; VU = [VU, repmat({vu}, 1, ncols)]; endfor UvarTable.VariableDescriptions = VD; UvarTable.VariableUnits = VU; ## Replicate each unstacked variable's variable-scoped custom properties ## onto its new columns (MATLAB copies them); table-scoped properties are ## carried by the constant and grouping variables through the final ## horzcat that assembles the output. if (! isempty (this.CustomProperties)) cpNames = fieldnames (this.CustomProperties); for ci = 1:numel (cpNames) if (strcmp (this.CustomPropTypes{ci}, 'variable')) blk = []; for i = 1:nvars srcval = this.CustomProperties.(cpNames{ci})(ixVars(i)); blk = [blk, repmat(srcval, 1, ncols)]; endfor UvarTable.CustomProperties.(cpNames{ci}) = blk; UvarTable.CustomPropTypes{end+1} = 'variable'; endif endfor endif ## Process each separate variable to be unstacked vi = 1; for v = 1:nvars ## Get values of selected variable vvals = VarsTable.VariableValues{v}; ## Add type-specific NaN values and handle multicolumn variables. ## Resolve the aggregation per variable into THISAGGR so that the ## original AGGRFCN (or its default placeholder) is not overwritten ## between variables of different types. [mcvec, thisAggr] = get_default_aggrFcn (vvals, nrows, aggrFcn); if (ischar (thisAggr)) error (thisAggr); endif ## Process each unstacked variable for i = 1:ncols UvarTable.VariableValues{vi} = mcvec; ix = strcmp (IvarNames{i}, IvarValues); if (nrows == 1) aggrVal = thisAggr (vvals(ix, :)); UvarTable.VariableValues{vi} = aggrVal; CixRows = 1; else CixRows = []; for j = 1:nrows tmpIvarNames = IvarValues(J == j); ix = strcmp (IvarNames{i}, tmpIvarNames); if (any (ix)) aggrVec = ismember (tmpIvarNames, IvarNames{i}); aggrVal = thisAggr (vvals(J == j, :)(aggrVec,:)); UvarTable.VariableValues{vi}(j,:) = aggrVal; endif if (v == 1) CixRows = [CixRows, find(J == j, 1)]; endif endfor endif vi++; endfor endfor ## Keep corresponding rows from ConstantVariables if (! isempty (CvarTable)) CvarTable = subsetrows (CvarTable, CixRows); endif endif ## Merge output table and return index tbl = [GvarTable, CvarTable, UvarTable]; idxA = rowIdx; endfunction ## -*- texinfo -*- ## @deftypefn {table} {@var{tblB} =} inner2outer (@var{tblA}) ## ## Invert the nested hierarchy of nested tables in a table. ## ## @code{@var{tblB} = inner2outer (@var{tblA})} finds the variables in ## @var{tblA} that are themselves tables (nested tables) and returns a table ## @var{tblB} in which the inner and outer levels of nesting are transposed. ## The variables of the nested tables in @var{tblA} become the variables of ## @var{tblB}, and the variables of @var{tblA} that contain the nested ## tables become the variables of the nested tables in @var{tblB}. Any ## variable in @var{tblA} that is not a nested table is copied unaltered ## into @var{tblB}. ## ## For example, if @var{tblA} has two variables @var{A} and @var{B} that ## both contain nested tables with the variables @var{X} and @var{Y}, then ## @var{tblB} has two variables @var{X} and @var{Y}, each containing a ## nested table with the variables @var{A} and @var{B}. As a result, the ## table variables @code{tblA.A.X} and @code{tblA.B.X} are regrouped into ## @code{tblB.X.A} and @code{tblB.X.B}, while @code{tblA.A.Y} and ## @code{tblA.B.Y} are regrouped into @code{tblB.Y.A} and @code{tblB.Y.B}. ## ## The new variables of @var{tblB} are the union of the variable names of ## the nested tables in @var{tblA}, placed at the position of the first ## nested table. An inner variable name shared by more than one nested ## table becomes a nested table in @var{tblB} grouping the corresponding ## variables; an inner variable name held by a single nested table becomes ## a plain variable carrying that column. ## ## @end deftypefn function tbl = inner2outer (this) ## Identify the variables that are themselves tables (nested tables) isNested = cellfun (@istable, this.VariableValues); ixNest = find (isNested); if (isempty (ixNest)) error (strcat ("table.inner2outer: TBLA must have at least one", ... " variable that contains a table.")); endif ## The names of the nested-table variables become the variable names of ## the nested tables in the output. nestNames = this.VariableNames(ixNest); ## The union of the inner variable names (ordered by first appearance ## across the nested tables) becomes the outer variable names of the ## output. Nested tables need not share the same set of names. allNames = {}; for j = 1:numel (ixNest) allNames = [allNames, this.VariableValues{ixNest(j)}.VariableNames]; endfor innerNames = __unique__ (allNames, 'stable'); ## Build one outer variable per inner variable name. An inner name held ## by more than one nested table becomes a nested table grouping those ## source variables (named by the source nested-table variable names), ## inheriting each source variable's description and units. An inner name ## held by a single nested table becomes a plain variable carrying that ## column and its metadata. newVals = cell (1, numel (innerNames)); newTypes = cell (1, numel (innerNames)); newDesc = cell (1, numel (innerNames)); newUnits = cell (1, numel (innerNames)); for k = 1:numel (innerNames) srcJ = []; srcP = []; for j = 1:numel (ixNest) nt = this.VariableValues{ixNest(j)}; p = find (strcmp (nt.VariableNames, innerNames{k}), 1); if (! isempty (p)) srcJ(end+1) = j; srcP(end+1) = p; endif endfor if (numel (srcJ) == 1) nt = this.VariableValues{ixNest(srcJ)}; newVals{k} = nt.VariableValues{srcP}; newTypes{k} = nt.VariableTypes{srcP}; newDesc{k} = nt.VariableDescriptions{srcP}; newUnits{k} = nt.VariableUnits{srcP}; else cols = cell (1, numel (srcJ)); descs = cell (1, numel (srcJ)); units = cell (1, numel (srcJ)); for m = 1:numel (srcJ) nt = this.VariableValues{ixNest(srcJ(m))}; cols{m} = nt.VariableValues{srcP(m)}; descs{m} = nt.VariableDescriptions{srcP(m)}; units{m} = nt.VariableUnits{srcP(m)}; endfor nt2 = table (cols{:}, 'VariableNames', nestNames(srcJ)); nt2.VariableDescriptions = descs; nt2.VariableUnits = units; newVals{k} = nt2; newTypes{k} = 'table'; newDesc{k} = ''; newUnits{k} = ''; endif endfor ## Assemble the output variable order: the new outer block sits at the ## position of the first nested variable, the other nested variables drop ## out, and the non-nested variables keep their relative position. outNames = {}; outVals = {}; outTypes = {}; outDesc = {}; outUnits = {}; emitted = false; for ix = 1:width (this) if (ismember (ix, ixNest)) if (! emitted) for k = 1:numel (innerNames) outNames{end+1} = innerNames{k}; outVals{end+1} = newVals{k}; outTypes{end+1} = newTypes{k}; outDesc{end+1} = newDesc{k}; outUnits{end+1} = newUnits{k}; endfor emitted = true; endif else outNames{end+1} = this.VariableNames{ix}; outVals{end+1} = this.VariableValues{ix}; outTypes{end+1} = this.VariableTypes{ix}; outDesc{end+1} = this.VariableDescriptions{ix}; outUnits{end+1} = this.VariableUnits{ix}; endif endfor ## A new outer variable name must not clash with a kept non-nested one. if (numel (__unique__ (outNames)) != numel (outNames)) error (strcat ("table.inner2outer: an inner variable name clashes", ... " with an existing variable name in TBLA.")); endif ## Build the output: preserve table-level metadata and row names; drop ## variable-scoped custom properties since the variable identities change. tbl = this; tbl.VariableNames = outNames; tbl.VariableValues = outVals; tbl.VariableTypes = outTypes; tbl.VariableDescriptions = outDesc; tbl.VariableUnits = outUnits; if (! isempty (this.CustomProperties)) cpIdx = strcmp (this.CustomPropTypes, "variable"); if (any (cpIdx)) cpNames = fieldnames (this.CustomProperties); cpNames = cpNames(cpIdx); for i = 1:numel (cpNames) tbl.CustomProperties = rmfield (tbl.CustomProperties, cpNames{i}); endfor tbl.CustomPropTypes(cpIdx) = []; endif endif endfunction ## -*- texinfo -*- ## @deftypefn {table} {@var{T} =} addprop (@var{T}, @var{propertyNames}, @var{propertyTypes}) ## ## Add custom properties to a table. ## ## @code{@var{T} = addprop (@var{T}, @var{propertyNames}, ## @var{propertyTypes})} adds properties that contain custom metadata to the ## table @var{T}. The input argument @var{propertyNames} specifies the ## names of the custom properties to be added and @var{propertyTypes} the ## type of each corresponding custom property, that is whether the metadata ## values contained in the property apply to table @var{T} as a whole, or ## to the variables of @var{T}. Both @var{propertyNames} and ## @var{propertyTypes} can be character vectors, cell arrays of character ## vectors, or strings. When defined as cell arrays of character vectors or ## strings, they must have the same number of elements. ## ## Valid @var{propertyTypes} are either @qcode{'table'} or ## @qcode{'variable'}. When defined as @qcode{'table'}, the custom property ## can contain a scalar value of arbitrary type, which applies as metadata ## to the table as a whole. When defined as @qcode{'variable'}, the custom ## property contains a vector with one element per variable in the table. ## ## After adding custom properties using @code{addprop}, metadata values can ## be assigned to the properties using dot syntax. ## ## @end deftypefn function tbl = addprop (this, Names, Types) ## Check input arguments if (nargin < 3) error ("table.addprop: too few input arguments."); elseif (! (any (isa (Names, {'string', 'char'})) || iscellstr (Names))) error ("table.addprop: invalid input type for 'propertyNames'."); elseif (! (any (isa (Types, {'string', 'char'})) || iscellstr (Types))) error ("table.addprop: invalid input type for 'propertyTypes'."); endif ## Force to cellstr Names = cellstr (Names); Types = cellstr (Types); if (numel (Names) != numel (Types)) error (strcat ("table.addprop: the number of 'propertyTypes'", ... " must equal the number of 'propertyNames'.")); endif ## Check for duplicate property names within the input if (numel (unique (Names)) != numel (Names)) error (strcat ("table.addprop: 'propertyNames' cannot contain", ... " duplicate names.")); endif ## Check for property names that already exist if (! isempty (this.CustomProperties)) existingNames = fieldnames (this.CustomProperties); idx = ismember (Names, existingNames); if (any (idx)) error ("table.addprop: custom property '%s' already exists.", ... Names{find (idx)(1)}); endif offset = numel (this.CustomPropTypes); else offset = 0; endif ## Add each custom property for idx = 1:numel (Names) ## Check for valid custom property name if (! isvarname (Names{idx})) error (strcat ("table.addprop: custom property '%s' does not", ... " have a valid name."), ... Names{idx}); endif ## Check for valid custom property type if (! any (strcmp (Types{idx}, {'table', 'variable'}))) error ("table.addprop: invalid value for 'propertyTypes'."); endif this.CustomProperties.(Names{idx}) = []; this.CustomPropTypes(idx + offset) = Types{idx}; endfor tbl = this; endfunction ## -*- texinfo -*- ## @deftypefn {table} {@var{T} =} rmprop (@var{T}, @var{propertyNames}) ## ## Remove custom properties from a table. ## ## @code{@var{T} = rmprop (@var{T}, @var{propertyNames})} removes properties ## that contain custom metadata from the table @var{T}. The input argument ## @var{propertyNames} specifies the names of the custom properties to be ## removed and it can either be a character vector, a cell array of ## character vectors, or a string array. Names that do not match any ## existing custom property are silently ignored. ## ## @end deftypefn function tbl = rmprop (this, Names) ## Check input arguments if (nargin < 2) error ("table.rmprop: too few input arguments."); elseif (! (any (isa (Names, {'string', 'char'})) || iscellstr (Names))) error ("table.rmprop: invalid input type for 'propertyNames'."); endif ## Force to cellstr Names = cellstr (Names); ## Remove the referenced custom properties that exist; names that do not ## match any existing custom property (including repeated names) are ## silently ignored, matching MATLAB. if (! isempty (this.CustomProperties)) existingNames = fieldnames (this.CustomProperties); tf = ismember (existingNames, Names); if (any (tf)) this.CustomProperties = rmfield (this.CustomProperties, ... existingNames(tf)); this.CustomPropTypes(tf) = []; endif endif tbl = this; endfunction endmethods ################################################################################ ## ** Join and Set Operations ** ## ################################################################################ ## Available Methods ## ## ## ## 'join' 'innerjoin' 'outerjoin' 'union' ## ## 'intersect' 'ismember' 'setdiff' 'setxor' ## ## ## ################################################################################ methods (Access = public) ## -*- texinfo -*- ## @deftypefn {table} {@var{tbl} =} join (@var{tblL}, @var{tblR}) ## @deftypefnx {table} {@var{tbl} =} join (@var{tblL}, @var{tblR}, @var{Name}, @var{Value}) ## @deftypefnx {table} {[@var{tbl}, @var{ixR}] =} join (@dots{}) ## ## Combine two tables by rows using key variables. ## ## @code{@var{tbl} = join (@var{tblL}, @var{tblR})} combines @var{tblL} and ## @var{tblR} by matching the values of their @emph{key variables}, which by ## default are the variables that share the same name in both tables. ## @var{tbl} contains one row for each row of @var{tblL}, in the same order; ## each is completed with the single row of @var{tblR} whose key variables ## match. The key variables of @var{tblR} must contain unique combinations ## of values, and every key combination in @var{tblL} must be present in ## @var{tblR}. ## ## By default @var{tbl} contains all the variables of @var{tblL} followed by ## the non-key variables of @var{tblR}. Whenever a non-key variable name ## appears in both tables, a suffix derived from each input's argument name ## is appended to the conflicting names (for inputs named @var{tblL} and ## @var{tblR}, the suffixes @qcode{'_tblL'} and @qcode{'_tblR'}; when an ## input has no name, @qcode{'_left'} and @qcode{'_right'} are used). The ## row names of @var{tblL}, if any, are preserved. ## ## @code{@var{tbl} = join (@var{tblL}, @var{tblR}, @var{Name}, @var{Value})} ## customizes the join with the following options: ## ## @table @asis ## @item @qcode{'Keys'} ## Variables to use as keys in both tables, given as variable names or ## indices. It cannot be combined with @qcode{'LeftKeys'} or ## @qcode{'RightKeys'}. ## ## @item @qcode{'LeftKeys'}, @qcode{'RightKeys'} ## Variables to use as keys in @var{tblL} and @var{tblR}, respectively, when ## the key variables have different names. They must be specified together ## and reference the same number of variables. ## ## @item @qcode{'LeftVariables'}, @qcode{'RightVariables'} ## Variables of @var{tblL} and @var{tblR} to include in @var{tbl}. By ## default @qcode{'LeftVariables'} is all the variables of @var{tblL} and ## @qcode{'RightVariables'} is the non-key variables of @var{tblR}. ## ## @item @qcode{'KeepOneCopy'} ## Names of non-key variables that occur in both tables for which only the ## copy from @var{tblL} is kept (no suffix is added and the @var{tblR} copy ## is dropped). ## @end table ## ## @code{[@var{tbl}, @var{ixR}] = join (@dots{})} also returns the index ## vector @var{ixR} that identifies, for each row of @var{tbl}, the matching ## row of @var{tblR}. ## ## @end deftypefn function [tbl, ixR] = join (tblL, tblR, varargin) ## Check input arguments if (nargin < 2) error ("table.join: too few input arguments."); endif if (! istable (tblL) || ! istable (tblR)) error ("table.join: both inputs must be tables."); endif ## Parse Name/Value options optNames = {'Keys', 'LeftKeys', 'RightKeys', 'LeftVariables', ... 'RightVariables', 'KeepOneCopy'}; dfValues = {[], [], [], [], [], []}; [Keys, LeftKeys, RightKeys, LeftVariables, RightVariables, KeepOneCopy, ... rem] = parsePairedArguments (optNames, dfValues, varargin(:)); if (! isempty (rem)) error ("table.join: invalid optional input argument."); endif ## Resolve key variables on each side if (! isempty (Keys)) if (! isempty (LeftKeys) || ! isempty (RightKeys)) error (strcat ("table.join: 'Keys' cannot be combined with", ... " 'LeftKeys' or 'RightKeys'.")); endif lKeyIdx = resolveVarRef (tblL, Keys); rKeyIdx = resolveVarRef (tblR, Keys); elseif (! isempty (LeftKeys) || ! isempty (RightKeys)) if (isempty (LeftKeys) || isempty (RightKeys)) error (strcat ("table.join: 'LeftKeys' and 'RightKeys' must be", ... " specified together.")); endif lKeyIdx = resolveVarRef (tblL, LeftKeys); rKeyIdx = resolveVarRef (tblR, RightKeys); if (numel (lKeyIdx) != numel (rKeyIdx)) error (strcat ("table.join: 'LeftKeys' and 'RightKeys' must", ... " reference the same number of variables.")); endif else ## Default keys are the variables common to both tables (left order) isCommon = ismember (tblL.VariableNames, tblR.VariableNames); lKeyIdx = find (isCommon); if (isempty (lKeyIdx)) error (strcat ("table.join: cannot find any common key variables", ... " between the two tables.")); endif [~, rKeyIdx] = ismember (tblL.VariableNames(lKeyIdx), ... tblR.VariableNames); endif ## Resolve output variables on each side if (isempty (LeftVariables)) lVarIdx = 1:width (tblL); else lVarIdx = resolveVarRef (tblL, LeftVariables); endif if (isempty (RightVariables)) rVarIdx = setdiff (1:width (tblR), rKeyIdx); else rVarIdx = resolveVarRef (tblR, RightVariables); endif ## Drop the right copy of any 'KeepOneCopy' variable shared with the left if (! isempty (KeepOneCopy)) keepNames = cellstr (KeepOneCopy); rNames = tblR.VariableNames(rVarIdx); lNames = tblL.VariableNames(lVarIdx); dropMask = ismember (rNames, keepNames) & ismember (rNames, lNames); rVarIdx(dropMask) = []; endif ## Build consistent numeric key proxies for both tables leftProxy = []; rightProxy = []; for k = 1:numel (lKeyIdx) lcol = tblL.VariableValues{lKeyIdx(k)}; rcol = tblR.VariableValues{rKeyIdx(k)}; [lp, rp, errmsg] = key_col_proxy (lcol, rcol); if (! isempty (errmsg)) error ("table.join: %s", errmsg); endif leftProxy = [leftProxy, lp]; rightProxy = [rightProxy, rp]; endfor ## The right key combinations must be unique if (rows (unique (rightProxy, 'rows')) != rows (rightProxy)) error (strcat ("table.join: the key variables of TBLR must contain", ... " unique combinations of values.")); endif ## Match each left row to its unique right row [tf, ixR] = ismember (leftProxy, rightProxy, 'rows'); if (! all (tf)) error (strcat ("table.join: the key variables of TBLR must contain", ... " all values of the key variables of TBLL.")); endif ## Assemble the output: all selected left rows + matched right rows Lpart = subsetvars (tblL, lVarIdx); Rpart = subsetrows (subsetvars (tblR, rVarIdx), ixR); Rpart.RowNames = {}; ## Suffix any non-key variable names shared by both sides shared = intersect (Lpart.VariableNames, Rpart.VariableNames); if (! isempty (shared)) [lsuf, rsuf] = join_suffixes (inputname (1), inputname (2)); lNames = Lpart.VariableNames; rNames = Rpart.VariableNames; for i = find (ismember (lNames, shared)) lNames{i} = [lNames{i}, lsuf]; endfor for i = find (ismember (rNames, shared)) rNames{i} = [rNames{i}, rsuf]; endfor Lpart.VariableNames = lNames; Rpart.VariableNames = rNames; endif tbl = horzcat (Lpart, Rpart); endfunction ## -*- texinfo -*- ## @deftypefn {table} {@var{tbl} =} innerjoin (@var{tblL}, @var{tblR}) ## @deftypefnx {table} {@var{tbl} =} innerjoin (@var{tblL}, @var{tblR}, @var{Name}, @var{Value}) ## @deftypefnx {table} {[@var{tbl}, @var{ixL}, @var{ixR}] =} innerjoin (@dots{}) ## ## Inner join between two tables by rows using key variables. ## ## @code{@var{tbl} = innerjoin (@var{tblL}, @var{tblR})} combines the tables ## @var{tblL} and @var{tblR} by matching the values of their @emph{key ## variables}, which by default are the variables that share the same name ## in both tables. Each row of @var{tbl} is formed by horizontally ## concatenating a row of @var{tblL} with a row of @var{tblR} whose key ## variables share the same combination of values. If @math{m} rows in ## @var{tblL} and @math{n} rows in @var{tblR} share the same key ## combination, then @var{tbl} contains all @math{m*n} pairings for that ## combination. The rows of @var{tbl} are sorted by the values of the key ## variables, and any row names are dropped. ## ## By default @var{tbl} contains all the variables of @var{tblL} followed by ## the non-key variables of @var{tblR}. Whenever a non-key variable name ## appears in both tables, a suffix derived from each input's argument name ## is appended to the conflicting names in @var{tbl} (for inputs named ## @var{tblL} and @var{tblR}, the suffixes @qcode{'_tblL'} and ## @qcode{'_tblR'}; when an input has no name, @qcode{'_left'} and ## @qcode{'_right'} are used). ## ## @code{@var{tbl} = innerjoin (@var{tblL}, @var{tblR}, @var{Name}, ## @var{Value})} customizes the join with the following options: ## ## @table @asis ## @item @qcode{'Keys'} ## Variables to use as keys in both tables, given as variable names or ## indices. It cannot be combined with @qcode{'LeftKeys'} or ## @qcode{'RightKeys'}. ## ## @item @qcode{'LeftKeys'}, @qcode{'RightKeys'} ## Variables to use as keys in @var{tblL} and @var{tblR}, respectively, when ## the key variables have different names. They must be specified together ## and reference the same number of variables. ## ## @item @qcode{'LeftVariables'}, @qcode{'RightVariables'} ## Variables of @var{tblL} and @var{tblR} to include in @var{tbl}. They may ## include or exclude key variables. By default @qcode{'LeftVariables'} is ## all the variables of @var{tblL} and @qcode{'RightVariables'} is the ## non-key variables of @var{tblR}. ## @end table ## ## @code{[@var{tbl}, @var{ixL}, @var{ixR}] = innerjoin (@dots{})} also ## returns the row-index vectors @var{ixL} and @var{ixR} such that @var{tbl} ## is the horizontal concatenation of @code{@var{tblL}(@var{ixL}, leftVars)} ## and @code{@var{tblR}(@var{ixR}, rightVars)}. ## ## @end deftypefn function [tbl, ixL, ixR] = innerjoin (tblL, tblR, varargin) ## Check input arguments if (nargin < 2) error ("table.innerjoin: too few input arguments."); endif if (! istable (tblL) || ! istable (tblR)) error ("table.innerjoin: both inputs must be tables."); endif ## Parse Name/Value options optNames = {'Keys', 'LeftKeys', 'RightKeys', 'LeftVariables', ... 'RightVariables'}; dfValues = {[], [], [], [], []}; [Keys, LeftKeys, RightKeys, LeftVariables, RightVariables, rem] = ... parsePairedArguments (optNames, dfValues, varargin(:)); if (! isempty (rem)) error ("table.innerjoin: invalid optional input argument."); endif ## Resolve key variables on each side if (! isempty (Keys)) if (! isempty (LeftKeys) || ! isempty (RightKeys)) error (strcat ("table.innerjoin: 'Keys' cannot be combined with", ... " 'LeftKeys' or 'RightKeys'.")); endif lKeyIdx = resolveVarRef (tblL, Keys); rKeyIdx = resolveVarRef (tblR, Keys); elseif (! isempty (LeftKeys) || ! isempty (RightKeys)) if (isempty (LeftKeys) || isempty (RightKeys)) error (strcat ("table.innerjoin: 'LeftKeys' and 'RightKeys' must", ... " be specified together.")); endif lKeyIdx = resolveVarRef (tblL, LeftKeys); rKeyIdx = resolveVarRef (tblR, RightKeys); if (numel (lKeyIdx) != numel (rKeyIdx)) error (strcat ("table.innerjoin: 'LeftKeys' and 'RightKeys' must", ... " reference the same number of variables.")); endif else ## Default keys are the variables common to both tables (left order) isCommon = ismember (tblL.VariableNames, tblR.VariableNames); lKeyIdx = find (isCommon); if (isempty (lKeyIdx)) error (strcat ("table.innerjoin: cannot find any common key", ... " variables between the two tables.")); endif [~, rKeyIdx] = ismember (tblL.VariableNames(lKeyIdx), ... tblR.VariableNames); endif ## Resolve output variables on each side if (isempty (LeftVariables)) lVarIdx = 1:width (tblL); else lVarIdx = resolveVarRef (tblL, LeftVariables); endif if (isempty (RightVariables)) rVarIdx = setdiff (1:width (tblR), rKeyIdx); else rVarIdx = resolveVarRef (tblR, RightVariables); endif ## Build consistent numeric key proxies for both tables leftProxy = []; rightProxy = []; for k = 1:numel (lKeyIdx) lcol = tblL.VariableValues{lKeyIdx(k)}; rcol = tblR.VariableValues{rKeyIdx(k)}; [lp, rp, errmsg] = key_col_proxy (lcol, rcol); if (! isempty (errmsg)) error ("table.innerjoin: %s", errmsg); endif leftProxy = [leftProxy, lp]; rightProxy = [rightProxy, rp]; endfor ## Match key rows and lay out the Cartesian product, key-sorted Nl = height (tblL); [uKeys, ~, ic] = unique ([leftProxy; rightProxy], 'rows'); icL = ic(1:Nl); icR = ic(Nl+1:end); ixL = []; ixR = []; for g = 1:rows (uKeys) lr = find (icL == g); rr = find (icR == g); if (! isempty (lr) && ! isempty (rr)) ixL = [ixL; repelem(lr(:), numel (rr))]; ixR = [ixR; repmat(rr(:), numel (lr), 1)]; endif endfor ## Assemble the output table Lpart = subsetrows (subsetvars (tblL, lVarIdx), ixL); Rpart = subsetrows (subsetvars (tblR, rVarIdx), ixR); Lpart.RowNames = {}; Rpart.RowNames = {}; ## Suffix any non-key variable names shared by both sides shared = intersect (Lpart.VariableNames, Rpart.VariableNames); if (! isempty (shared)) [lsuf, rsuf] = join_suffixes (inputname (1), inputname (2)); lNames = Lpart.VariableNames; rNames = Rpart.VariableNames; for i = find (ismember (lNames, shared)) lNames{i} = [lNames{i}, lsuf]; endfor for i = find (ismember (rNames, shared)) rNames{i} = [rNames{i}, rsuf]; endfor Lpart.VariableNames = lNames; Rpart.VariableNames = rNames; endif tbl = horzcat (Lpart, Rpart); endfunction ## -*- texinfo -*- ## @deftypefn {table} {@var{tbl} =} outerjoin (@var{tblL}, @var{tblR}) ## @deftypefnx {table} {@var{tbl} =} outerjoin (@var{tblL}, @var{tblR}, @var{Name}, @var{Value}) ## @deftypefnx {table} {[@var{tbl}, @var{ixL}, @var{ixR}] =} outerjoin (@dots{}) ## ## Outer join between two tables by rows using key variables. ## ## @code{@var{tbl} = outerjoin (@var{tblL}, @var{tblR})} combines the tables ## @var{tblL} and @var{tblR} by matching the values of their @emph{key ## variables}, which by default are the variables that share the same name ## in both tables. Unlike @code{innerjoin}, an outer join also keeps the ## rows of each table that have no match in the other table, filling the ## variables taken from the non-matching table with missing values ## (@qcode{NaN}, @qcode{NaT}, @qcode{}, empty string, etc., as ## appropriate). If @math{m} rows in @var{tblL} and @math{n} rows in ## @var{tblR} share the same key combination, then @var{tbl} contains all ## @math{m*n} pairings for that combination. The rows of @var{tbl} are ## sorted by the values of the key variables and any row names are dropped. ## ## By default @var{tbl} contains all the variables of @var{tblL} followed ## by all the variables of @var{tblR}. Because the key variables are kept ## from both tables, conflicting names receive a suffix derived from each ## input's argument name (for inputs named @var{tblL} and @var{tblR}, the ## suffixes @qcode{'_tblL'} and @qcode{'_tblR'}; when an input has no name, ## @qcode{'_left'} and @qcode{'_right'} are used). See @qcode{'MergeKeys'} ## to combine the keys into single columns instead. ## ## @code{@var{tbl} = outerjoin (@var{tblL}, @var{tblR}, @var{Name}, ## @var{Value})} customizes the join with the following options: ## ## @table @asis ## @item @qcode{'Type'} ## The type of outer join: @qcode{'full'} (default) keeps unmatched rows ## from both tables, @qcode{'left'} keeps all rows of @var{tblL} and only ## matching rows of @var{tblR}, and @qcode{'right'} keeps all rows of ## @var{tblR} and only matching rows of @var{tblL}. ## ## @item @qcode{'MergeKeys'} ## A logical scalar (default @qcode{false}). When @qcode{true}, each pair ## of key variables is merged into a single variable that takes the value ## from @var{tblL} where a matching left row exists and from @var{tblR} ## otherwise. The merged variable is named after the left key when both ## keys share the same name, or @qcode{'leftName_rightName'} when their ## names differ. ## ## @item @qcode{'Keys'} ## Variables to use as keys in both tables, given as variable names or ## indices. It cannot be combined with @qcode{'LeftKeys'} or ## @qcode{'RightKeys'}. ## ## @item @qcode{'LeftKeys'}, @qcode{'RightKeys'} ## Variables to use as keys in @var{tblL} and @var{tblR}, respectively, ## when the key variables have different names. They must be specified ## together and reference the same number of variables. ## ## @item @qcode{'LeftVariables'}, @qcode{'RightVariables'} ## Variables of @var{tblL} and @var{tblR} to include in @var{tbl}. By ## default all the variables of each table are included. ## @end table ## ## @code{[@var{tbl}, @var{ixL}, @var{ixR}] = outerjoin (@dots{})} also ## returns the row-index vectors @var{ixL} and @var{ixR} that identify the ## row of @var{tblL} and @var{tblR}, respectively, corresponding to each ## row of @var{tbl}. A zero indicates a row of @var{tbl} that has no ## corresponding row in that table. ## ## @end deftypefn function [tbl, ixL, ixR] = outerjoin (tblL, tblR, varargin) ## Check input arguments if (nargin < 2) error ("table.outerjoin: too few input arguments."); endif if (! istable (tblL) || ! istable (tblR)) error ("table.outerjoin: both inputs must be tables."); endif ## Parse Name/Value options optNames = {'Keys', 'LeftKeys', 'RightKeys', 'LeftVariables', ... 'RightVariables', 'Type', 'MergeKeys'}; dfValues = {[], [], [], [], [], 'full', false}; [Keys, LeftKeys, RightKeys, LeftVariables, RightVariables, Type, ... MergeKeys, rem] = parsePairedArguments (optNames, dfValues, varargin(:)); if (! isempty (rem)) error ("table.outerjoin: invalid optional input argument."); endif ## Validate 'Type' and 'MergeKeys' if (! (ischar (Type) && isrow (Type)) || ! any (strcmpi (Type, {'full', 'left', 'right'}))) error (strcat ("table.outerjoin: 'Type' must be 'full', 'left', or", ... " 'right'.")); endif Type = lower (Type); if (! (islogical (MergeKeys) && isscalar (MergeKeys))) error ("table.outerjoin: 'MergeKeys' must be a logical scalar."); endif ## Resolve key variables on each side if (! isempty (Keys)) if (! isempty (LeftKeys) || ! isempty (RightKeys)) error (strcat ("table.outerjoin: 'Keys' cannot be combined with", ... " 'LeftKeys' or 'RightKeys'.")); endif lKeyIdx = resolveVarRef (tblL, Keys); rKeyIdx = resolveVarRef (tblR, Keys); elseif (! isempty (LeftKeys) || ! isempty (RightKeys)) if (isempty (LeftKeys) || isempty (RightKeys)) error (strcat ("table.outerjoin: 'LeftKeys' and 'RightKeys' must", ... " be specified together.")); endif lKeyIdx = resolveVarRef (tblL, LeftKeys); rKeyIdx = resolveVarRef (tblR, RightKeys); if (numel (lKeyIdx) != numel (rKeyIdx)) error (strcat ("table.outerjoin: 'LeftKeys' and 'RightKeys' must", ... " reference the same number of variables.")); endif else ## Default keys are the variables common to both tables (left order) isCommon = ismember (tblL.VariableNames, tblR.VariableNames); lKeyIdx = find (isCommon); if (isempty (lKeyIdx)) error (strcat ("table.outerjoin: cannot find any common key", ... " variables between the two tables.")); endif [~, rKeyIdx] = ismember (tblL.VariableNames(lKeyIdx), ... tblR.VariableNames); endif ## Resolve output variables (defaults: all variables of each table) if (isempty (LeftVariables)) lVarIdx = 1:width (tblL); else lVarIdx = resolveVarRef (tblL, LeftVariables); endif if (isempty (RightVariables)) rVarIdx = 1:width (tblR); else rVarIdx = resolveVarRef (tblR, RightVariables); endif ## Build consistent numeric key proxies for both tables leftProxy = []; rightProxy = []; for k = 1:numel (lKeyIdx) lcol = tblL.VariableValues{lKeyIdx(k)}; rcol = tblR.VariableValues{rKeyIdx(k)}; [lp, rp, errmsg] = key_col_proxy (lcol, rcol); if (! isempty (errmsg)) error ("table.outerjoin: %s", errmsg); endif leftProxy = [leftProxy, lp]; rightProxy = [rightProxy, rp]; endfor ## Match key rows, producing zero-filled index vectors per join type Nl = height (tblL); [uKeys, ~, ic] = unique ([leftProxy; rightProxy], 'rows'); icL = ic(1:Nl); icR = ic(Nl+1:end); keepL = any (strcmp (Type, {'full', 'left'})); keepR = any (strcmp (Type, {'full', 'right'})); ixL = []; ixR = []; for g = 1:rows (uKeys) lr = find (icL == g); rr = find (icR == g); nl = numel (lr); nr = numel (rr); if (nl > 0 && nr > 0) ixL = [ixL; repelem(lr(:), nr)]; ixR = [ixR; repmat(rr(:), nl, 1)]; elseif (nl > 0 && keepL) ixL = [ixL; lr(:)]; ixR = [ixR; zeros(nl, 1)]; elseif (nr > 0 && keepR) ixL = [ixL; zeros(nr, 1)]; ixR = [ixR; rr(:)]; endif endfor ## Assemble each side, filling unmatched rows with missing values [Lout, emsg] = joinBuildSide (subsetvars (tblL, lVarIdx), ixL); if (! isempty (emsg)) error ("table.outerjoin: %s", emsg); endif [Rout, emsg] = joinBuildSide (subsetvars (tblR, rVarIdx), ixR); if (! isempty (emsg)) error ("table.outerjoin: %s", emsg); endif ## Optionally merge each key pair into a single variable. A merged key ## keeps the left position; its name is the left key name when both keys ## share it, or 'leftName_rightName' when they differ. if (MergeKeys) [tfL, posL] = ismember (lKeyIdx, lVarIdx); [tfR, posR] = ismember (rKeyIdx, rVarIdx); dropR = []; fillRows = (ixL == 0); lNames = Lout.VariableNames; for k = 1:numel (lKeyIdx) if (tfL(k) && tfR(k)) mcol = Lout.VariableValues{posL(k)}; rcol = Rout.VariableValues{posR(k)}; mcol(fillRows,:) = rcol(fillRows,:); Lout.VariableValues{posL(k)} = mcol; lkn = tblL.VariableNames{lKeyIdx(k)}; rkn = tblR.VariableNames{rKeyIdx(k)}; if (! strcmp (lkn, rkn)) lNames{posL(k)} = [lkn, '_', rkn]; endif dropR = [dropR, posR(k)]; endif endfor Lout.VariableNames = lNames; if (! isempty (dropR)) Rout = subsetvars (Rout, setdiff (1:width (Rout), dropR)); endif endif ## Suffix any variable names shared by both sides shared = intersect (Lout.VariableNames, Rout.VariableNames); if (! isempty (shared)) [lsuf, rsuf] = join_suffixes (inputname (1), inputname (2)); lNames = Lout.VariableNames; rNames = Rout.VariableNames; for i = find (ismember (lNames, shared)) lNames{i} = [lNames{i}, lsuf]; endfor for i = find (ismember (rNames, shared)) rNames{i} = [rNames{i}, rsuf]; endfor Lout.VariableNames = lNames; Rout.VariableNames = rNames; endif tbl = horzcat (Lout, Rout); endfunction ## -*- texinfo -*- ## @deftypefn {table} {@var{tbl} =} union (@var{tblA}, @var{tblB}) ## @deftypefnx {table} {@var{tbl} =} union (@var{tblA}, @var{tblB}, @var{setOrder}) ## @deftypefnx {table} {[@var{tbl}, @var{ixA}, @var{ixB}] =} union (@dots{}) ## ## Union of two tables by rows. ## ## @code{@var{tbl} = union (@var{tblA}, @var{tblB})} returns the combined ## set of rows of @var{tblA} and @var{tblB}, with duplicate rows removed. ## Both tables must have the same variable names, although not necessarily ## in the same order; @var{tbl} keeps the variable order of @var{tblA}. ## Rows are compared by their variable values only (row names are ignored), ## and by default @var{tbl} is sorted by those values. ## ## @code{@var{tbl} = union (@var{tblA}, @var{tblB}, @var{setOrder})} ## controls the ordering of @var{tbl}. @var{setOrder} is either ## @qcode{'sorted'} (default) for ascending order, or @qcode{'stable'} to ## keep the order in which the rows appear in @var{tblA} and @var{tblB}. ## ## @code{[@var{tbl}, @var{ixA}, @var{ixB}] = union (@dots{})} also returns ## the index vectors @var{ixA} and @var{ixB} such that @var{tbl} is the ## vertical concatenation of @code{@var{tblA}(@var{ixA},:)} and ## @code{@var{tblB}(@var{ixB},:)}. ## ## @end deftypefn function [tbl, ixA, ixB] = union (tblA, tblB, varargin) if (nargin < 2) error ("table.union: too few input arguments."); endif if (! istable (tblA) || ! istable (tblB)) error ("table.union: both inputs must be tables."); endif [order, emsg] = parse_set_order (varargin); if (! isempty (emsg)) error ("table.union: %s", emsg); endif [proxyA, proxyB, emsg] = rowProxies (tblA, tblB); if (! isempty (emsg)) error ("table.union: %s", emsg); endif [keyU, ixA, ixB] = union (proxyA, proxyB, 'rows', order); ## ixA, ixB list A's then B's contributions, but the result row order ## interleaves them per SETORDER, so reorder the assembled rows to the ## result's own order. Row names are dropped: rows are drawn from both ## tables and cannot be attributed to a single input (like MATLAB). sA = subsetrows (tblA, ixA); sB = subsetrows (tblB, ixB); sA.RowNames = {}; sB.RowNames = {}; sel = vertcat (sA, sB); [~, perm] = ismember (keyU, [proxyA(ixA,:); proxyB(ixB,:)], 'rows'); tbl = subsetrows (sel, perm); endfunction ## -*- texinfo -*- ## @deftypefn {table} {@var{tbl} =} intersect (@var{tblA}, @var{tblB}) ## @deftypefnx {table} {@var{tbl} =} intersect (@var{tblA}, @var{tblB}, @var{setOrder}) ## @deftypefnx {table} {[@var{tbl}, @var{ixA}, @var{ixB}] =} intersect (@dots{}) ## ## Intersection of two tables by rows. ## ## @code{@var{tbl} = intersect (@var{tblA}, @var{tblB})} returns the set of ## rows common to both @var{tblA} and @var{tblB}, with duplicate rows ## removed. Both tables must have the same variable names, although not ## necessarily in the same order; @var{tbl} keeps the variable order of ## @var{tblA}. Rows are compared by their variable values only (row names ## are ignored), and by default @var{tbl} is sorted by those values. ## ## @code{@var{tbl} = intersect (@var{tblA}, @var{tblB}, @var{setOrder})} ## controls the ordering of @var{tbl}, either @qcode{'sorted'} (default) or ## @qcode{'stable'}. ## ## @code{[@var{tbl}, @var{ixA}, @var{ixB}] = intersect (@dots{})} also ## returns index vectors @var{ixA} and @var{ixB} such that @var{tbl} equals ## @code{@var{tblA}(@var{ixA},:)} and @code{@var{tblB}(@var{ixB},:)}. ## ## @end deftypefn function [tbl, ixA, ixB] = intersect (tblA, tblB, varargin) if (nargin < 2) error ("table.intersect: too few input arguments."); endif if (! istable (tblA) || ! istable (tblB)) error ("table.intersect: both inputs must be tables."); endif [order, emsg] = parse_set_order (varargin); if (! isempty (emsg)) error ("table.intersect: %s", emsg); endif [proxyA, proxyB, emsg] = rowProxies (tblA, tblB); if (! isempty (emsg)) error ("table.intersect: %s", emsg); endif [~, ixA, ixB] = intersect (proxyA, proxyB, 'rows', order); tbl = subsetrows (tblA, ixA); endfunction ## -*- texinfo -*- ## @deftypefn {table} {@var{TF} =} ismember (@var{tblA}, @var{tblB}) ## @deftypefnx {table} {[@var{TF}, @var{ixB}] =} ismember (@var{tblA}, @var{tblB}) ## ## Find set members between two tables by rows. ## ## @code{@var{TF} = ismember (@var{tblA}, @var{tblB})} returns a logical ## column vector @var{TF} with one element per row of @var{tblA}, where ## @code{@var{TF}(i)} is @qcode{true} when the @math{i}-th row of @var{tblA} ## also appears as a row of @var{tblB}. Both tables must have the same ## variable names, although not necessarily in the same order, and rows are ## compared by their variable values only (row names are ignored). ## ## @code{[@var{TF}, @var{ixB}] = ismember (@var{tblA}, @var{tblB})} also ## returns a column vector @var{ixB} containing, for each row of @var{tblA}, ## the index of the lowest matching row in @var{tblB}, or @qcode{0} if there ## is no match. ## ## @end deftypefn function [TF, ixB] = ismember (tblA, tblB) if (nargin < 2) error ("table.ismember: too few input arguments."); endif if (! istable (tblA) || ! istable (tblB)) error ("table.ismember: both inputs must be tables."); endif [proxyA, proxyB, emsg] = rowProxies (tblA, tblB); if (! isempty (emsg)) error ("table.ismember: %s", emsg); endif [TF, ixB] = ismember (proxyA, proxyB, 'rows'); endfunction ## -*- texinfo -*- ## @deftypefn {table} {@var{tbl} =} setdiff (@var{tblA}, @var{tblB}) ## @deftypefnx {table} {@var{tbl} =} setdiff (@var{tblA}, @var{tblB}, @var{setOrder}) ## @deftypefnx {table} {[@var{tbl}, @var{ixA}] =} setdiff (@dots{}) ## ## Difference between two tables by rows. ## ## @code{@var{tbl} = setdiff (@var{tblA}, @var{tblB})} returns the set of ## rows that are present in @var{tblA} but not in @var{tblB}, with duplicate ## rows removed. Both tables must have the same variable names, although ## not necessarily in the same order; @var{tbl} keeps the variable order of ## @var{tblA}. Rows are compared by their variable values only (row names ## are ignored), and by default @var{tbl} is sorted by those values. ## ## @code{@var{tbl} = setdiff (@var{tblA}, @var{tblB}, @var{setOrder})} ## controls the ordering of @var{tbl}, either @qcode{'sorted'} (default) or ## @qcode{'stable'}. ## ## @code{[@var{tbl}, @var{ixA}] = setdiff (@dots{})} also returns the index ## vector @var{ixA} such that @var{tbl} equals ## @code{@var{tblA}(@var{ixA},:)}. ## ## @end deftypefn function [tbl, ixA] = setdiff (tblA, tblB, varargin) if (nargin < 2) error ("table.setdiff: too few input arguments."); endif if (! istable (tblA) || ! istable (tblB)) error ("table.setdiff: both inputs must be tables."); endif [order, emsg] = parse_set_order (varargin); if (! isempty (emsg)) error ("table.setdiff: %s", emsg); endif [proxyA, proxyB, emsg] = rowProxies (tblA, tblB); if (! isempty (emsg)) error ("table.setdiff: %s", emsg); endif [~, ixA] = setdiff (proxyA, proxyB, 'rows', order); tbl = subsetrows (tblA, ixA); endfunction ## -*- texinfo -*- ## @deftypefn {table} {@var{tbl} =} setxor (@var{tblA}, @var{tblB}) ## @deftypefnx {table} {@var{tbl} =} setxor (@var{tblA}, @var{tblB}, @var{setOrder}) ## @deftypefnx {table} {[@var{tbl}, @var{ixA}, @var{ixB}] =} setxor (@dots{}) ## ## Exclusive OR of two tables by rows. ## ## @code{@var{tbl} = setxor (@var{tblA}, @var{tblB})} returns the set of ## rows that are present in either @var{tblA} or @var{tblB} but not in both, ## with duplicate rows removed. Both tables must have the same variable ## names, although not necessarily in the same order; @var{tbl} keeps the ## variable order of @var{tblA}. Rows are compared by their variable values ## only (row names are ignored), and by default @var{tbl} is sorted by those ## values. ## ## @code{@var{tbl} = setxor (@var{tblA}, @var{tblB}, @var{setOrder})} ## controls the ordering of @var{tbl}, either @qcode{'sorted'} (default) or ## @qcode{'stable'}. ## ## @code{[@var{tbl}, @var{ixA}, @var{ixB}] = setxor (@dots{})} also returns ## index vectors @var{ixA} and @var{ixB} such that @var{tbl} is the ## vertical concatenation of @code{@var{tblA}(@var{ixA},:)} and ## @code{@var{tblB}(@var{ixB},:)}. ## ## @end deftypefn function [tbl, ixA, ixB] = setxor (tblA, tblB, varargin) if (nargin < 2) error ("table.setxor: too few input arguments."); endif if (! istable (tblA) || ! istable (tblB)) error ("table.setxor: both inputs must be tables."); endif [order, emsg] = parse_set_order (varargin); if (! isempty (emsg)) error ("table.setxor: %s", emsg); endif [proxyA, proxyB, emsg] = rowProxies (tblA, tblB); if (! isempty (emsg)) error ("table.setxor: %s", emsg); endif [keyX, ixA, ixB] = setxor (proxyA, proxyB, 'rows', order); ## ixA, ixB list A's then B's contributions, but the result row order ## interleaves them per SETORDER, so reorder the assembled rows to the ## result's own order. Row names are dropped: rows are drawn from both ## tables and cannot be attributed to a single input (like MATLAB). sA = subsetrows (tblA, ixA); sB = subsetrows (tblB, ixB); sA.RowNames = {}; sB.RowNames = {}; sel = vertcat (sA, sB); [~, perm] = ismember (keyX, [proxyA(ixA,:); proxyB(ixB,:)], 'rows'); tbl = subsetrows (sel, perm); endfunction endmethods ################################################################################ ## ** Missing Values ** ## ################################################################################ ## Available Methods ## ## ## ## 'anymissing' 'ismissing' 'rmmissing' 'fillmissing' ## ## 'standardizeMissing' ## ## ## ################################################################################ methods (Access = public) ## -*- texinfo -*- ## @deftypefn {table} {@var{TF} =} anymissing (@var{tblA}) ## ## Determine if any table element is missing. ## ## @code{@var{TF} = anymissing (@var{tblA})} returns @qcode{true} if at ## least one element in table @var{tblA} is missing, otherwise it returns ## @qcode{false}. @var{TF} is a logical scalar value. ## ## Missing values are defined according to the data type of each variable in ## @var{tblA}: ## ## @itemize ## @item @qcode{NaN} - double, single, duration and calendarDuration ## @item @qcode{NaT} - datetime ## @item @qcode{} - string ## @item @qcode{} - categorical ## @item @qcode{@{''@}} - cell arrays of character vectors ## @item @qcode{''} - character arrays ## @end itemize ## ## @end deftypefn function TF = anymissing (this) TF = any (any (ismissing (this))); endfunction ## -*- texinfo -*- ## @deftypefn {table} {@var{TF} =} ismissing (@var{tbl}) ## @deftypefnx {table} {@var{TF} =} ismissing (@var{tbl}, @var{indicator}) ## @deftypefnx {table} {@var{TF} =} ismissing (@dots{}, @qcode{'OutputFormat'}, @var{outFmt}) ## ## Find missing values in table. ## ## @code{@var{TF} = ismissing (@var{tbl})} returns a logical array, ## @var{TF}, with any @qcode{true} values corresponding to missing elements ## in the input table @var{tbl}. ## ## Missing values are defined according to the data type of each variable in ## @var{tbl}: ## ## @itemize ## @item @qcode{NaN} - double, single, duration and calendarDuration ## @item @qcode{NaT} - datetime ## @item @qcode{} - string ## @item @qcode{} - categorical ## @item @qcode{@{''@}} - cell arrays of character vectors ## @item @qcode{''} - character arrays ## @end itemize ## ## @code{@var{TF} = ismissing (@var{tbl}, @var{indicator})} also returns a ## logical array, @var{TF}, with any @qcode{true} values corresponding to ## elements in the input table @var{tbl}, which are equal to the values in ## @var{indicator}. When specifying an @var{indicator}, all default missing ## values are ignored. If you want to keep them, you need to define them in ## @var{indicator}. ## ## @var{indicator} can be either a vector of specific data type, in which ## case all other data types in table @var{tbl} are ignored, or a cell array ## containing mixed types of data types, in which case they match the data ## types of the variables in table @var{tbl}. Missing values specified by ## @var{indicator} also apply to nested tables. ## ## Besides the explicit data type match between @var{indicator} and ## @var{tbl}, the following additional data types matches apply. ## @itemize ## @item @qcode{double} indicators match numeric and logical variables. ## @item @qcode{logical} indicators match numeric and logical variables. ## @item @qcode{char} and @qcode{cellstr} indicators match string variables. ## @item @qcode{char} and @qcode{string} indicators match categorical ## variables. ## @end itemize ## ## The output array @var{TF} has the same size as the input table @var{tbl}. ## ## @code{@var{TF} = ismissing (@dots{}, @qcode{'OutputFormat'}, ## @var{outFmt})} specifies whether @var{TF} is returned as a logical array ## or as a table, which maintains the variable names and all other ## information of the input table @var{tbl}. Specifying @var{outFmt} as ## @qcode{'logical'} (default) returns a logical array. Specifying ## @var{outFmt} as @qcode{'tabular'} returns a table. ## ## @end deftypefn function TF = ismissing (this, varargin) ## Parse optional Name-Value paired arguments optNames = {'OutputFormat'}; dfValues = {'logical'}; [outFmt, indicator] = parsePairedArguments (optNames, dfValues, ... varargin(:)); if (! any (strcmpi (outFmt, {'logical', 'tabular'}))) error ("table.ismissing: invalid value for 'OutputFormat'."); endif ## Process each table variable with default missing values if (isempty (indicator)) for i = 1:width (this) tmpVar = this.VariableValues{i}; if (isa (tmpVar, 'table')) varTF = ismissing (tmpVar, 'OutputFormat', 'logical'); varTF = any (varTF, 2); this.VariableValues{i} = varTF; elseif (any (isa (tmpVar, {'calendarDuration', 'categorical', ... 'datetime', 'duration', 'string'}))) varTF = ismissing (tmpVar); varTF = any (varTF, 2); this.VariableValues{i} = varTF; elseif (ischar (tmpVar)) varTF = __ismissing__ (tmpVar); varTF = all (varTF, 2); this.VariableValues{i} = varTF; else # numeric, logical, and cellstr arrays varTF = __ismissing__ (tmpVar); varTF = any (varTF, 2); this.VariableValues{i} = varTF; endif endfor else ## Remove nested cell caused by parsing with paredArgs indicator = indicator{1}; ## Indicator must be a vector in any case if (! isvector (indicator)) error ("table.ismissing: INDICATOR must be a vector."); endif ## NaN values for calendarDuration and duration nan_calendarDuration = nan_duration = false; ## Preprocess indicator if it is a cell array if (iscell (indicator) && ! iscellstr (indicator)) ## Elements in indicator vector must be scalars (except char vectors) fcn = @(x) isscalar (x) | isempty (x) | (ischar (x) & isvector (x)); all_scalar = all (cellfun (fcn, indicator)); if (! all_scalar) error (strcat ("table.ismissing: INDICATOR must explicitly", ... " contain scalar elements or character", ... " vectors.")); endif ## categorical arrays idx_categorical = false; categorical_indicator = []; fcn = @(x) isa (x, 'categorical'); ids_categorical = cellfun (fcn, indicator); if (any (ids_categorical)) new_categories = [indicator{ids_categorical}]; categorical_indicator = [categorical_indicator, new_categories]; idx_categorical = true; endif fcn = @(x) isa (x, 'string'); ids_categorical = cellfun (fcn, indicator); if (any (ids_categorical)) new_categories = categorical ([indicator{ids_categorical}]); categorical_indicator = [categorical_indicator, new_categories]; idx_categorical = true; endif ids_categorical = cellfun ('ischar', indicator); if (any (ids_categorical)) new_categories = ... categorical (string ([indicator{ids_categorical}])); categorical_indicator = [categorical_indicator, new_categories]; idx_categorical = true; endif ## datetime arrays fcn = @(x) isa (x, 'datetime'); idx_datetime = cellfun (fcn, indicator); if (any (idx_datetime)) datetime_indicator = [indicator{idx_datetime}]; idx_datetime = true; endif ## duration arrays fcn = @(x) isa (x, 'duration'); idx_duration = cellfun (fcn, indicator); if (any (idx_duration)) duration_indicator = [indicator{idx_duration}]; idx_duration = true; endif ## string arrays fcn = @(x) isa (x, 'string') || ischar (x) || iscellstr (x); idx_string = cellfun (fcn, indicator); if (any (idx_string)) string_indicator = string (indicator(idx_string)); idx_string = true; endif ## cell arrays of character vectors fcn = @(x) iscellstr (x); idx_iscstr = cellfun (fcn, indicator); if (any (idx_iscstr)) iscstr_indicator = indicator{idx_iscstr}; idx_iscstr = true; endif ## char arrays idx_ischar = cellfun ('ischar', indicator); if (any (idx_ischar)) ischar_indicator = [indicator{idx_ischar}]; idx_ischar = true; endif ## numeric and logical arrays fcn = @(x) isnumeric (x) || islogical (x); idx_numlog = cellfun (fcn, indicator); if (any (idx_numlog)) numlog_indicator = [indicator{idx_numlog}]; idx_numlog = true; ## Check for NaN and apply to duration and calendarDuration arrays if (any (isnan (numlog_indicator))) nan_calendarDuration = nan_duration = true; endif endif elseif (iscellstr (indicator)) ## cell arrays of character vectors and string arrays are searched idx_iscstr = true; iscstr_indicator = indicator; idx_string = true; string_indicator = indicator; ## all other arrays are ignored idx_categorical = false; idx_datetime = false; idx_duration = false; idx_ischar = false; idx_numlog = false; else # single data type indicator idx_categorical = false; idx_datetime = false; idx_duration = false; idx_string = false; idx_iscstr = false; idx_ischar = false; idx_numlog = false; if (isa (indicator, 'categorical')) idx_categorical = true; categorical_indicator = indicator; elseif (isa (indicator, 'datetime')) idx_datetime = true; datetime_indicator = indicator; elseif (isa (indicator, 'duration')) idx_duration = true; duration_indicator = indicator; elseif (isa (indicator, 'string')) idx_string = true; string_indicator = indicator; idx_categorical = true; categorical_indicator = categorical (indicator); elseif (iscellstr (indicator)) idx_iscstr = true; iscstr_indicator = indicator; idx_string = true; string_indicator = string (indicator); elseif (ischar (indicator)) idx_ischar = true; ischar_indicator = cellstr (indicator); idx_string = true; string_indicator = string (indicator); idx_categorical = true; categorical_indicator = categorical (string_indicator); else # numeric and logical arrays idx_numlog = true; numlog_indicator = indicator; ## Check for NaN and apply to duration and calendarDuration arrays if (any (isnan (numlog_indicator))) nan_calendarDuration = nan_duration = true; endif endif endif ## Return false TF vector for any datatypes that are not ## represented in the indicator and should be ignored TF_false = false (rows (this), 1); for i = 1:width (this) tmpVar = this.VariableValues{i}; if (isa (tmpVar, 'table')) varTF = ismissing (tmpVar, indicator, 'OutputFormat', 'logical'); elseif (isa (tmpVar, 'calendarDuration')) if (nan_calendarDuration) varTF = ismissing (tmpVar); else varTF = TF_false; endif elseif (isa (tmpVar, 'categorical')) if (idx_categorical) varTF = ismissing (tmpVar, categorical_indicator); else varTF = TF_false; endif elseif (isa (tmpVar, 'datetime')) if (idx_datetime) varTF = ismissing (tmpVar, datetime_indicator); else varTF = TF_false; endif elseif (isa (tmpVar, 'duration')) varTF = TF_false; if (nan_duration) varTF = varTF | ismissing (tmpVar); endif if (idx_duration) varTF = varTF | ismissing (tmpVar, duration_indicator); endif elseif (isa (tmpVar, 'string')) if (idx_string) varTF = ismissing (tmpVar, string_indicator); else varTF = TF_false; endif elseif (iscellstr (tmpVar)) if (idx_iscstr) varTF = __ismissing__ (tmpVar, iscstr_indicator); else varTF = TF_false; endif elseif (ischar (tmpVar)) if (idx_ischar) varTF = __ismissing__ (cellstr (tmpVar), ischar_indicator); else varTF = TF_false; endif else # numeric and logical arrays if (idx_numlog) varTF = __ismissing__ (tmpVar, numlog_indicator); else varTF = TF_false; endif endif varTF = any (varTF, 2); this.VariableValues{i} = varTF; endfor endif ## Return appropriate OutputFormat if (strcmpi (outFmt, 'logical')) TF = table2array (this); else TF = this; endif endfunction ## -*- texinfo -*- ## @deftypefn {table} {@var{tbl} =} rmmissing (@var{tblA}) ## @deftypefnx {table} {@var{tbl} =} rmmissing (@dots{}, @var{Name}, @var{Value}) ## @deftypefnx {table} {[@var{tbl}, @var{TF}] =} rmmissing (@dots{}) ## ## Remove missing table elements by rows. ## ## @code{@var{tbl} = rmmissing (@var{tblA})} returns a table with the rows ## of @var{tblA} that contain at least one missing value removed. Missing ## values are determined per variable according to its data type ## (@code{NaN} for numeric, @code{NaT} for @code{datetime}, @code{} ## for @code{string}, @code{} for @code{categorical}, ## @code{@{''@}} for cellstr, etc.), as reported by @code{ismissing}. ## ## @code{@var{tbl} = rmmissing (@dots{}, @var{Name}, @var{Value})} ## customizes the operation with the following options: ## ## @table @asis ## @item @qcode{'MinNumMissing'} ## A positive integer @var{n} (default @code{1}). A row is removed only ## when it has at least @var{n} variables with a missing value. ## ## @item @qcode{'DataVariables'} ## Restrict the search for missing values to the indicated subset of table ## variables, using the same variable referencing as the other @code{table} ## methods. Variables outside the subset are not inspected, but all ## variables are kept in the output. ## ## @item @qcode{'MissingLocations'} ## Supply the missing-value locations explicitly instead of deriving them ## with @code{ismissing}. The value is either a logical matrix with one row ## per row of the input and one column per inspected variable, or a ## @code{table} of logical variables whose names and sizes match the ## inspected variables. ## @end table ## ## @code{[@var{tbl}, @var{TF}] = rmmissing (@dots{})} also returns a logical ## column vector @var{TF}, with one element per row of @var{tblA}, that is ## @qcode{true} for each removed row. ## ## @end deftypefn function [tbl, TF] = rmmissing (this, varargin) ## Handle simple input argument first if (numel (varargin) == 0) TF = any (ismissing (this), 2); tbl = subsetrows (this, ! TF); return; endif ## Parse optional Name-Value paired arguments optNames = {'MinNumMissing', 'DataVariables', 'MissingLocations'}; dfValues = {1, [], []}; [minNum, dVars, mLocs] = parsePairedArguments (optNames, dfValues, ... varargin(:)); ## Check optional Name-Value paired arguments and operate accordingly if (! isscalar (minNum) || fix (minNum) != minNum || minNum <= 0) error ("table.rmmissing: 'MinNumMissing' must be a positive integer."); endif if (! isempty (dVars)) dIxVars = resolveVarRef (this, dVars, 'lenient'); if (any (dIxVars == 0)) badpos = find (dIxVars == 0)(1); dv = dVars; if (isa (dv, 'string')) dv = cellstr (dv); endif if (ischar (dv)) badname = dv; elseif (iscellstr (dv)) badname = dv{badpos}; else badname = ""; endif error (strcat ("table.rmmissing: 'DataVariables' index a", ... " non-existing variable: '%s'."), badname); endif tmpT = subsetvars (this, dIxVars); else tmpT = this; endif if (! isempty (mLocs)) if (islogical (mLocs)) if (! isequal (size (mLocs), size (tmpT))) error (strcat ("table.rmmissing: 'MissingLocations' must be", ... " a logical matrix of the same size as the", ... " input table or the part of it referenced by", ... " 'DataVariables'.")); endif TF = sum (mLocs, 2) >= minNum; tbl = subsetrows (this, ! TF); elseif (isa (mLocs, 'table')) if (! all (ismember (tmpT.VariableNames, mLocs.VariableNames))) error (strcat ("table.rmmissing: 'MissingLocations' must be", ... " a table with the same variable names as the", ... " input table or the part of it referenced by", ... " 'DataVariables'.")); endif TF = false (rows (this), 0); for jx = 1:width (tmpT) kx = find (strcmp (tmpT.VariableNames{jx}, mLocs.VariableNames), 1); varTF = mLocs.VariableValues{kx}; if (! islogical (varTF)) error (strcat ("table.rmmissing: 'MissingLocations' must", ... " be a table with logical variables.")); endif if (! isequal (size (varTF), size (tmpT.VariableValues{jx}))) error (strcat ("table.rmmissing: 'MissingLocations' must", ... " be a table with the same variable sizes", ... " as the input table or the part of it", ... " referenced by 'DataVariables'.")); endif TF = [TF, any(varTF, 2)]; endfor TF = sum (TF, 2) >= minNum; tbl = subsetrows (this, ! TF); else error ("table.rmmissing: invalid data type for 'MissingLocations'."); endif else TF = sum (ismissing (tmpT), 2) >= minNum; tbl = subsetrows (this, ! TF); endif endfunction ## -*- texinfo -*- ## @deftypefn {table} {@var{tblB} =} fillmissing (@var{tblA}, @qcode{'constant'}, @var{val}) ## @deftypefnx {table} {@var{tblB} =} fillmissing (@var{tblA}, @var{method}) ## @deftypefnx {table} {@var{tblB} =} fillmissing (@dots{}, @var{Name}, @var{Value}) ## @deftypefnx {table} {[@var{tblB}, @var{TF}] =} fillmissing (@dots{}) ## ## Fill missing entries of a table, variable by variable. ## ## @code{@var{tblB} = fillmissing (@var{tblA}, @qcode{'constant'}, ## @var{val})} replaces the missing entries of each table variable with the ## fill value @var{val}. @var{val} can be a scalar that is broadcast to ## every targeted variable, a vector with one element per targeted ## variable, or a cell array with one fill value per targeted variable. ## The fill value of each variable must be compatible with that variable's ## data type. ## ## @code{@var{tblB} = fillmissing (@var{tblA}, @var{method})} fills missing ## entries using the gap-filling method @var{method}, which can be one of: ## ## @table @asis ## @item @qcode{'previous'} ## Fill with the previous non-missing entry along each column. ## ## @item @qcode{'next'} ## Fill with the next non-missing entry along each column. ## ## @item @qcode{'nearest'} ## Fill with the nearest non-missing entry along each column. When two ## non-missing entries are equidistant, the later (next) one is used. ## ## @item @qcode{'linear'} ## Fill numeric variables by linear interpolation of neighboring ## non-missing entries. Non-numeric variables are left unchanged. ## @end table ## ## The @qcode{'previous'}, @qcode{'next'}, and @qcode{'nearest'} methods ## operate on variables of any data type. Leading or trailing missing ## entries that cannot be reached by the method are left missing. ## ## The following @var{Name}/@var{Value} pairs are supported: ## ## @table @asis ## @item @qcode{'DataVariables'} ## Restrict the operation to the indicated subset of table variables. The ## value uses the same variable referencing as the rest of the @code{table} ## methods. By default, every variable is targeted. ## ## @item @qcode{'EndValues'} ## Control how the @qcode{'linear'} method treats leading and trailing ## missing entries. Valid values are @qcode{'extrap'} (default, linear ## extrapolation), @qcode{'none'} (leave them missing), or a numeric scalar ## used as a constant for the end gaps. ## @end table ## ## @code{[@var{tblB}, @var{TF}] = fillmissing (@dots{})} also returns a ## logical array @var{TF} with @code{height (@var{tblA})} rows and one ## column per table variable. @code{@var{TF}(i,j)} is @qcode{true} when an ## entry of the j-th variable in the i-th row was missing and has been ## filled. ## ## Not yet supported: the @qcode{'spline'}, @qcode{'pchip'}, ## @qcode{'makima'}, @qcode{'movmean'}, @qcode{'movmedian'}, ## @qcode{'mean'}, @qcode{'median'}, @qcode{'mode'}, and @qcode{'knn'} ## methods, as well as the @qcode{'ReplaceValues'}, @qcode{'MaxGap'}, ## @qcode{'SamplePoints'}, and @qcode{'MissingLocations'} options. ## ## @end deftypefn function [tbl, TF] = fillmissing (tblA, varargin) ## Check input arguments if (nargin < 2) error ("table.fillmissing: too few input arguments."); endif ## Resolve the fill method (and the value for the 'constant' method) method = varargin{1}; if (isa (method, 'string') && isscalar (method)) method = char (method); endif if (! (ischar (method) && isrow (method))) error ("table.fillmissing: METHOD must be a character vector."); endif method = lower (method); rest = varargin(2:end); constVal = []; switch (method) case 'constant' if (isempty (rest)) error (strcat ("table.fillmissing: the 'constant' method", ... " requires a fill value.")); endif constVal = rest{1}; rest = rest(2:end); case {'previous', 'next', 'nearest', 'linear'} ## supported; no extra positional argument case {'movmean', 'movmedian', 'spline', 'pchip', 'makima', 'knn', ... 'mean', 'median', 'mode'} error (strcat ("table.fillmissing: method '%s' is not supported", ... " yet."), method); otherwise error ("table.fillmissing: unknown method '%s'.", method); endswitch ## Parse optional Name-Value paired arguments optNames = {'DataVariables', 'EndValues', 'ReplaceValues'}; dfValues = {[], 'extrap', true}; [dVars, endVals, replace] = parsePairedArguments (optNames, dfValues, ... rest(:)); if (! (islogical (replace) && isscalar (replace))) error ("table.fillmissing: 'ReplaceValues' must be a logical scalar."); endif if (! replace) error (strcat ("table.fillmissing: 'ReplaceValues' set to false is", ... " not supported yet.")); endif ## Resolve targeted variables if (isempty (dVars)) ixVars = 1:width (tblA); else ixVars = resolveVarRef (tblA, dVars, 'lenient'); if (any (ixVars == 0)) badpos = find (ixVars == 0)(1); dv = dVars; if (isa (dv, 'string')) dv = cellstr (dv); endif if (ischar (dv)) badname = dv; elseif (iscellstr (dv)) badname = dv{badpos}; else badname = ""; endif error (strcat ("table.fillmissing: 'DataVariables' index a", ... " non-existing variable: '%s'."), badname); endif endif ## Resolve per-variable fill values for the 'constant' method if (strcmp (method, 'constant')) fillVals = resolve_const_values (constVal, numel (ixVars)); endif ## Initialize outputs (TF has one column per table variable) tbl = tblA; TF = false (height (tblA), width (tblA)); ## Fill each targeted variable for k = 1:numel (ixVars) iv = ixVars(k); v = tbl.VariableValues{iv}; M = var_missing_mask (v); if (! any (M(:))) continue; endif filled = false (size (M)); if (strcmp (method, 'constant')) [v, filled] = fill_constant (v, M, fillVals{k}, ... tbl.VariableNames{iv}); elseif (strcmp (method, 'linear') && ! (isnumeric (v) || islogical (v))) ## 'linear' applies only to numeric variables; skip others continue; else for c = 1:columns (M) m = M(:,c); if (! any (m)) continue; endif if (strcmp (method, 'linear')) [v(:,c), filled(:,c)] = fill_linear (v(:,c), m, endVals); else si = fill_neighbor_idx (m, method); rows = m & si > 0; v(rows,c) = v(si(rows),c); filled(:,c) = rows; endif endfor endif tbl.VariableValues{iv} = v; TF(:,iv) = any (filled, 2); endfor endfunction ## -*- texinfo -*- ## @deftypefn {table} {@var{tblB} =} standardizeMissing (@var{tblA}, @var{indicator}) ## @deftypefnx {table} {@var{tblB} =} standardizeMissing (@dots{}, @var{Name}, @var{Value}) ## ## Insert standard missing values into a table. ## ## @code{@var{tblB} = standardizeMissing (@var{tblA}, @var{indicator})} ## replaces every entry of @var{tblA} that matches a value in ## @var{indicator} with the standard missing value of that variable's data ## type (@code{NaN} for @code{double}/@code{single}, @qcode{''} for cell ## arrays of character vectors, @code{} for @code{string}, and ## @code{} for @code{categorical}). ## ## @var{indicator} may be a numeric scalar or vector, a character vector, a ## @code{string} array, a cell array of character vectors, or a cell array ## mixing numeric and text indicators. Each indicator is applied only to ## the variables whose type is compatible with it: numeric indicators match ## @code{double} and @code{single} variables, while text indicators (char, ## @code{string}, or cellstr) match cell-array-of-character-vector, ## @code{string}, and @code{categorical} variables. ## ## The @qcode{'DataVariables'} @var{Name}/@var{Value} pair restricts the ## operation to a subset of variables, using the same variable referencing ## as the other @code{table} methods. Variables not selected pass through ## unchanged. ## ## Logical and integer variables (which have no standard missing value) and ## @code{duration}, @code{datetime}, and @code{calendarDuration} variables ## pass through unchanged. ## ## @end deftypefn function tbl = standardizeMissing (tblA, indicator, varargin) ## Check input arguments if (nargin < 2) error ("table.standardizeMissing: too few input arguments."); endif ## Parse optional Name-Value paired arguments optNames = {'DataVariables'}; dfValues = {[]}; dVars = parsePairedArguments (optNames, dfValues, varargin(:)); ## Resolve targeted variables if (isempty (dVars)) ixVars = 1:width (tblA); else ixVars = resolveVarRef (tblA, dVars, 'lenient'); if (any (ixVars == 0)) badpos = find (ixVars == 0)(1); dv = dVars; if (isa (dv, 'string')) dv = cellstr (dv); endif if (ischar (dv)) badname = dv; elseif (iscellstr (dv)) badname = dv{badpos}; else badname = ""; endif error (strcat ("table.standardizeMissing: 'DataVariables' index", ... " a non-existing variable: '%s'."), badname); endif endif ## Split the indicator into numeric and text indicator values [numInd, txtInd] = std_normalize_indicator (indicator); ## Standardize each targeted variable tbl = tblA; for k = 1:numel (ixVars) iv = ixVars(k); v = std_apply_indicator (tbl.VariableValues{iv}, numInd, txtInd); tbl.VariableValues{iv} = v; endfor endfunction endmethods ################################################################################ ## ** Apply Functions to Table Contents ** ## ################################################################################ ## Available Methods ## ## ## ## 'pivot' 'groupcounts' 'groupfilter' 'groupsummary' ## ## 'grouptransform' 'findgroups' 'splitapply' 'rowfun' ## ## 'varfun' ## ## ## ################################################################################ methods (Access = public) ## -*- texinfo -*- ## @deftypefn {table} {@var{G} =} findgroups (@var{T}) ## @deftypefnx {table} {[@var{G}, @var{TID}] =} findgroups (@var{T}) ## ## Find groups defined by the variables of a table. ## ## @code{@var{G} = findgroups (@var{T})} returns @var{G}, a column vector of ## positive integer group numbers, with one element for each row of the ## table @var{T}. Each variable of @var{T} acts as a grouping variable, and ## the groups are the unique combinations of values across those variables, ## sorted in ascending order. If @var{N} groups are found, every integer ## between 1 and @var{N} labels a group. Rows holding a missing value ## (@code{NaN}, @code{NaT}, @code{}, @code{''}, or ## @code{}) in any grouping variable are labelled @code{NaN} in ## @var{G}. ## ## @code{[@var{G}, @var{TID}] = findgroups (@var{T})} also returns ## @var{TID}, a table whose rows are the sorted unique combinations ## identifying each group, with the same variables as @var{T}. ## ## @end deftypefn function [G, TID] = findgroups (this) if (nargin != 1) print_usage (); endif nvar = width (this); n = height (this); if (nvar == 0) error ("table.findgroups: T must have at least one variable."); endif ## Build the combined proxy matrix and the overall missing-row mask. P = []; miss = false (n, 1); for j = 1:nvar [p, m, errmsg] = group_col_proxy (this.VariableValues{j}); if (! isempty (errmsg)) error ("table.findgroups: %s", errmsg); endif P = [P, p]; miss = miss | m; endfor ## Label the non-missing rows by sorted unique combination. G = NaN (n, 1); keep = find (! miss); if (! isempty (keep)) [~, ia, ic] = unique (P(keep,:), "rows"); G(keep) = ic; endif if (nargout > 1) if (isempty (keep)) TID = this([], :); else repRows = keep(ia); idcols = cell (1, nvar); for j = 1:nvar col = this.VariableValues{j}; idcols{j} = col(repRows,:); endfor TID = table (idcols{:}, "VariableNames", this.VariableNames); endif endif endfunction ## -*- texinfo -*- ## @deftypefn {table} {@var{Y} =} splitapply (@var{func}, @var{T}, @var{G}) ## @deftypefnx {table} {[@var{Y1}, @dots{}, @var{YM}] =} splitapply (@var{func}, @var{T}, @var{G}) ## ## Split table data into groups and apply a function to each group. ## ## @code{@var{Y} = splitapply (@var{func}, @var{T}, @var{G})} splits the ## rows of the table @var{T} into groups according to the group numbers ## @var{G} (typically produced by @code{findgroups}), applies the function ## handle @var{func} to each group, and concatenates the per-group results ## into the output @var{Y}. @var{G} must be a column vector of positive ## integers with one element per row of @var{T}; if it identifies @var{N} ## groups, every integer between 1 and @var{N} must occur at least once. ## Rows for which @var{G} is @code{NaN} are omitted. Each variable of ## @var{T} is passed to @var{func} as a separate input argument, so ## @var{func} must accept as many arguments as @var{T} has variables. ## ## @code{[@var{Y1}, @dots{}, @var{YM}] = splitapply (@dots{})} returns the ## multiple outputs of @var{func}, each concatenated across groups. ## ## @end deftypefn function varargout = splitapply (func, this, G) if (nargin != 3) print_usage (); endif if (! is_function_handle (func)) error ("table.splitapply: FUNC must be a function handle."); endif n = height (this); if (! (isnumeric (G) && isvector (G) && numel (G) == n)) error (strcat ("table.splitapply: G must be a numeric vector with", ... " one element per row of T.")); endif G = G(:); gv = G(! isnan (G)); if (any (gv != fix (gv)) || any (gv < 1)) error ("table.splitapply: G must contain positive integers."); endif if (isempty (gv)) N = 0; else N = max (gv); if (! isequal (unique (gv), (1:N)')) error (strcat ("table.splitapply: G must contain every integer", ... " between 1 and the number of groups.")); endif endif nvar = width (this); nout = max (nargout, 1); results = cell (N, nout); for g = 1:N rows = (G == g); args = cell (1, nvar); for j = 1:nvar col = this.VariableValues{j}; args{j} = col(rows,:); endfor [results{g,:}] = func (args{:}); endfor varargout = cell (1, nout); for k = 1:nout varargout{k} = vertcat (results{:,k}); endfor endfunction ## -*- texinfo -*- ## @deftypefn {table} {@var{B} =} varfun (@var{func}, @var{A}) ## @deftypefnx {table} {@var{B} =} varfun (@var{func}, @var{A}, @var{Name}, @var{Value}, @dots{}) ## ## Apply a function to each variable of a table. ## ## @code{@var{B} = varfun (@var{func}, @var{A})} applies the function ## handle @var{func} separately to each variable of the table @var{A} and ## returns the results in the table @var{B}. @var{func} is called once per ## variable with that variable as its single input argument. By default ## each output variable of @var{B} is named @qcode{@var{f}_@var{v}}, where ## @var{f} is the name of @var{func} (or @qcode{Fun} when @var{func} is ## anonymous) and @var{v} is the name of the corresponding variable of ## @var{A}. ## ## @code{@var{B} = varfun (@var{func}, @var{A}, @var{Name}, @var{Value}, ## @dots{})} modifies the operation through the following ## @var{Name}/@var{Value} pairs: ## ## @table @asis ## @item @qcode{'InputVariables'} ## The variables of @var{A} to which @var{func} is applied, given as ## variable names, indices, a logical vector, or a function handle that ## returns @code{true} for the variables to include. By default @var{func} ## is applied to every variable of @var{A} that is not a grouping variable. ## ## @item @qcode{'GroupingVariables'} ## One or more variables of @var{A} that define groups of rows. When ## grouping variables are given, @var{func} is applied to the values of each ## input variable within each group, @var{B} has one row per group, and ## @var{B} also includes the grouping variables and a @qcode{GroupCount} ## variable holding the number of rows in each group. Rows with a missing ## value in any grouping variable are omitted. ## ## @item @qcode{'OutputFormat'} ## The format of @var{B}, one of @qcode{'auto'} (the default, equivalent to ## @qcode{'table'}), @qcode{'table'}, @qcode{'uniform'}, or @qcode{'cell'}. ## For @qcode{'uniform'}, @var{func} must return a scalar on each call and ## the results are concatenated into an array. For @qcode{'cell'} the ## results are returned in a cell array. The @qcode{'uniform'} and ## @qcode{'cell'} formats return only the results of @var{func}, without the ## grouping variables or @qcode{GroupCount}. ## ## @item @qcode{'ErrorHandler'} ## A function handle that is called when @var{func} throws an error. It ## receives a structure with fields @qcode{identifier}, @qcode{message}, and ## @qcode{index}, followed by the same inputs that were passed to ## @var{func}, and its outputs are used in place of the outputs of ## @var{func}. ## @end table ## ## @end deftypefn function B = varfun (func, A, varargin) if (nargin < 2) print_usage (); endif if (! is_function_handle (func)) error ("table.varfun: FUNC must be a function handle."); endif ## Parse optional Name-Value paired arguments optNames = {'InputVariables', 'GroupingVariables', 'OutputFormat', ... 'ErrorHandler'}; dfValues = {[], [], 'auto', []}; [inVars, grpVars, outFmt, errHandler] = ... parsePairedArguments (optNames, dfValues, varargin(:)); outFmt = check_output_format ('varfun', outFmt); if (! isempty (errHandler) && ! is_function_handle (errHandler)) error ("table.varfun: 'ErrorHandler' must be a function handle."); endif ## Resolve grouping variables and input variables (default input is every ## variable that is not a grouping variable). if (isempty (grpVars)) gIx = []; else gIx = resolveVarRef (A, grpVars)(:)'; endif if (isempty (inVars)) iIx = 1:width (A); iIx(ismember (iIx, gIx)) = []; else iIx = resolveVarRef (A, inVars)(:)'; endif if (isempty (iIx)) error ("table.varfun: there are no variables to which to apply FUNC."); endif ## Build the output variable names from the function and variable names. inNames = A.VariableNames(iIx); fname = apply_func_name (func); outNames = strcat (fname, '_', inNames); if (isempty (gIx)) ## Ungrouped: apply FUNC to each whole variable. res = cell (1, numel (iIx)); for k = 1:numel (iIx) out = apply_func (func, errHandler, k, 1, {A.VariableValues{iIx(k)}}); res{1,k} = out{1}; endfor B = build_apply_result ('varfun', outFmt, res, outNames, {}, {}, []); else ## Grouped: apply FUNC to each group's slice of each variable. inCols = A.VariableValues(iIx); [G, ng, repRows, errmsg] = group_table_rows (A.VariableValues(gIx)); if (! isempty (errmsg)) error ("table.varfun: %s", errmsg); endif res = cell (ng, numel (iIx)); for g = 1:ng rows = (G == g); for k = 1:numel (iIx) col = inCols{k}; out = apply_func (func, errHandler, g, 1, {col(rows,:)}); res{g,k} = out{1}; endfor endfor [gcols, gcount] = group_output_cols (A.VariableValues(gIx), G, repRows); B = build_grouped_apply_result ('varfun', outFmt, res, outNames, ... gcols, A.VariableNames(gIx), gcount); endif endfunction ## -*- texinfo -*- ## @deftypefn {table} {@var{B} =} rowfun (@var{func}, @var{A}) ## @deftypefnx {table} {@var{B} =} rowfun (@var{func}, @var{A}, @var{Name}, @var{Value}, @dots{}) ## ## Apply a function to each row of a table. ## ## @code{@var{B} = rowfun (@var{func}, @var{A})} applies the function ## handle @var{func} to each row of the table @var{A} and returns the ## results in the table @var{B}, which has one row for each row of @var{A}. ## By default the value of each variable in the row is passed to @var{func} ## as a separate input argument, and the output variables of @var{B} are ## named @qcode{Var1}, @qcode{Var2}, and so on. ## ## @code{@var{B} = rowfun (@var{func}, @var{A}, @var{Name}, @var{Value}, ## @dots{})} modifies the operation through the following ## @var{Name}/@var{Value} pairs: ## ## @table @asis ## @item @qcode{'InputVariables'} ## The variables of @var{A} that are passed to @var{func}, given as variable ## names, indices, a logical vector, or a function handle. By default every ## variable of @var{A} that is not a grouping variable is used. ## ## @item @qcode{'GroupingVariables'} ## One or more variables of @var{A} that define groups of rows. When ## grouping variables are given, @var{func} is applied once to each group, ## receiving the values of each input variable across the rows of the group; ## @var{B} has one row per group and also includes the grouping variables ## and a @qcode{GroupCount} variable. Rows with a missing value in any ## grouping variable are omitted. ## ## @item @qcode{'OutputVariableNames'} ## The names of the output variables of @var{B}, one per output of ## @var{func}. ## ## @item @qcode{'NumOutputs'} ## The number of output arguments to request from @var{func}. It defaults ## to the number of @qcode{'OutputVariableNames'} if those are given, ## otherwise to @code{1}. ## ## @item @qcode{'SeparateInputs'} ## A logical scalar. When @code{true} (the default), the value of each ## input variable is passed to @var{func} as a separate argument. When ## @code{false}, the values of the row are horizontally concatenated and ## passed as a single argument. ## ## @item @qcode{'ExtractCellContents'} ## A logical scalar. When @code{true}, the contents of cell-valued ## variables are extracted before being passed to @var{func}. It defaults ## to @code{false}. ## ## @item @qcode{'OutputFormat'} ## The format of @var{B}, one of @qcode{'auto'} (the default, equivalent to ## @qcode{'table'}), @qcode{'table'}, @qcode{'uniform'}, or @qcode{'cell'}. ## For @qcode{'uniform'}, every call to @var{func} must return scalars of ## the same type, which are concatenated into an array. For @qcode{'cell'} ## the results are returned in a cell array. The @qcode{'uniform'} and ## @qcode{'cell'} formats return only the results of @var{func}. ## ## @item @qcode{'ErrorHandler'} ## A function handle that is called when @var{func} throws an error, ## receiving a structure with fields @qcode{identifier}, @qcode{message}, ## and @qcode{index} followed by the inputs passed to @var{func}. ## @end table ## ## @end deftypefn function B = rowfun (func, A, varargin) if (nargin < 2) print_usage (); endif if (! is_function_handle (func)) error ("table.rowfun: FUNC must be a function handle."); endif ## Parse optional Name-Value paired arguments optNames = {'InputVariables', 'GroupingVariables', ... 'OutputVariableNames', 'NumOutputs', 'SeparateInputs', ... 'ExtractCellContents', 'OutputFormat', 'ErrorHandler'}; dfValues = {[], [], [], [], true, false, 'auto', []}; [inVars, grpVars, outNames, numOut, sepIn, extractCell, outFmt, ... errHandler] = parsePairedArguments (optNames, dfValues, varargin(:)); outFmt = check_output_format ('rowfun', outFmt); if (! (isscalar (sepIn) && (islogical (sepIn) || isnumeric (sepIn)))) error ("table.rowfun: 'SeparateInputs' must be a logical scalar."); endif sepIn = logical (sepIn); if (! (isscalar (extractCell) && (islogical (extractCell) || isnumeric (extractCell)))) error ("table.rowfun: 'ExtractCellContents' must be a logical scalar."); endif extractCell = logical (extractCell); if (! isempty (errHandler) && ! is_function_handle (errHandler)) error ("table.rowfun: 'ErrorHandler' must be a function handle."); endif ## Resolve grouping variables and input variables (default input is every ## variable that is not a grouping variable). if (isempty (grpVars)) gIx = []; else gIx = resolveVarRef (A, grpVars)(:)'; endif if (isempty (inVars)) iIx = 1:width (A); iIx(ismember (iIx, gIx)) = []; else iIx = resolveVarRef (A, inVars)(:)'; endif if (isempty (iIx)) error ("table.rowfun: there are no variables to which to apply FUNC."); endif ## Determine the number of outputs requested from FUNC. if (! isempty (numOut)) if (! (isnumeric (numOut) && isscalar (numOut) && numOut >= 0 && numOut == fix (numOut))) error ("table.rowfun: 'NumOutputs' must be a nonnegative integer."); endif nout = numOut; if (! isempty (outNames) && numel (cellstr (outNames)) != nout) error (strcat ("table.rowfun: the number of", ... " 'OutputVariableNames' must equal 'NumOutputs'.")); endif elseif (! isempty (outNames)) nout = numel (cellstr (outNames)); else nout = 1; endif ## Build the output variable names. Default names are 'Var'; for ## grouped output the numbering continues past the grouping variables and ## the GroupCount column (so the first result is 'Var'). if (isempty (outNames)) if (isempty (gIx)) base = 0; else base = numel (gIx) + 1; endif resNames = arrayfun (@(k) sprintf ("Var%d", base + k), 1:nout, ... "UniformOutput", false); else resNames = cellstr (outNames)(:)'; endif inCols = A.VariableValues(iIx); if (isempty (gIx)) ## Ungrouped: apply FUNC to each row. n = height (A); res = cell (n, max (nout, 1)); for r = 1:n rows = false (n, 1); rows(r) = true; args = build_row_args (inCols, rows, sepIn, extractCell); res(r,:) = apply_func (func, errHandler, r, nout, args); endfor B = build_apply_result ('rowfun', outFmt, res(:,1:nout), resNames, ... {}, {}, [], A.RowNames); else ## Grouped: apply FUNC to the rows of each group. [G, ng, repRows, errmsg] = group_table_rows (A.VariableValues(gIx)); if (! isempty (errmsg)) error ("table.rowfun: %s", errmsg); endif res = cell (ng, max (nout, 1)); for g = 1:ng rows = (G == g); args = build_row_args (inCols, rows, sepIn, extractCell); res(g,:) = apply_func (func, errHandler, g, nout, args); endfor [gcols, gcount] = group_output_cols (A.VariableValues(gIx), G, repRows); B = build_grouped_apply_result ('rowfun', outFmt, res(:,1:nout), ... resNames, gcols, ... A.VariableNames(gIx), gcount); endif endfunction ## -*- texinfo -*- ## @deftypefn {table} {@var{G} =} groupsummary (@var{T}, @var{groupvars}) ## @deftypefnx {table} {@var{G} =} groupsummary (@var{T}, @var{groupvars}, @var{groupbins}) ## @deftypefnx {table} {@var{G} =} groupsummary (@var{T}, @var{groupvars}, @var{groupbins}, @var{method}) ## @deftypefnx {table} {@var{G} =} groupsummary (@var{T}, @var{groupvars}, @var{groupbins}, @var{method}, @var{datavars}) ## @deftypefnx {table} {@var{G} =} groupsummary (@dots{}, @var{Name}, @var{Value}) ## ## Compute summary statistics by group for the variables of a table. ## ## @code{@var{G} = groupsummary (@var{T}, @var{groupvars})} groups the rows ## of the table @var{T} by the grouping variables @var{groupvars} and ## returns the table @var{G} with one row per group, holding the grouping ## variables and a @qcode{GroupCount} variable counting the rows in each ## group. @var{groupvars} selects the grouping variables by name, index, ## logical vector, function handle, or @code{vartype} subscript. ## ## @code{@var{G} = groupsummary (@var{T}, @var{groupvars}, @var{method})} ## also applies @var{method} to each data variable within each group and ## appends the results to @var{G}. @var{method} is one of the method names ## below, a function handle, or a cell array of method names and@/or ## function handles: ## ## @table @asis ## @item @qcode{'sum'}, @qcode{'mean'}, @qcode{'median'}, @qcode{'mode'} ## @itemx @qcode{'var'}, @qcode{'std'}, @qcode{'min'}, @qcode{'max'} ## @itemx @qcode{'range'}, @qcode{'nnz'} ## Standard statistics, computed over numeric or logical data variables. ## @code{NaN} values are omitted (as in MATLAB) for every named method ## except @qcode{'nummissing'}. ## ## @item @qcode{'nummissing'} ## The number of missing values in the group, supported for data variables ## of any type. ## ## @item @qcode{'numunique'} ## The number of unique non-missing values in the group, supported for data ## variables of any type. ## @end table ## ## A function handle is applied to each group's slice of each data variable ## and must return a single row (its first dimension must be @code{1}); it ## receives the values with @code{NaN} included. ## ## @code{@var{G} = groupsummary (@var{T}, @var{groupvars}, @var{method}, ## @var{datavars})} applies @var{method} only to the data variables selected ## by @var{datavars} (named, indexed, logical, function handle, or ## @code{vartype} subscript). By default every variable that is not a ## grouping variable is a data variable. ## ## The computed variables of @var{G} are named @code{_}, ## e.g.@: @qcode{mean_X}; results from a function handle are named ## @code{fun_}, where @var{n} is the position of the handle ## among the requested methods. When several methods are requested the ## computed variables are ordered method first, then data variable. ## ## The optional @var{groupbins} argument bins the grouping variables before ## grouping. A binning scheme is one of: a vector of bin edges; a positive ## integer number of equal-width bins spanning the data range; a ## @code{duration} scalar giving a fixed bin width (for a datetime or ## duration grouping variable); or, for a datetime grouping variable, a ## calendar-unit keyword (@qcode{'second'}, @qcode{'minute'}, ## @qcode{'hour'}, @qcode{'day'}, @qcode{'week'}, @qcode{'month'}, ## @qcode{'quarter'}, @qcode{'year'}, @qcode{'decade'}, or ## @qcode{'century'}) that bins by that calendar period. A binned grouping ## variable becomes a categorical and is renamed @code{disc_} for edge, ## bin-count, or width binning, or @code{_} for calendar-unit ## binning. Pass a cell array with one scheme per grouping variable to bin ## them differently, or @qcode{'none'} to leave a variable unbinned. ## ## The following @var{Name}/@var{Value} pairs are accepted: ## ## @table @asis ## @item @qcode{'IncludeMissingGroups'} ## A logical scalar. When @code{true} (the default), rows holding a missing ## value in a grouping variable form their own groups, sorted after the ## non-missing groups. When @code{false}, such rows are excluded. ## ## @item @qcode{'IncludeEmptyGroups'} ## A logical scalar, @code{false} by default. When @code{true}, the unused ## categories of a categorical or binned grouping variable contribute empty ## groups (@qcode{GroupCount} @code{0}, @code{0} for @qcode{'sum'} and ## @qcode{'nnz'}, @code{NaN} otherwise). ## ## @item @qcode{'IncludedEdge'} ## Either @qcode{'left'} (the default) or @qcode{'right'}, selecting which ## edge of each bin is inclusive when @var{groupbins} is given. ## @end table ## ## @end deftypefn function G = groupsummary (T, groupvars, varargin) if (nargin < 2) print_usage (); endif ## Split the trailing arguments into the optional positional METHOD and ## DATAVARS arguments and any Name-Value pairs. A Name-Value region starts ## at the first char-vector/string that names a known option. optNames = {'IncludeMissingGroups', 'IncludeEmptyGroups', 'IncludedEdge'}; nvStart = numel (varargin) + 1; for k = 1:numel (varargin) a = varargin{k}; if ((ischar (a) && isrow (a)) || (isa (a, 'string') && isscalar (a))) if (any (strcmpi (char (a), optNames))) nvStart = k; break; endif endif endfor posArgs = varargin(1:nvStart-1); nvArgs = varargin(nvStart:end); ## An optional GROUPBINS positional argument precedes METHOD. hasGroupbins = false; groupbins = []; if (! isempty (posArgs) && __groupbins__ ('is_spec', posArgs{1})) hasGroupbins = true; groupbins = posArgs{1}; posArgs = posArgs(2:end); endif if (numel (posArgs) > 2) error ("table.groupsummary: too many positional arguments."); endif if (numel (posArgs) >= 1) method = posArgs{1}; else method = {}; endif if (numel (posArgs) >= 2) datavars = posArgs{2}; hasDataVars = true; else datavars = []; hasDataVars = false; endif ## Parse Name-Value options. dfValues = {true, false, 'left'}; [incMiss, incEmpty, incEdge] = ... parsePairedArguments (optNames, dfValues, nvArgs(:)); if (! (isscalar (incMiss) && (islogical (incMiss) || isnumeric (incMiss)))) error (strcat ("table.groupsummary: 'IncludeMissingGroups' must be", ... " a logical scalar.")); endif incMiss = logical (incMiss); if (! (isscalar (incEmpty) && (islogical (incEmpty) || isnumeric (incEmpty)))) error (strcat ("table.groupsummary: 'IncludeEmptyGroups' must be", ... " a logical scalar.")); endif incEmpty = logical (incEmpty); incEdge = check_included_edge ('groupsummary', incEdge); ## Normalise METHOD into parallel cell arrays of method specs and the ## display names used to build output variable names. [methods, methNames, errmsg] = gs_normalise_methods (method); if (! isempty (errmsg)) error ("table.groupsummary: %s", errmsg); endif ## Resolve grouping and data variables. The default data variables are all ## variables that are not grouping variables. gIx = resolveVarRef (T, groupvars)(:)'; if (isempty (gIx)) error ("table.groupsummary: at least one grouping variable is required."); endif if (hasDataVars) dIx = resolveVarRef (T, datavars)(:)'; else dIx = 1:width (T); dIx(ismember (dIx, gIx)) = []; endif ## Bin the grouping variables when a GROUPBINS argument was given. grpCols = T.VariableValues(gIx); grpNames = T.VariableNames(gIx); if (hasGroupbins) [grpCols, grpNames, errmsg] = __groupbins__ ('bin', grpCols, ... grpNames, groupbins, incEdge, 'groupsummary'); if (! isempty (errmsg)) error ("table.groupsummary: %s", errmsg); endif endif ## Group the rows, treating missing grouping values as their own groups ## (sorted last) when IncludeMissingGroups is true; IncludeEmptyGroups adds ## the unused categories of a categorical or binned grouping variable as ## empty groups. [Grp, ng, gcols, errmsg] = gs_grouping (grpCols, incMiss, incEmpty); if (! isempty (errmsg)) error ("table.groupsummary: %s", errmsg); endif gcount = accumarray (Grp(! isnan (Grp)), 1, [ng, 1]); ## Compute each method over each data variable. Output columns are ordered ## data variable first, then method, to match MATLAB's column order. datNames = T.VariableNames(dIx); rescols = {}; resNames = {}; for di = 1:numel (dIx) for mi = 1:numel (methods) col = T.VariableValues{dIx(di)}; vals = cell (ng, 1); for g = 1:ng rows = (Grp == g); [v, errmsg] = gs_apply_method (methods{mi}, col(rows,:)); if (! isempty (errmsg)) error ("table.groupsummary: %s (variable '%s').", errmsg, ... datNames{di}); endif vals{g} = v; endfor try rescols{end+1} = vertcat (vals{:}); catch error (strcat ("table.groupsummary: the '%s' results for", ... " variable '%s' cannot be concatenated into a", ... " column."), methNames{mi}, datNames{di}); end_try_catch resNames{end+1} = sprintf ("%s_%s", methNames{mi}, datNames{di}); endfor endfor vars = [gcols, {gcount}, rescols]; names = [grpNames, {'GroupCount'}, resNames]; G = table (vars{:}, 'VariableNames', names); endfunction ## -*- texinfo -*- ## @deftypefn {table} {@var{G} =} groupcounts (@var{T}, @var{groupvars}) ## @deftypefnx {table} {@var{G} =} groupcounts (@var{T}, @var{groupvars}, @var{groupbins}) ## @deftypefnx {table} {@var{G} =} groupcounts (@dots{}, @var{Name}, @var{Value}) ## ## Count the number of rows in each group of a table. ## ## @code{@var{G} = groupcounts (@var{T}, @var{groupvars})} groups the rows ## of the table @var{T} by the grouping variables @var{groupvars} and ## returns the table @var{G} with one row per group, holding the grouping ## variables, a @qcode{GroupCount} variable counting the rows in each group, ## and a @qcode{Percent} variable giving each group's count as a percentage ## of the total. @var{groupvars} selects the grouping variables by name, ## index, logical vector, function handle, or @code{vartype} subscript. ## ## The optional @var{groupbins} argument bins the grouping variables before ## grouping, using bin edges, a number of equal-width bins, a ## @code{duration} bin width, or a datetime calendar-unit keyword, or a cell ## array with one scheme per grouping variable. A binned grouping variable ## becomes a categorical and is renamed @code{disc_} or ## @code{_}. See @code{groupsummary} for details. ## ## Groups are the sorted unique combinations of grouping values. The ## following @var{Name}/@var{Value} pairs are accepted: ## ## @table @asis ## @item @qcode{'IncludeMissingGroups'} ## A logical scalar. When @code{true} (the default), rows holding a missing ## value in a grouping variable form their own groups, sorted after the ## non-missing groups. When @code{false}, such rows are excluded. ## ## @item @qcode{'IncludeEmptyGroups'} ## A logical scalar, @code{false} by default. When @code{true}, the unused ## categories of a categorical or binned grouping variable contribute empty ## groups with a @qcode{GroupCount} of @code{0}. ## ## @item @qcode{'IncludedEdge'} ## Either @qcode{'left'} (the default) or @qcode{'right'}, selecting which ## edge of each bin is inclusive when @var{groupbins} is given. ## @end table ## ## @end deftypefn function G = groupcounts (T, groupvars, varargin) if (nargin < 2) print_usage (); endif ## An optional GROUPBINS positional argument may precede the Name-Value ## options; anything else after GROUPVARS must be a recognised option. optNames = {'IncludeMissingGroups', 'IncludeEmptyGroups', 'IncludedEdge'}; hasGroupbins = false; groupbins = []; if (! isempty (varargin)) a = varargin{1}; isOpt = ((ischar (a) && isrow (a)) ... || (isa (a, 'string') && isscalar (a))) ... && any (strcmpi (char (a), optNames)); if (! isOpt) if (__groupbins__ ('is_spec', a)) hasGroupbins = true; groupbins = a; varargin = varargin(2:end); else error (strcat ("table.groupcounts: invalid argument; expected a", ... " GROUPBINS binning scheme or a Name-Value option.")); endif endif endif ## Parse Name-Value options. dfValues = {true, false, 'left'}; [incMiss, incEmpty, incEdge] = ... parsePairedArguments (optNames, dfValues, varargin(:)); if (! (isscalar (incMiss) && (islogical (incMiss) || isnumeric (incMiss)))) error (strcat ("table.groupcounts: 'IncludeMissingGroups' must be", ... " a logical scalar.")); endif incMiss = logical (incMiss); if (! (isscalar (incEmpty) && (islogical (incEmpty) || isnumeric (incEmpty)))) error (strcat ("table.groupcounts: 'IncludeEmptyGroups' must be", ... " a logical scalar.")); endif incEmpty = logical (incEmpty); incEdge = check_included_edge ('groupcounts', incEdge); ## Resolve grouping variables. gIx = resolveVarRef (T, groupvars)(:)'; if (isempty (gIx)) error (strcat ("table.groupcounts: at least one grouping variable", ... " is required.")); endif ## Bin the grouping variables when a GROUPBINS argument was given. grpCols = T.VariableValues(gIx); grpNames = T.VariableNames(gIx); if (hasGroupbins) [grpCols, grpNames, errmsg] = __groupbins__ ('bin', grpCols, ... grpNames, groupbins, incEdge, 'groupcounts'); if (! isempty (errmsg)) error ("table.groupcounts: %s", errmsg); endif endif ## Group the rows, treating missing grouping values as their own groups ## (sorted last) when IncludeMissingGroups is true; IncludeEmptyGroups adds ## the unused categories of a categorical or binned grouping variable as ## empty groups. [Grp, ng, gcols, errmsg] = gs_grouping (grpCols, incMiss, incEmpty); if (! isempty (errmsg)) error ("table.groupcounts: %s", errmsg); endif gcount = accumarray (Grp(! isnan (Grp)), 1, [ng, 1]); pcent = 100 * gcount / sum (gcount); vars = [gcols, {gcount, pcent}]; names = [grpNames, {'GroupCount', 'Percent'}]; G = table (vars{:}, 'VariableNames', names); endfunction ## -*- texinfo -*- ## @deftypefn {table} {@var{G} =} groupfilter (@var{T}, @var{groupvars}, @var{method}) ## @deftypefnx {table} {@var{G} =} groupfilter (@var{T}, @var{groupvars}, @var{groupbins}, @var{method}) ## @deftypefnx {table} {@var{G} =} groupfilter (@dots{}, @var{method}, @var{datavars}) ## ## Filter the rows of a table by a per-group condition. ## ## @code{@var{G} = groupfilter (@var{T}, @var{groupvars}, @var{method})} ## groups the rows of the table @var{T} by the grouping variables ## @var{groupvars}, applies the filter function @var{method} to each group, ## and returns the table @var{G} holding the rows that satisfy the ## condition, in their original order and with all the variables of ## @var{T}. @var{groupvars} selects the grouping variables by name, index, ## logical vector, function handle, or @code{vartype} subscript. ## ## @var{method} is a function handle applied to each group's slice of every ## data variable. It must return either a logical scalar, which keeps or ## drops the whole group, or a logical vector with one element per row of ## the group, which keeps or drops the individual rows. A row is kept only ## when the condition holds for it across all data variables. ## ## @code{@var{G} = groupfilter (@var{T}, @var{groupvars}, @var{method}, ## @var{datavars})} applies @var{method} only to the data variables selected ## by @var{datavars} (named, indexed, logical, function handle, or ## @code{vartype} subscript). By default every variable that is not a ## grouping variable is a data variable. ## ## Rows holding a missing value in a grouping variable form their own ## groups, to which @var{method} is applied like any other group. ## ## The optional @var{groupbins} argument bins the grouping variables before ## grouping, using bin edges, a number of equal-width bins, a ## @code{duration} bin width, or a datetime calendar-unit keyword, or a cell ## array with one scheme per grouping variable; see @code{groupsummary} for ## details. The @qcode{'IncludedEdge'} Name-Value pair (@qcode{'left'} by ## default, or @qcode{'right'}) selects which bin edge is inclusive. ## ## @end deftypefn function G = groupfilter (T, groupvars, varargin) if (nargin < 3) print_usage (); endif ## Split off a trailing 'IncludedEdge' Name-Value option, then an optional ## GROUPBINS positional argument that precedes the filter function METHOD. optNames = {'IncludedEdge'}; args = varargin; nvStart = numel (args) + 1; for k = 1:numel (args) a = args{k}; if (((ischar (a) && isrow (a)) || (isa (a, 'string') && isscalar (a))) && any (strcmpi (char (a), optNames))) nvStart = k; break; endif endfor nvArgs = args(nvStart:end); args = args(1:nvStart-1); incEdge = parsePairedArguments (optNames, {'left'}, nvArgs(:)); incEdge = check_included_edge ('groupfilter', incEdge); hasGroupbins = false; groupbins = []; if (! isempty (args) && __groupbins__ ('is_spec', args{1})) hasGroupbins = true; groupbins = args{1}; args = args(2:end); endif ## The filter function METHOD is the first remaining argument. if (isempty (args)) print_usage (); endif method = args{1}; if (! is_function_handle (method)) error ("table.groupfilter: METHOD must be a function handle."); endif ## An optional DATAVARS argument may follow the filter function. rest = args(2:end); if (numel (rest) > 1) error ("table.groupfilter: too many positional arguments."); endif if (numel (rest) == 1) datavars = rest{1}; hasDataVars = true; else datavars = []; hasDataVars = false; endif ## Resolve grouping and data variables. The default data variables are all ## variables that are not grouping variables. gIx = resolveVarRef (T, groupvars)(:)'; if (isempty (gIx)) error (strcat ("table.groupfilter: at least one grouping variable", ... " is required.")); endif if (hasDataVars) dIx = resolveVarRef (T, datavars)(:)'; else dIx = 1:width (T); dIx(ismember (dIx, gIx)) = []; endif ## Bin the grouping variables when a GROUPBINS argument was given, then ## group the rows, treating missing grouping values as their own groups so ## that every row belongs to exactly one group. grpCols = T.VariableValues(gIx); if (hasGroupbins) [grpCols, ~, errmsg] = __groupbins__ ('bin', grpCols, T.VariableNames(gIx), ... groupbins, incEdge, 'groupfilter'); if (! isempty (errmsg)) error ("table.groupfilter: %s", errmsg); endif endif [Grp, ng, ~, errmsg] = gs_group_rows (grpCols, true); if (! isempty (errmsg)) error ("table.groupfilter: %s", errmsg); endif ## Build the row keep-mask by applying METHOD to each data variable. [keep, errmsg] = gf_keep_mask (method, T.VariableValues(dIx), Grp, ng); if (! isempty (errmsg)) error ("table.groupfilter: %s", errmsg); endif G = subsetrows (T, find (keep)); endfunction ## -*- texinfo -*- ## @deftypefn {table} {@var{G} =} grouptransform (@var{T}, @var{groupvars}, @var{method}) ## @deftypefnx {table} {@var{G} =} grouptransform (@var{T}, @var{groupvars}, @var{groupbins}, @var{method}) ## @deftypefnx {table} {@var{G} =} grouptransform (@dots{}, @var{method}, @var{datavars}) ## @deftypefnx {table} {@var{G} =} grouptransform (@dots{}, @var{Name}, @var{Value}) ## ## Transform the data variables of a table group by group. ## ## @code{@var{G} = grouptransform (@var{T}, @var{groupvars}, @var{method})} ## groups the rows of the table @var{T} by the grouping variables ## @var{groupvars}, applies @var{method} to each data variable within each ## group, and returns the table @var{G} with the transformed values, one ## row per row of @var{T} and in the original order. @var{groupvars} ## selects the grouping variables by name, index, logical vector, function ## handle, or @code{vartype} subscript. ## ## @var{method} is one of the transform names below or a function handle: ## ## @table @asis ## @item @qcode{'zscore'} ## Center and scale each group to zero mean and unit standard deviation. ## ## @item @qcode{'norm'} ## Divide each group by its 2-norm. ## ## @item @qcode{'meancenter'} ## Subtract the group mean. ## ## @item @qcode{'rescale'} ## Rescale each group to the range @code{[0, 1]}. ## ## @item @qcode{'meanfill'} ## Replace missing values with the group mean. ## ## @item @qcode{'linearfill'} ## Fill missing values by linear interpolation within the group; leading and ## trailing missing values are left unchanged. ## @end table ## ## For the named methods @code{NaN} values are omitted when computing the ## group statistics. A function handle is applied to each group's slice of ## each data variable and must return either a single row (broadcast to all ## the group's rows) or a result with one row per row of the group. ## ## @code{@var{G} = grouptransform (@var{T}, @var{groupvars}, @var{method}, ## @var{datavars})} transforms only the data variables selected by ## @var{datavars} (named, indexed, logical, function handle, or ## @code{vartype} subscript). By default every variable that is not a ## grouping variable is a data variable. ## ## The following @var{Name}/@var{Value} pair is accepted: ## ## @table @asis ## @item @qcode{'ReplaceValues'} ## A logical scalar. When @code{true} (the default), each data variable is ## replaced by its transformed values. When @code{false}, the transformed ## values are appended as new variables named @code{_} ## (@code{fun1_} for a function handle), leaving the originals in ## place. ## ## @item @qcode{'IncludedEdge'} ## Either @qcode{'left'} (the default) or @qcode{'right'}, selecting which ## edge of each bin is inclusive when @var{groupbins} is given. ## @end table ## ## Rows holding a missing value in a grouping variable form their own ## groups, which are transformed like any other group. The optional ## @var{groupbins} argument bins the grouping variables before grouping, ## using bin edges, a number of equal-width bins, a @code{duration} bin ## width, or a datetime calendar-unit keyword, or a cell array with one ## scheme per grouping variable; see @code{groupsummary} for details. ## ## @end deftypefn function G = grouptransform (T, groupvars, varargin) if (nargin < 3) print_usage (); endif ## An optional GROUPBINS positional argument precedes the transform METHOD ## (a known method name or a function handle). args = varargin; hasGroupbins = false; groupbins = []; if (! isempty (args) && __groupbins__ ('is_spec', args{1})) hasGroupbins = true; groupbins = args{1}; args = args(2:end); endif if (isempty (args)) print_usage (); endif method = args{1}; knownMethods = {'zscore', 'norm', 'meancenter', 'rescale', ... 'meanfill', 'linearfill'}; if (is_function_handle (method)) methDisp = 'fun1'; elseif (((ischar (method) && isrow (method)) || (isa (method, 'string') && isscalar (method))) && any (strcmpi (char (method), knownMethods))) method = lower (char (method)); methDisp = method; else error (strcat ("table.grouptransform: METHOD must be one of 'zscore',", ... " 'norm', 'meancenter', 'rescale', 'meanfill',", ... " 'linearfill', or a function handle.")); endif ## Split the remaining arguments into the optional positional DATAVARS and ## any Name-Value pairs (a Name-Value region starts at the first option). rest = args(2:end); optNames = {'ReplaceValues', 'IncludedEdge'}; nvStart = numel (rest) + 1; for k = 1:numel (rest) a = rest{k}; if (((ischar (a) && isrow (a)) || (isa (a, 'string') && isscalar (a))) && any (strcmpi (char (a), optNames))) nvStart = k; break; endif endfor posArgs = rest(1:nvStart-1); nvArgs = rest(nvStart:end); if (numel (posArgs) > 1) error ("table.grouptransform: too many positional arguments."); endif if (numel (posArgs) == 1) datavars = posArgs{1}; hasDataVars = true; else datavars = []; hasDataVars = false; endif dfValues = {true, 'left'}; [replaceVals, incEdge] = ... parsePairedArguments (optNames, dfValues, nvArgs(:)); if (! (isscalar (replaceVals) && (islogical (replaceVals) || isnumeric (replaceVals)))) error (strcat ("table.grouptransform: 'ReplaceValues' must be a", ... " logical scalar.")); endif replaceVals = logical (replaceVals); incEdge = check_included_edge ('grouptransform', incEdge); ## Resolve grouping and data variables. The default data variables are all ## variables that are not grouping variables. gIx = resolveVarRef (T, groupvars)(:)'; if (isempty (gIx)) error (strcat ("table.grouptransform: at least one grouping variable", ... " is required.")); endif if (hasDataVars) dIx = resolveVarRef (T, datavars)(:)'; else dIx = 1:width (T); dIx(ismember (dIx, gIx)) = []; endif ## Bin the grouping variables when a GROUPBINS argument was given, then ## group the rows, treating missing grouping values as their own groups so ## that every row belongs to exactly one group. grpCols = T.VariableValues(gIx); if (hasGroupbins) [grpCols, ~, errmsg] = __groupbins__ ('bin', grpCols, T.VariableNames(gIx), ... groupbins, incEdge, 'grouptransform'); if (! isempty (errmsg)) error ("table.grouptransform: %s", errmsg); endif endif [Grp, ng, ~, errmsg] = gs_group_rows (grpCols, true); if (! isempty (errmsg)) error ("table.grouptransform: %s", errmsg); endif ## Transform each data variable, group by group. transCols = cell (1, numel (dIx)); for i = 1:numel (dIx) [tc, errmsg] = gt_transform_col (method, T.VariableValues{dIx(i)}, ... Grp, ng); if (! isempty (errmsg)) error ("table.grouptransform: %s (variable '%s').", errmsg, ... T.VariableNames{dIx(i)}); endif transCols{i} = tc; endfor if (replaceVals) G = T; for i = 1:numel (dIx) G.VariableValues{dIx(i)} = transCols{i}; endfor else newNames = cell (1, numel (dIx)); for i = 1:numel (dIx) newNames{i} = sprintf ("%s_%s", methDisp, T.VariableNames{dIx(i)}); endfor G = addvars (T, transCols{:}, 'NewVariableNames', newNames); endif endfunction ## -*- texinfo -*- ## @deftypefn {table} {@var{P} =} pivot (@var{T}, @qcode{'Columns'}, @var{colvars}) ## @deftypefnx {table} {@var{P} =} pivot (@var{T}, @qcode{'Rows'}, @var{rowvars}) ## @deftypefnx {table} {@var{P} =} pivot (@dots{}, @var{Name}, @var{Value}) ## ## Summarize tabular data in a pivoted table. ## ## @code{@var{P} = pivot (@var{T}, 'Columns', @var{colvars}, 'Rows', ## @var{rowvars})} reshapes the table @var{T} into the pivoted table ## @var{P}. The unique combinations of the grouping variables @var{colvars} ## become the variables (columns) of @var{P}, the unique combinations of the ## grouping variables @var{rowvars} become its rows, and each cell holds one ## statistic computed over the rows of @var{T} that fall into that ## row-and-column group. At least one of @qcode{'Columns'} or ## @qcode{'Rows'} is required; an omitted dimension collapses to a single ## group. Each of @var{colvars} and @var{rowvars} selects variables by ## name, index, or logical vector, and may name several variables. ## ## Groups are the sorted unique combinations of the grouping values, with ## the first variable varying slowest; a categorical variable groups by its ## category order. Column variable names are taken from the grouping values ## (e.g.@: @qcode{'true'}/@qcode{'false'} for a logical variable), joined ## with @qcode{'_'} when several variables define the columns. ## ## The following @var{Name}/@var{Value} pairs are accepted: ## ## @table @asis ## @item @qcode{'DataVariable'} ## The single variable whose values are aggregated. When omitted, the cells ## hold group counts. ## ## @item @qcode{'Method'} ## The aggregation applied to @qcode{'DataVariable'}: one of ## @qcode{'count'}, @qcode{'sum'}, @qcode{'mean'}, @qcode{'median'}, ## @qcode{'mode'}, @qcode{'std'}, @qcode{'var'}, @qcode{'min'}, ## @qcode{'max'}, @qcode{'range'}, @qcode{'nummissing'}, ## @qcode{'numunique'}, @qcode{'nnz'}, @qcode{'percentage'}, ## @qcode{'none'}, or a function handle. Named methods omit missing ## values. The default is @qcode{'count'} when no data variable is given ## or the data variable is non-numeric, and @qcode{'sum'} when it is ## numeric. @qcode{'none'} rearranges the data without aggregating and ## requires at most one value per cell. ## ## @item @qcode{'IncludeMissingGroups'} ## A logical scalar, @code{true} by default. When @code{true}, rows ## holding a missing value in a grouping variable form their own group, ## sorted last; when @code{false}, such rows are excluded. ## ## @item @qcode{'IncludeEmptyGroups'} ## A logical scalar, @code{false} by default. When @code{true}, every ## category of a categorical grouping variable contributes a group even ## if it is unused in the data, so unused combinations appear as empty ## cells. ## ## @item @qcode{'IncludeTotals'} ## A logical scalar, @code{false} by default. When @code{true}, a ## @qcode{'Total'} marginal row and/or column holding the same statistic ## computed over each margin is appended. Row labels are then placed in the ## row names. ## ## @item @qcode{'RowLabelPlacement'} ## Either @qcode{'variable'} (the default), which keeps the row grouping ## variables as the leftmost variables of @var{P}, or @qcode{'rownames'}, ## which places the row group labels in the @code{RowNames} property. ## ## @item @qcode{'ColumnsBinMethod'}, @qcode{'RowsBinMethod'} ## A binning scheme applied to the @qcode{'Columns'} or @qcode{'Rows'} ## grouping variables before pivoting: a vector of bin edges, a number of ## equal-width bins, a @code{duration} bin width, or a datetime ## calendar-unit keyword (see @code{groupsummary}), or a cell array with one ## scheme per variable. Each binned variable becomes a categorical. The ## default @qcode{'none'} applies no binning. ## ## @item @qcode{'IncludedEdge'} ## Either @qcode{'left'} (the default) or @qcode{'right'}, selecting which ## edge of each bin is inclusive when a binning scheme is given. ## ## @item @qcode{'OutputFormat'} ## @qcode{'flat'} (default) names each output column after the joined column ## grouping values (@qcode{@var{lvl}_@var{lvl}}). @qcode{'nested'} instead ## groups two or more @qcode{'Columns'} variables into nested tables: one ## outer variable per level of the first column grouping variable, each a ## nested @code{table} whose variables are the next grouping variable's ## levels (recursively). A marginal-total column, if any, stays a flat ## outer variable. ## @end table ## ## @end deftypefn function P = pivot (T, varargin) if (nargin < 1) print_usage (); endif ## Parse Name-Value options; unrecognised names land in REST. optNames = {'Columns', 'Rows', 'DataVariable', 'Method', ... 'IncludeMissingGroups', 'IncludeEmptyGroups', ... 'IncludeTotals', 'RowLabelPlacement', 'OutputFormat', ... 'ColumnsBinMethod', 'RowsBinMethod', 'IncludedEdge'}; dfValues = {[], [], [], [], true, false, false, 'variable', 'flat', ... 'none', 'none', 'left'}; [colvars, rowvars, datavar, method, incMiss, incEmpty, incTot, ... rowPlace, outFmt, colBin, rowBin, incEdge, rest] = ... parsePairedArguments (optNames, dfValues, varargin(:)); if (! isempty (rest)) bad = rest{1}; if (isa (bad, 'string')) bad = char (bad); endif if (ischar (bad) && isrow (bad)) error ("table.pivot: unrecognised option '%s'.", bad); else error ("table.pivot: invalid optional arguments."); endif endif ## Validate the IncludedEdge binning and OutputFormat options. incEdge = check_included_edge ('pivot', incEdge); if (isa (outFmt, 'string')) outFmt = char (outFmt); endif if (! (ischar (outFmt) && isrow (outFmt) ... && any (strcmpi (outFmt, {'flat', 'nested'})))) error ("table.pivot: 'OutputFormat' must be 'flat' or 'nested'."); endif nested = strcmpi (outFmt, 'nested'); ## Validate the logical-scalar and RowLabelPlacement options. incMiss = pivot_logical_opt ('IncludeMissingGroups', incMiss); incEmpty = pivot_logical_opt ('IncludeEmptyGroups', incEmpty); incTot = pivot_logical_opt ('IncludeTotals', incTot); if (isa (rowPlace, 'string')) rowPlace = char (rowPlace); endif if (! (ischar (rowPlace) && isrow (rowPlace) ... && any (strcmpi (rowPlace, {'variable', 'rownames'})))) error (strcat ("table.pivot: 'RowLabelPlacement' must be 'variable'", ... " or 'rownames'.")); endif rowPlace = lower (rowPlace); ## At least one grouping dimension is required. if (isempty (colvars) && isempty (rowvars)) error (strcat ("table.pivot: specify at least one of 'Columns' or", ... " 'Rows'.")); endif ## Resolve grouping and data variables. if (isempty (colvars)) colIx = []; else colIx = resolveVarRef (T, colvars)(:)'; endif if (isempty (rowvars)) rowIx = []; else rowIx = resolveVarRef (T, rowvars)(:)'; endif hasDV = ! isempty (datavar); if (hasDV) dvIx = resolveVarRef (T, datavar)(:)'; if (! isscalar (dvIx)) error (strcat ("table.pivot: 'DataVariable' must specify a single", ... " variable.")); endif dataVals = T.VariableValues{dvIx}; else dvIx = []; dataVals = []; endif ## Resolve the aggregation method and its default. reqDataMethods = {'sum', 'mean', 'median', 'mode', 'std', 'var', ... 'min', 'max', 'range', 'nnz'}; knownMethods = [{'count', 'percentage', 'nummissing', 'numunique', ... 'none'}, reqDataMethods]; if (isempty (method)) if (! hasDV || ! (isnumeric (dataVals) || islogical (dataVals))) method = 'count'; else method = 'sum'; endif elseif (isa (method, 'string') && isscalar (method)) method = char (method); endif if (ischar (method)) method = lower (method); if (! (isrow (method) && any (strcmp (method, knownMethods)))) error ("table.pivot: unknown Method '%s'.", method); endif if (! hasDV && (any (strcmp (method, reqDataMethods)) ... || strcmp (method, 'none'))) error ("table.pivot: Method '%s' requires a 'DataVariable'.", method); endif elseif (! is_function_handle (method)) error (strcat ("table.pivot: 'Method' must be a method name or a", ... " function handle.")); endif isNone = ischar (method) && strcmp (method, 'none'); if (isNone && incTot) error (strcat ("table.pivot: 'IncludeTotals' is not supported with", ... " Method 'none'.")); endif ## The display name of the method labels the single output variable when ## 'Columns' is omitted and the 'Overall_' totals row and column. if (ischar (method)) methodName = method; else methodName = apply_func_name (method); endif totalLabel = ['Overall_', methodName]; ## Bin the row/column grouping variables when a 'RowsBinMethod' or ## 'ColumnsBinMethod' was given (a per-variable cell or a single scheme). rowGcols = T.VariableValues(rowIx); colGcols = T.VariableValues(colIx); rowGnames = T.VariableNames(rowIx); colGnames = T.VariableNames(colIx); rowNone = ((ischar (rowBin) && isrow (rowBin)) ... || (isa (rowBin, 'string') && isscalar (rowBin))) ... && strcmpi (char (rowBin), 'none'); colNone = ((ischar (colBin) && isrow (colBin)) ... || (isa (colBin, 'string') && isscalar (colBin))) ... && strcmpi (char (colBin), 'none'); if (! rowNone && ! isempty (rowIx)) [rowGcols, rowGnames, emsg] = __groupbins__ ('bin', rowGcols, ... rowGnames, rowBin, incEdge, 'pivot'); if (! isempty (emsg)) error ("table.pivot: %s", emsg); endif endif if (! colNone && ! isempty (colIx)) [colGcols, colGnames, emsg] = __groupbins__ ('bin', colGcols, ... colGnames, colBin, incEdge, 'pivot'); if (! isempty (emsg)) error ("table.pivot: %s", emsg); endif endif ## Group the rows along each dimension. n = height (T); [rGid, nR, rLvlOf, rLevVals, ~, emsg] = ... pivot_dimension (rowGcols, n, incMiss, incEmpty); if (! isempty (emsg)) error ("table.pivot: %s.", emsg); endif [cGid, nC, cLvlOf, cLevVals, cMissLvls, emsg] = ... pivot_dimension (colGcols, n, incMiss, incEmpty); if (! isempty (emsg)) error ("table.pivot: %s.", emsg); endif assigned = ! isnan (rGid) & ! isnan (cGid); totalAssigned = sum (assigned); ## Build one output data column per column group. dataCols = cell (1, nC); for c = 1:nC if (isNone) [col, emsg] = pivot_none_column (dataVals, rGid, cGid, c, nR); if (! isempty (emsg)) error ("table.pivot: %s.", emsg); endif else col = NaN (nR, 1); for r = 1:nR rows = find (rGid == r & cGid == c); [v, emsg] = ... pivot_cell_value (method, hasDV, dataVals, rows, totalAssigned); if (! isempty (emsg)) error ("table.pivot: %s.", emsg); endif col(r) = v; endfor endif dataCols{c} = col; endfor ## Column variable names. With no 'Columns', a single variable named for ## the method ('_', or just 'count' with no data variable); ## otherwise one per column group, from the grouping values (a missing group ## becomes '>'). if (isempty (colIx)) if (hasDV) colNames = {[methodName, '_', T.VariableNames{dvIx}]}; else colNames = {methodName}; endif else colNames = cell (1, nC); for c = 1:nC parts = cell (1, numel (colIx)); for j = 1:numel (colIx) lvl = cLvlOf(c,j); if (cMissLvls{j}(lvl)) parts{j} = sprintf ("", colGnames{j}); else parts{j} = pivot_value_name (cLevVals{j}(lvl, :)); endif endfor colNames{c} = strjoin (parts, '_'); endfor endif ## Row label columns (one per row grouping variable). if (isempty (rowIx)) rowLabelCols = {}; rowLabelNames = {}; else rowLabelCols = cell (1, numel (rowIx)); for j = 1:numel (rowIx) rowLabelCols{j} = rLevVals{j}(rLvlOf(:,j), :); endfor rowLabelNames = rowGnames; endif ## Marginal totals, recomputed over each margin from the raw data. addTotalCol = incTot && ! isempty (colIx); addTotalRow = incTot && ! isempty (rowIx); if (incTot) colMargin = NaN (nR, 1); for r = 1:nR rows = find (rGid == r & assigned); [colMargin(r), emsg] = ... pivot_cell_value (method, hasDV, dataVals, rows, totalAssigned); if (! isempty (emsg)) error ("table.pivot: %s.", emsg); endif endfor rowMargin = NaN (1, nC); for c = 1:nC rows = find (cGid == c & assigned); [rowMargin(c), emsg] = ... pivot_cell_value (method, hasDV, dataVals, rows, totalAssigned); if (! isempty (emsg)) error ("table.pivot: %s.", emsg); endif endfor rows = find (assigned); [grand, emsg] = ... pivot_cell_value (method, hasDV, dataVals, rows, totalAssigned); if (! isempty (emsg)) error ("table.pivot: %s.", emsg); endif if (addTotalRow) for c = 1:nC dataCols{c} = [dataCols{c}; rowMargin(c)]; endfor endif if (addTotalCol) tcol = colMargin; if (addTotalRow) tcol = [tcol; grand]; endif dataCols{end+1} = tcol; colNames{end+1} = totalLabel; endif endif ## Column variables for the output: flat (the '_' data columns) or, ## for 'OutputFormat','nested' with two or more 'Columns' variables, nested ## tables grouped by the column hierarchy (the marginal-total column, if ## any, stays a flat outer variable). if (nested && numel (colIx) >= 2) [colVars, finalNames] = pivot_nest (dataCols(1:nC), cLvlOf, cLevVals, ... cMissLvls, colGnames); if (addTotalCol) colVars{end+1} = dataCols{nC + 1}; finalNames{end+1} = totalLabel; endif else colVars = dataCols; finalNames = colNames; endif ## Assemble the output table. Totals keep the row grouping variables and ## label the marginal row with 'Overall_', appended to the single ## row-label variable; a multi-variable or non-text row label falls back to ## row-name labelling instead. useRowNames = strcmp (rowPlace, 'rownames'); if (incTot && ! useRowNames && addTotalRow) if (numel (rowIx) == 1) [lc, ok] = pivot_append_label (rowLabelCols{1}, totalLabel); if (ok) rowLabelCols{1} = lc; else useRowNames = true; endif else useRowNames = true; endif endif if (useRowNames) if (isempty (rowIx)) P = table (colVars{:}, 'VariableNames', finalNames); else rn = pivot_row_names (rowLabelCols); if (addTotalRow) rn = [rn; {totalLabel}]; endif P = table (colVars{:}, 'VariableNames', finalNames, 'RowNames', rn); endif else allVars = [rowLabelCols, colVars]; allNames = [rowLabelNames, finalNames]; P = table (allVars{:}, 'VariableNames', allNames); endif endfunction endmethods ################################################################################ ## ** Auxiliary Methods ** ## ################################################################################ ## Available Methods ## ## ## ## 'horzcat' 'iscolumn' 'isempty' 'ismatrix' ## ## 'isrow' 'isscalar' 'istable' 'isvector' ## ## 'length' 'ndims' 'numel' 'repelem' ## ## 'repmat' 'size' 'squeeze' 'vertcat' ## ## ## ################################################################################ methods (Access = public) ## -*- texinfo -*- ## @deftypefn {table} {@var{tbl} =} horzcat (@var{tbl1}, @var{tbl2}, @dots{}) ## ## Horizontal concatenation for tables. ## ## @code{@var{tbl} = horzcat (@var{tbl1}, @var{tbl2}, @dots{})} merges ## tables by horizontally concatenating them, provided that all input tables ## have collectively unique variable names and the same number of rows. ## ## Input tables that have row names must share the same unique set of row ## names but not necessarily in the same order. When row names are present ## in multiple input tables, their position is matched to the row names of ## the first input table. Input tables without row names are concatenated ## by position without re-indexing. Output table's @qcode{Description} and ## @qcode{UserData} properties are assigned using the first non-empty value. ## ## @end deftypefn function tbl = horzcat (varargin) ## All inputs must be tables are_tables = cellfun (@istable, varargin); if (! all (are_tables)) error ("table.horzcat: all inputs must be tables."); endif ## All tables must have unique variable names varNames = cellfun (@(obj) obj.VariableNames, varargin, ... 'UniformOutput', false); is_empty = cellfun (@isempty, varNames); varNames = [varNames{:}]; if (numel (varNames) != numel (unique (varNames))) error (strcat ("table.horzcat: all input tables must have unique", ... " variable names.")); endif ## All tables must have the same rows (height) numRows = cellfun (@height, varargin); if (numel (unique (numRows(! is_empty))) != 1) error ("table.horzcat: all input tables must have the same height."); endif numRows = numRows(1); ## Check for RowNames has_RowNames = ! cellfun (@(obj) isempty (obj.RowNames), varargin); if (! any (has_RowNames)) # no RowNames in any table (easy) tbl = varargin{1}; tbl.VariableNames = varNames; for i = 2:numel (varargin) in = varargin{i}; tbl.VariableValues = [tbl.VariableValues, in.VariableValues]; tbl.VariableDescriptions = [tbl.VariableDescriptions, ... in.VariableDescriptions]; tbl.VariableUnits = [tbl.VariableUnits, in.VariableUnits]; if (isempty (tbl.Description)) tbl.Description = in.Description; endif if (isempty (tbl.UserData)) tbl.UserData = in.UserData; endif endfor elseif (sum (has_RowNames) == 1) # only one input table has RowNames (ok) tbl = varargin{1}; tbl.VariableNames = varNames; for i = 2:numel (varargin) in = varargin{i}; tbl.VariableValues = [tbl.VariableValues, in.VariableValues]; tbl.VariableDescriptions = [tbl.VariableDescriptions, ... in.VariableDescriptions]; tbl.VariableUnits = [tbl.VariableUnits, in.VariableUnits]; if (! isempty (in.RowNames)) tbl.RowNames = in.RowNames; endif if (isempty (tbl.Description)) tbl.Description = in.Description; endif if (isempty (tbl.UserData)) tbl.UserData = in.UserData; endif endfor else # multiple tables has rowNames (we are screwed) ## First we need to ensure that all tables with RowNames share the ## same unique RowNames (in any order) rowNames = cellfun (@(obj) obj.RowNames, varargin(has_RowNames), ... 'UniformOutput', false); sortedRowNames = cellfun (@sort, rowNames, 'UniformOutput', false); if (! isequal (sortedRowNames{:})) error ("table.horzcat: input tables must have identical RowNames."); endif ## We need to figure out some indexing for every other table with ## RowNames so that we now how to merge them with the first table ## with RowNames tbl_withRowNames = find (has_RowNames); index = [1:numRows]'; # first table is reindexed to itself for i = 2:numel (rowNames) ## For each row of the first table, find the matching row in the ## i-th table (the inverse map), so subsetrows aligns it to the first. fcn = @(x) find (ismember (rowNames{i}, x)); index(:,i) = cellfun (fcn, rowNames{1}); endfor ## Start merging tables and re-index every other table with RowNames ## before merging tbl = varargin{1}; tbl.VariableNames = varNames; tbl.VariableValues = {}; tbl.VariableDescriptions = {}; tbl.VariableUnits = {}; tbl.RowNames = {}; add_row_names = true; # only once for i = 1:numel (varargin) in = varargin{i}; if (ismember (i, tbl_withRowNames)) # this table has RowNames ixRows = index(:,1); index(:,1) = []; in = subsetrows (in, ixRows); if (add_row_names) tbl.RowNames = in.RowNames(ixRows); add_row_names = false; endif tbl.VariableValues = [tbl.VariableValues, in.VariableValues]; tbl.VariableDescriptions = [tbl.VariableDescriptions, ... in.VariableDescriptions]; tbl.VariableUnits = [tbl.VariableUnits, in.VariableUnits]; else tbl.VariableValues = [tbl.VariableValues, in.VariableValues]; tbl.VariableDescriptions = [tbl.VariableDescriptions, ... in.VariableDescriptions]; tbl.VariableUnits = [tbl.VariableUnits, in.VariableUnits]; endif if (isempty (tbl.Description)) tbl.Description = in.Description; endif if (isempty (tbl.UserData)) tbl.UserData = in.UserData; endif endfor endif ## Assign variable types in the new table new_types = cellfun ('class', tbl.VariableValues, 'UniformOutput', false); tbl.VariableTypes = new_types; ## Merge custom properties across all inputs: table-scoped properties are ## unioned (the first input wins on a name clash) and variable-scoped ## properties are concatenated across the inputs' variable blocks, NaN- ## filling the block of any input that lacks the property. [cp, cpTypes] = merge_hcat_props (tbl, varargin); tbl.CustomProperties = cp; tbl.CustomPropTypes = cpTypes; endfunction ## -*- texinfo -*- ## @deftypefn {table} {@var{TF} =} iscolumn (@var{tbl}) ## ## Test input table for being a column vector. ## ## @qcode{@var{TF} = iscolumn (@var{tbl})} returns @qcode{true} if the input ## table @var{tbl} has a single variable. The number of columns within that ## variable does not matter. ## ## @end deftypefn function TF = iscolumn (this) TF = width (this) == 1; endfunction ## -*- texinfo -*- ## @deftypefn {table} {@var{TF} =} isempty (@var{tbl}) ## ## Test input table for being empty. ## ## For tables, @code{isempty} is true if the number of rows is 0 or the ## number of variables is 0. ## ## @end deftypefn function TF = isempty (this) TF = prod (size (this)) == 0; endfunction ## -*- texinfo -*- ## @deftypefn {table} {@var{TF} =} ismatrix (@var{tbl}) ## ## Test input table for being a matrix. ## ## For tables, @code{ismatrix} is always true, by definition. ## ## @end deftypefn function TF = ismatrix (this) TF = true; endfunction ## -*- texinfo -*- ## @deftypefn {table} {@var{TF} =} isrow (@var{tbl}) ## ## Test input table for being a row vector. ## ## @qcode{@var{TF} = isrow (@var{tbl})} returns @qcode{true} if the input ## table @var{tbl} has a single row. ## ## @end deftypefn function TF = isrow (this) TF = height (this) == 1; endfunction ## -*- texinfo -*- ## @deftypefn {table} {@var{TF} =} isscalar (@var{tbl}) ## ## Test input table for being a scalar. ## ## @qcode{@var{TF} = isscalar (@var{tbl})} returns @qcode{true} if the input ## table @var{tbl} has a single row and a single variable. ## ## @end deftypefn function TF = isscalar (this) TF = height (this) == 1 && width (this) == 1; endfunction ## -*- texinfo -*- ## @deftypefn {table} {@var{tf} =} istable (@var{tbl}) ## ## Return @qcode{true} if input is a table. ## ## @end deftypefn function TF = istable (this) TF = true; endfunction ## -*- texinfo -*- ## @deftypefn {table} {@var{TF} =} isvector (@var{tbl}) ## ## Test input table for being a vector. ## ## @qcode{@var{TF} = isvector (@var{tbl})} returns @qcode{true} if the input ## table @var{tbl} has a single row or a single column. ## ## @end deftypefn function TF = isvector (this) TF = isrow (this) || iscolumn (this); endfunction ## -*- texinfo -*- ## @deftypefn {table} {@var{out} =} length (@var{tbl}) ## ## Length along longest dimension. ## ## @end deftypefn function out = length (this, varargin) out = max (size (this)); endfunction ## -*- texinfo -*- ## @deftypefn {table} {@var{out} =} ndims (@var{tbl}) ## ## Number of table dimensions. ## ## For tables, @code{ndims (tbl)} is always 2. ## ## @end deftypefn function out = ndims (this) out = 2; endfunction ## -*- texinfo -*- ## @deftypefn {table} {@var{out} =} numel (@var{tbl}) ## ## Total number of elements in table. ## ## @code{@var{out} = numel (@var{tbl})} returns the number of elements in ## the table, @var{tbl}, equivalent to @qcode{prod (size (@var{tbl}))}. A ## table is treated as a two-dimensional container, so this is the number of ## rows times the number of variables. Variables may themselves span ## multiple columns, but @code{numel} only accounts for the number of rows ## and the number of variables, not the underlying columns. ## ## @end deftypefn function out = numel (this, varargin) out = prod (size (this)); endfunction ## -*- texinfo -*- ## @deftypefn {table} {@var{tblB} =} repelem (@var{tblA}, @var{sz}) ## @deftypefnx {table} {@var{tblB} =} repelem (@var{tblA}, @var{rows}, @var{columns}) ## ## Replicate elements of a table. ## ## Replicates elements of the input table @var{tblA} in a similar fashion ## to how @code{repelem} applies to a matrix. Only two dimensions are ## supported for tables. ## ## @end deftypefn function tbl = repelem (this, varargin) ## Check input arguments nargs = numel (varargin); if (nargs < 1) error ("table.repelem: too few input arguments for table input."); endif if (nargs > 2) error ("table.repelem: only 2 dimensions are supported for tables."); endif if (nargs == 1) rows = cols = varargin{1}; else rows = varargin{1}; cols = varargin{2}; endif if (rows < 1 || fix (rows) != rows || ! isnumeric (rows)) if (nargs == 1) error ("table.repelem: SZ must be a positive integer."); else error ("table.repelem: ROWS must be a positive integer."); endif endif if (cols < 1 || fix (cols) != cols || ! isnumeric (cols)) error ("table.repelem: COLUMNS must be a positive integer."); endif tbl = this; ## Replicate elements per rows (apply on each variable) if (rows > 1) for i = 1:width (this) tbl.VariableValues{i} = repelem (this.VariableValues{i}, rows, 1); endfor ## Handle RowNames (if not empty) if (! isempty (this.RowNames)) tbl.RowNames = repelem (this.RowNames, rows, 1); ## Fix row name repetitions for i = 1:rows - 1 vec = i + 1:rows:height (tbl); fcn = eval (["@(x) sprintf (""%s_", sprintf("%d", i), """, x)"]); tbl.RowNames(vec) = cellfun (fcn, tbl.RowNames(vec), ... 'UniformOutput', false); endfor endif endif ## Replicate variables accordingly if (cols > 1) ## Replicate variables tbl.VariableTypes = repelem (tbl.VariableTypes, 1, cols); tbl.VariableValues = repelem (tbl.VariableValues, 1, cols); tbl.VariableDescriptions = repelem (tbl.VariableDescriptions, 1, cols); tbl.VariableUnits = repelem (tbl.VariableUnits, 1, cols); ## Fix variable name repetitions idx = num2cell (1:cols - 1); newNames = {}; for i = 1:width (this) newNames = [newNames, this.VariableNames{i}]; fnc = eval (["@(x) sprintf (""", this.VariableNames{i}, "_%d"", x)"]); addNames = cellfun (fnc, idx, 'UniformOutput', false); newNames = [newNames, addNames]; endfor tbl.VariableNames = newNames; ## Handle custom variable properties if (! isempty (this.CustomProperties)) cp_names = fieldnames (this.CustomProperties); cp_types = this.CustomPropTypes; idx = find (strcmpi (cp_types, "variable")); ## Replicate custom variable properties only if (! isempty (idx)) for i = idx cvp_name = cp_names{i}; tbl.CustomProperties.(cvp_name) = ... repelem (tbl.CustomProperties.(cvp_name), 1, cols); endfor endif endif endif endfunction ## -*- texinfo -*- ## @deftypefn {table} {@var{tblB} =} repmat (@var{tblA}, @var{sz}) ## @deftypefnx {table} {@var{tblB} =} repmat (@var{tblA}, @var{rows}, @var{columns}) ## ## Repeat copies of a table. ## ## Repeats copies of the input table @var{tblA} in a similar fashion ## to how @code{repmat} applies to a matrix. Only two dimensions are ## supported for tables. ## ## @end deftypefn function tbl = repmat (this, varargin) ## Check input arguments nargs = numel (varargin); if (nargs < 1) error ("table.repmat: too few input arguments for table input."); endif if (nargs > 2) error ("table.repmat: only 2 dimensions are supported for tables."); endif if (nargs == 1) rows = cols = varargin{1}; else rows = varargin{1}; cols = varargin{2}; endif if (rows < 1 || fix (rows) != rows || ! isnumeric (rows)) if (nargs == 1) error ("table.repmat: SZ must be a positive integer."); else error ("table.repmat: ROWS must be a positive integer."); endif endif if (cols < 1 || fix (cols) != cols || ! isnumeric (cols)) error ("table.repmat: COLUMNS must be a positive integer."); endif tbl = this; ## Replicate elements per rows (apply on each variable) if (rows > 1) for i = 1:width (this) tbl.VariableValues{i} = repmat (this.VariableValues{i}, rows, 1); endfor ## Handle RowNames (if not empty) if (! isempty (this.RowNames)) tbl.RowNames = repmat (this.RowNames, rows, 1); ## Fix row name repetitions for i = 1:rows - 1 rep = height (this); vec = i * rep + 1:rep * (i + 1); fcn = eval (["@(x) sprintf (""%s_", sprintf("%d", i), """, x)"]); tbl.RowNames(vec) = cellfun (fcn, tbl.RowNames(vec), ... 'UniformOutput', false); endfor endif endif ## Replicate variables accordingly if (cols > 1) ## Replicate variables tbl.VariableTypes = repmat (tbl.VariableTypes, 1, cols); tbl.VariableValues = repmat (tbl.VariableValues, 1, cols); tbl.VariableDescriptions = repmat (tbl.VariableDescriptions, 1, cols); tbl.VariableUnits = repmat (tbl.VariableUnits, 1, cols); ## Fix variable name repetitions newNames = this.VariableNames; for i = 1:cols - 1 fnc = eval (["@(x) sprintf (""%s_", sprintf("%d", i), """, x)"]); addNames = cellfun (fnc, this.VariableNames, 'UniformOutput', false); newNames = [newNames, addNames]; endfor tbl.VariableNames = newNames; ## Handle custom variable properties if (! isempty (this.CustomProperties)) cp_names = fieldnames (this.CustomProperties); cp_types = this.CustomPropTypes; idx = find (strcmpi (cp_types, "variable")); ## Replicate custom variable properties only if (! isempty (idx)) for i = idx cvp_name = cp_names{i}; tbl.CustomProperties.(cvp_name) = ... repmat (tbl.CustomProperties.(cvp_name), 1, cols); endfor endif endif endif endfunction ## -*- texinfo -*- ## @deftypefn {table} {@var{sz} =} size (@var{tbl}) ## @deftypefnx {table} {@var{dim_sz} =} size (@var{tbl}, @var{dim}) ## @deftypefnx {table} {@var{dim_sz} =} size (@var{tbl}, @var{vecdim}) ## @deftypefnx {table} {[@var{rows}, @var{columns}] =} size (@var{tbl}) ## @deftypefnx {table} {[@var{rows}, @var{columns}, @dots{}] =} size (@var{tbl}) ## ## Return the size of a table. ## ## For tables, the size is [number-of-rows x number-of-variables]. ## This is the same as @code{[height(obj), width(obj)]}. ## ## @code{size (@var{tbl}, @var{dim})} returns the size along dimension ## @var{dim}; dimensions greater than 2 have size 1. @var{dim} may be a ## vector @var{vecdim}, in which case a row vector of the corresponding ## sizes is returned. ## ## @end deftypefn function varargout = size (this, dim) sz = [height(this), width(this)]; if (nargin == 2) ## Sizes along the requested dimension(s); dimensions above 2 are 1. dim_sz = ones (1, numel (dim)); valid = dim <= 2; dim_sz(valid) = sz(dim(valid)); if (nargout > 1) varargout = num2cell (dim_sz); else varargout{1} = dim_sz; endif elseif (nargout <= 1) varargout{1} = sz; else varargout{1} = sz(1); varargout{2} = sz(2); [varargout{3:nargout}] = deal (1); endif endfunction ## -*- texinfo -*- ## @deftypefn {table} {@var{tblB} =} squeeze (@var{tblA}) ## ## Remove singleton dimensions. ## ## For tables, this is always a no-op that returns the input table ## unmodified, because tables always have exactly 2 dimensions. ## ## @end deftypefn function tbl = squeeze (this) tbl = this; endfunction ## -*- texinfo -*- ## @deftypefn {table} {@var{tbl} =} vertcat (@var{tbl1}, @var{tbl2}, @dots{}) ## ## Vertical concatenation for tables. ## ## @code{@var{tbl} = vertcat (@var{tbl1}, @var{tbl2}, @dots{})} merges ## tables by vertically concatenating them, provided that all input tables ## have the same variable names but not necessarily in the same order. The ## positions of the variable names are matched to those of the first input ## table. ## ## When any input table has row names, they must be unique across all input ## tables. In such a case, rows coming from input tables without row names ## are assigned default @qcode{Row@var{N}} names, where @var{N} is the row's ## position in the output table. Output table's @qcode{Description} and ## @qcode{UserData} properties are assigned using the first non-empty value. ## ## @end deftypefn function tbl = vertcat (varargin) ## All inputs must be tables are_tables = cellfun (@istable, varargin); if (! all (are_tables)) error ("table.vertcat: all inputs must be tables."); endif ## All tables must have the same variable names varNames = cellfun (@(obj) obj.VariableNames, varargin, ... 'UniformOutput', false); is_empty = cellfun (@isempty, varNames); sortedVarNames = cellfun (@sort, varNames(! is_empty), ... 'UniformOutput', false); if (! isequal (sortedVarNames{:})) error (strcat ("table.vertcat: input tables must have identical", ... " variable names.")); endif ## All tables must have the same columns (width) numCols = cellfun (@width, varargin); if (numel (unique (numCols)) != 1) error ("table.vertcat: all input tables must have the same width."); endif numCols = numCols(1); ## We need to figure out some indexing for the variables of every other ## table so we can re-index to the variables of the first table. index = [1:numCols]; # first table is reindexed to itself for i = 2:numel (varNames) fcn = @(x) find (ismember (varNames{1}, x)); index(i,:) = cellfun (fcn, varNames{i}); endfor ## Check for RowNames has_RowNames = ! cellfun (@(obj) isempty (obj.RowNames), varargin); ## Check that all RowNames are unique across tables rowNames = cellfun (@(obj) obj.RowNames, varargin(has_RowNames), ... 'UniformOutput', false); rowNames = [rowNames{:}]; if (numel (rowNames) != numel (unique (rowNames))) error (strcat ("table.vertcat: all input tables must have unique", ... " row names.")); endif ## Start vertical concatenation if (! any (has_RowNames)) # no RowNames in any table (easy) tbl = varargin{1}; for i = 2:numel (varargin) in = varargin{i}; ixVars = index(i,:); in = subsetvars (in, ixVars); for v = 1:numCols tbl.VariableValues{v} = [tbl.VariableValues{v}; ... in.VariableValues{v}]; endfor if (isempty (tbl.VariableDescriptions)) tbl.VariableDescriptions = in.VariableDescriptions; endif if (isempty (tbl.VariableUnits)) tbl.VariableUnits = in.VariableUnits; endif if (isempty (tbl.Description)) tbl.Description = in.Description; endif if (isempty (tbl.UserData)) tbl.UserData = in.UserData; endif ## Rows stack over identical variables, so the first table's custom ## properties already describe the result; adopt a later table's only ## when the first table has none. if (isempty (tbl.CustomProperties) && ! isempty (in.CustomProperties)) tbl.CustomProperties = in.CustomProperties; tbl.CustomPropTypes = in.CustomPropTypes; endif endfor else # at least one input table has RowNames ## Input tables without row names get default 'Row' names, where N ## is the row's position in the output table (MATLAB-compatible). tbl = varargin{1}; fcn = @(x) {sprintf("Row%d", x)}; ## If first input table does not have row names, add them here if (isempty (tbl.RowNames)) tbl.RowNames = arrayfun (fcn, 1:height (tbl))'; endif pos = height (tbl); for i = 2:numel (varargin) in = varargin{i}; ixVars = index(i,:); in = subsetvars (in, ixVars); for v = 1:numCols tbl.VariableValues{v} = [tbl.VariableValues{v}; ... in.VariableValues{v}]; endfor ## Handle row names here if (isempty (in.RowNames)) in.RowNames = arrayfun (fcn, pos + (1:height (in)))'; endif tbl.RowNames = [tbl.RowNames; in.RowNames]; pos += height (in); ## Handle remaining stuff if (isempty (tbl.VariableDescriptions)) tbl.VariableDescriptions = in.VariableDescriptions; endif if (isempty (tbl.VariableUnits)) tbl.VariableUnits = in.VariableUnits; endif if (isempty (tbl.Description)) tbl.Description = in.Description; endif if (isempty (tbl.UserData)) tbl.UserData = in.UserData; endif ## As above: adopt a later table's custom properties only when the ## first table has none (rows stack over identical variables). if (isempty (tbl.CustomProperties) && ! isempty (in.CustomProperties)) tbl.CustomProperties = in.CustomProperties; tbl.CustomPropTypes = in.CustomPropTypes; endif endfor endif endfunction endmethods ################################################################################ ## ** Forbidden Methods ** ## ################################################################################ ## Available Methods ## ## ## ## 'repelems' 'reshape' 'resize' 'shiftdims' ## ## 'vec' ## ## ## ################################################################################ methods (Hidden) function out = repelems (this, varargin) error ("Function 'repelems' is not supported for tables"); endfunction function out = reshape (this, varargin) error ("Function 'reshape' is not supported for tables"); endfunction function out = resize (this, varargin) error ("Function 'resize' is not supported for tables"); endfunction function out = shiftdims (this, varargin) error ("Function 'shiftdims' is not supported for tables"); endfunction function out = vec (this, varargin) error ("Function 'vec' is not supported for tables"); endfunction endmethods ################################################################################ ## ** Reference and Assignment Operations ** ## ################################################################################ ## Available Methods ## ## ## ## 'end' 'subsref' 'subsasgn' ## ## ## ################################################################################ methods (Hidden) ## Overload 'end' keyword function last_index = end (this, end_dim, ndim_obj) lastdim = ndims (this); if (end_dim == ndim_obj && ndim_obj == 1) last_index = prod (size (this)); elseif (end_dim == ndim_obj && end_dim < lastdim) last_index = prod (size (this)(end_dim:lastdim)); else last_index = size (this, end_dim); endif endfunction ## Class specific subscripted reference function varargout = subsref (this, s) chain_s = s(2:end); s = s(1); switch (s.type) case '()' if (numel (s.subs) != 2) error (strcat ("table.subsref: '()' indexing of table", ... " requires exactly two arguments.")); endif [ixRow, ixVar] = resolveRowVarRefs (this, s.subs{1}, s.subs{2}); tbl = this; tbl = subsetrows (tbl, ixRow); tbl = subsetvars (tbl, ixVar); case '{}' if (numel (s.subs) != 2) error (strcat ("table.subsref: '{}' indexing of table", ... " requires exactly two arguments.")); endif [ixRow, ixVar] = resolveRowVarRefs (this, s.subs{1}, s.subs{2}); tbl = this; tbl = subsetrows (tbl, ixRow); tbl = subsetvars (tbl, ixVar); pair = mixed_cell_pair (tbl.VariableValues); if (! isempty (pair)) error (strcat ("table.subsref: cannot concatenate the table", ... " variables '%s' and '%s', because their types", ... " are %s and %s."), tbl.VariableNames{pair(1)}, ... tbl.VariableNames{pair(2)}, ... class (tbl.VariableValues{pair(1)}), ... class (tbl.VariableValues{pair(2)})); endif try tbl = table2array (tbl); catch error (strcat ("table.subsref: table cannot be concatenated", ... " into a matrix")); end_try_catch case '.' if (! ischar (s.subs)) error (strcat ("table.subsref: '.' index argument must be a", ... " character vector.")); endif ## Handle special cases: "Properties" and "DimensionNames" if (isequal (s.subs, 'Properties')) if (nargout == 0 && isempty (chain_s)) print_properties (this); return; else tbl = getProperties (this); endif elseif (isequal (s.subs, this.DimensionNames{1})) tbl = this.RowNames; elseif (isequal (s.subs, this.DimensionNames{2})) try tbl = table2array (this); catch tbl = table2cell (this); end_try_catch ## Everything else is indexing an existing variable name else tbl = getvar (this, s.subs); endif endswitch ## Chained references if (! isempty (chain_s)) tbl = subsref (tbl, chain_s); endif varargout{1} = tbl; endfunction ## Class specific subscripted assignment function tbl = subsasgn (this, s, val) ## Chained subscripts chain_s = s(2:end); s = s(1); if (! isempty (chain_s) && ! isequal (s.subs, 'Properties')) rhs_in = single_subref (this, s); rhs = subsasgn (rhs_in, chain_s, val); else rhs = val; endif tbl = this; switch (s.type) case '()' if (numel (s.subs) != 2) error (strcat ("table.subsasgn: '()' indexing of table", ... " requires exactly two arguments.")); endif [ixRow, ixVar] = resolveRowVarRefs (this, s.subs{1}, s.subs{2}); ## Check input data matches referenced elements if (! isequal (size (rhs), [numel(ixRow), numel(ixVar)])) error ("table.subsasgn: input data mismatch indexed dimensions."); endif ## Handle different cases of input data if (isa (rhs, 'table')) # MATLAB compatible rhs = table2cell (rhs); endif if (isa (rhs, 'cell')) # MATLAB compatible for i = 1:numel (ixVar) varData = this.VariableValues{ixVar(i)}; col = rhs(:,i); try if (iscell (varData)) varData(ixRow) = col; else varData(ixRow) = vertcat (col{:}); endif catch error (strcat ("table.subsasgn: input data type mismatch", ... " indexed variable type.")); end_try_catch tbl.VariableValues{ixVar(i)} = varData; endfor else # Octave specific for i = 1:numel (ixVar) varData = this.VariableValues{ixVar(i)}; try varData(ixRow) = rhs(:,i); catch error (strcat ("table.subsasgn: input data type mismatch", ... " indexed variable type.")); end_try_catch tbl.VariableValues{ixVar(i)} = varData; endfor endif ## {} not used in Octave for assigning values case '{}' error (strcat ("table.subsasgn: '{}' invalid indexing for", ... " assigning values. Use '()' instead.")); case '.' if (! ischar (s.subs)) error (strcat ("table.subsasgn: '.' index argument must be a", ... " character vector.")); endif ## Grab Properties if (isequal (s.subs, 'Properties')) ## no further recursion, everything is handled here if (isempty (chain_s)) error ("table.subsasgn: cannot assign new properties."); endif s = chain_s(1); ## Handle table properties if (isequal (s.subs, 'Description')) ## Check for valid input: character vector of string if (isa (val, 'string')) if (numel (val) > 1) error (strcat ("table.subsasgn: Table description must", ... " be a character vector or a string", ... " scalar.")); endif val = cellstr (val){1}; endif if (! ischar (val)) error (strcat ("table.subsasgn: Table description must", ... " be a character vector or string scalar.")); endif this.Description = val; tbl = this; elseif (isequal (s.subs, 'UserData')) ## Any kind !! this.UserData = val; tbl = this; elseif (isequal (s.subs, 'DimensionNames')) ## Check for further indexing of specific variable(s) if (numel (chain_s) > 1) idx = chain_s(2).subs; if (numel (idx) > 1) error (strcat ("table.subsasgn: cannot index", ... " DimensionNames with more than one", ... " dimension. Use a vector to index", ... " multiple DimensionNames at once.")); endif idx = cell2mat (idx); if (isequal (idx, ':')) idx = [1:2]; endif if (! all (ismember (idx, [1:2]))) error (strcat ("table.subsasgn: out of bound index for", ... " DimensionNames.")); endif if (ischar (val) || isa (val, 'string')) val = cellstr (val); endif if (! (iscellstr (val) && numel (val) == numel (idx))) error (strcat ("table.subsasgn: DimensionNames must be", ... " a cell array of character vectors or", ... " a string array matching the number of", ... " indexed variables.")); endif this.DimensionNames(idx) = val; tbl = this; return endif ## Check for valid input: two-element cellstring or string array if (ischar (val) || isa (val, 'string')) val = cellstr (val); endif if (! (iscellstr (val) && numel (val) == 2)) error (strcat ("table.subsasgn: DimensionNames must be a", ... " two-element cell array of character", ... " vectors or string array.")); endif this.DimensionNames = val; tbl = this; elseif (isequal (s.subs, 'VariableNames')) ## Check for further indexing of specific variable(s) if (numel (chain_s) > 1) idx = chain_s(2).subs; if (numel (idx) > 1) error (strcat ("table.subsasgn: cannot index", ... " VariableNames with more than one", ... " dimension. Use a vector to index", ... " multiple VariableNames at once.")); endif idx = cell2mat (idx); if (isequal (idx, ':')) idx = [1:width(this)]; endif if (! all (ismember (idx, [1:width(this)]))) error (strcat ("table.subsasgn: out of bound index for", ... " VariableNames.")); endif if (ischar (val) || isa (val, 'string')) val = cellstr (val); endif if (! (iscellstr (val) && numel (val) == numel (idx))) error (strcat ("table.subsasgn: VariableNames must be", ... " a cell array of character vectors or", ... " a string array matching the number of", ... " indexed variables.")); endif this.VariableNames(idx) = val; tbl = this; return endif ## Check for valid input: cellstring or string array matching ## the number of variables in the table if (ischar (val) || isa (val, 'string')) val = cellstr (val); endif if (! (iscellstr (val) && numel (val) == width (this))) error (strcat ("table.subsasgn: VariableNames must be a", ... " cell array of character vectors or a", ... " string array matching the number of", ... " variables.")); endif this.VariableNames = val; tbl = this; elseif (isequal (s.subs, 'VariableTypes')) ## Check for further indexing of specific variable(s) if (numel (chain_s) > 1) idx = chain_s(2).subs; if (numel (idx) > 1) error (strcat ("table.subsasgn: cannot index", ... " VariableTypes with more than one", ... " dimension. Use a vector to index", ... " multiple VariableTypes at once.")); endif idx = cell2mat (idx); if (isequal (idx, ':')) idx = [1:width(this)]; endif if (! all (ismember (idx, [1:width(this)]))) error (strcat ("table.subsasgn: out of bound index for", ... " VariableTypes")); endif if (ischar (val) || isa (val, 'string')) val = cellstr (val); endif if (! (iscellstr (val) && numel (val) == numel (idx))) error (strcat ("table.subsasgn: VariableTypes must be", ... " a cell array of character vectors or", ... " a string array matching the number of", ... " indexed variables.")); endif ## Convert each selected variable to its new data type; ## convertvars updates both the data and the VariableTypes ## entry for the corresponding variable. tbl = this; for k = 1:numel (idx) tbl = convertvars (tbl, idx(k), val{k}); endfor return endif ## Check for valid input: cellstring or string array matching ## the number of variables in the table if (ischar (val) || isa (val, 'string')) val = cellstr (val); endif if (! (iscellstr (val) && numel (val) == width (this))) error (strcat ("table.subsasgn: VariableTypes must be a", ... " cell array of character vectors or a", ... " string array matching the number of", ... " variables.")); endif ## Convert each variable to its new data type; convertvars ## updates both the data and the VariableTypes entry for the ## corresponding variable. tbl = this; for k = 1:width (this) tbl = convertvars (tbl, k, val{k}); endfor elseif (isequal (s.subs, 'VariableDescriptions')) ## Check for further indexing of specific variable(s) if (numel (chain_s) > 1) idx = chain_s(2).subs; if (numel (idx) > 1) error (strcat ("table.subsasgn: cannot index", ... " VariableDescriptions with more than", ... " one dimension. Use a vector to index", ... " multiple VariableDescriptions at", ... " once.")); endif idx = cell2mat (idx); if (isequal (idx, ':')) idx = [1:width(this)]; endif if (! all (ismember (idx, [1:width(this)]))) error (strcat ("table.subsasgn: out of bound index for", ... " VariableDescriptions")); endif if (ischar (val) || isa (val, 'string')) val = cellstr (val); endif if (! (iscellstr (val) && numel (val) == numel (idx))) error (strcat ("table.subsasgn: VariableDescriptions", ... " must be a cell array of character", ... " vectors or a string array matching", ... " the number of indexed variables.")); endif this.VariableDescriptions(idx) = val; tbl = this; return endif ## Check for valid input: cellstring or string array matching ## the number of variables in the table if (ischar (val) || isa (val, 'string')) val = cellstr (val); endif if (! (iscellstr (val) && numel (val) == width (this))) error (strcat ("table.subsasgn: VariableDescriptions", ... " must be a cell array of character", ... " vectors or a string array matching the", ... " number of variables.")); endif this.VariableDescriptions = val; tbl = this; elseif (isequal (s.subs, 'VariableUnits')) ## Check for further indexing of specific variable(s) if (numel (chain_s) > 1) idx = chain_s(2).subs; if (numel (idx) > 1) error (strcat ("table.subsasgn: cannot index", ... " VariableUnits with more than one", ... " dimension. Use a vector to index", ... " multiple VariableUnits at once.")); endif idx = cell2mat (idx); if (isequal (idx, ':')) idx = [1:width(this)]; endif if (! all (ismember (idx, [1:width(this)]))) error (strcat ("table.subsasgn: out of bound index for", ... " VariableUnits.")); endif if (ischar (val) || isa (val, 'string')) val = cellstr (val); endif if (! (iscellstr (val) && numel (val) == numel (idx))) error (strcat ("table.subsasgn: VariableUnits must be", ... " a cell array of character vectors or", ... " a string array matching the number of", ... " indexed variables.")); endif this.VariableUnits(idx) = val; tbl = this; return endif ## Check for valid input: cellstring or string array matching ## the number of variables in the table if (ischar (val) || isa (val, 'string')) val = cellstr (val); endif if (! (iscellstr (val) && numel (val) == width (this))) error (strcat ("table.subsasgn: VariableUnits must be a", ... " cell array of character vectors or a", ... " string array matching the number of", ... " variables.")); endif this.VariableUnits = val; tbl = this; elseif (isequal (s.subs, 'RowNames')) ## Check for empty input to remove RowNames from table. if (isempty (val)) this.RowNames = {}; tbl = this; return; endif ## Check for valid input: cellstring scalar, char row vector,or ## string scalar matching an existing VariableName of appropriate ## type, or a numeric scalar referencing an existing VariableName ## of appropriate type. if ((ischar (val) && size (val, 1) == 1) || ((iscellstr (val) || isa (val, 'string') || isnumeric (val)) && numel (val) == 1)) if (ischar (val) || isa (val, 'string')) val = cellstr (val); endif [ixVar, ~] = resolveVarRef (this, val, 'lenient'); ## ## If variable name exists check for appropriate varTypes if (ixVar != 0) selvar = this.VariableValues{ixVar}; if (iscellstr (selvar) || ischar (selvar) || isa (selvar, 'string')) if (ischar (selvar) || isa (selvar, 'string')) selvar = cellstr (selvar); endif ## RowNames must be unique, just as for the array-form ## assignment and the constructor. The referenced ## variable may contain duplicates, so guard here. if (numel (__unique__ (selvar)) != numel (selvar)) error (strcat ("table.subsasgn: elements in", ... " 'RowNames' must be unique.")); endif ## When RowNames as set this way, the referenced Variable ## is removed. Octave specific behavior. MATLAB does not ## support this feature. this.RowNames = selvar; tbl = removevars (this, ixVar); return endif endif endif ## Check for valid input: cellstring, char, or string array ## with as many distinct elements as the table has rows. if (ischar (val) || isa (val, 'string')) val = cellstr (val); endif if (! iscellstr (val) || numel (val) != height (this)) error (strcat ("table.subsasgn: the number of 'RowNames'", ... " must equal the number of rows.")); elseif (numel (__unique__ (val)) != numel (val)) error (strcat ("table.subsasgn: elements in 'RowNames'", ... " must be unique.")); endif this.RowNames = val(:); tbl = this; elseif (isequal (s.subs, 'CustomProperties')) ## Check that a custom property name is indexed if (numel (chain_s) < 2) if (isempty (val)) error (strcat ("table.subsasgn: use 'rmprop' to remove", ... " an existing custom property.")); else error (strcat ("table.subsasgn: use 'addprop' to add a", ... " new custom property.")); endif endif ## Check for valid indexing a custom property if (! strcmp (chain_s(2).type, '.')) error (strcat ("table.subsasgn: use '.' notation to", ... " index a custom property.")); endif cpName = chain_s(2).subs; if (! ischar (cpName)) error (strcat ("table.subsasgn: indexing a custom", ... " property requires a character vector.")); endif ## Check that referenced custom property exists if (isempty (this.CustomProperties)) error (strcat ("table.subsasgn: custom property '%s'", ... " does not exist, use 'addprop' to add", ... " it."), ... cpName); endif existingNames = fieldnames (this.CustomProperties); if (! ismember (cpName, existingNames)) error (strcat ("table.subsasgn: custom property '%s'", ... " does not exist, use 'addprop' to add", ... " it."), ... cpName); endif ## Get type of custom property cpType = this.CustomPropTypes{strcmp (cpName, existingNames)}; if (strcmp (cpType, 'table')) if (! ischar (val) && numel (val) > 1) error (strcat ("table.subsasgn: custom property '%s'", ... " is a table property and only a scalar", ... " value can be assigned to it."), ... cpName); endif if (numel (chain_s) > 2) error (strcat ("table.subsasgn: custom property '%s'", ... " is a scalar table property and cannot", ... " be indexed any further."), ... cpName); endif this.CustomProperties.(cpName) = val; else maxIdx = width (this); ## Check input is a vector if (! isvector (val)) error (strcat ("table.subsasgn: assigned value to a", ... " custom variable property must be a", ... " vector.")); endif ## Get further indexing (if available) if (numel (chain_s) > 2) if (strcmp (chain_s(3).type, '.')) error (strcat ("table.subsasgn: custom property '%s'", ... " is a variable property and cannot", ... " be indexed any further with '.'", ... " notation."), ... cpName); endif cpIdx = chain_s(3).subs; if (numel (cpIdx) > 1) error (strcat ("table.subsasgn: cannot index a", ... " custom variable property in more", ... " than one dimension.")); endif cpIdx = cell2mat (cpIdx); if (isequal (cpIdx, ':')) cpIdx = [1:maxIdx]; endif if (! all (ismember (cpIdx, [1:maxIdx]))) error (strcat ("table.subsasgn: out of bound index", ... " for custom variable property '%s'."), ... cpName); endif if (numel (val) != numel (cpIdx)) error (strcat ("table.subsasgn: input vector does", ... " not match the number of indexed", ... " variables in the custom variable", ... " property '%s'."), ... cpName); endif this.CustomProperties.(cpName)(cpIdx) = val; else ## Check that input vector matches the number of variables if (numel (val) != maxIdx) error (strcat ("table.subsasgn: input vector does", ... " not match the number of variables", ... " in table.")); endif this.CustomProperties.(cpName) = val; endif endif tbl = this; endif else ## Everything else is indexing a variable name (existing of new) tbl = setvar (this, s.subs, rhs); endif endswitch endfunction endmethods ## Private methods for accessing Tables and Properties methods (Access = private) ## Resolve variable references to indices and variable names. ## Returns: ## @var{ixVar} - numeric indices of the variables in @var{tbl} ## @var{varNames} - a cellstr of the names of the indexed variables ## ## Raises an error if any of the specified variables could not be resolved, ## unless strictness is 'lenient', in which case it will return 0 for the ## index and '' for the name for each variable which could not be resolved. function [ixVar, varNames] = resolveVarRef (this, varRef, strictness) if (nargin < 3 || isempty (strictness)) strictness = 'strict'; endif if (! isvector (varRef)) error ("table: variable index must be a vector."); endif nvars = width (this); if (islogical (varRef)) vec = numel (varRef); if (nvars != vec) error ("table: variable logical index does not match table width."); endif ixVar = 1:nvars; ixVar(! varRef) = []; elseif (isnumeric (varRef)) ixVar = varRef; ix_bad = find (ixVar > nvars | ixVar < 1); if (! isempty (ix_bad)) error (strcat ("table: variable index out of bounds: requested", ... " index %d; table has %d variables."), ... ixVar(ix_bad(1)), nvars); endif elseif (ischar (varRef) && isequal (varRef, ':')) ixVar = 1:nvars; elseif (ischar (varRef) || iscellstr (varRef) || isa (varRef, 'string')) varRef = cellstr (varRef); [tf, ixVar] = ismember (varRef, this.VariableNames); if (isequal (strictness, 'strict')) if (! all (tf)) if (sum (! tf) == 1) error ("table: no such variable in table: '%s'.", varRef{! tf}); else missing_vars = sprintf ("'%s', ", varRef{! tf}); missing_vars(end-1:end) = []; error ("table: no such variables in table: %s.", missing_vars); endif endif else ixVar(! tf) = 0; endif elseif (isa (varRef, 'vartype')) ixVar = []; for i = 1:nvars if (varRef.varMatch (this.VariableValues{i})) ixVar(end+1) = i; endif endfor elseif (is_function_handle (varRef)) ixVar = []; for i = 1:nvars if (varRef (this.VariableValues{i})) ixVar(end+1) = i; endif endfor else error ("table: unsupported variable indexing operand type: '%s'.", ... class (varRef)); endif if (nargout > 1) varNames = repmat ({''}, size (ixVar)); varNames(ixVar != 0) = this.VariableNames(ixVar(ixVar != 0)); endif endfunction ## Resolve both row and variable references to indices. function [ixRow, ixVar] = resolveRowVarRefs (this, rowRef, varRef) if (isnumeric (rowRef) || islogical (rowRef)) ixRow = rowRef; elseif (isequal (rowRef, ':')) ixRow = 1:height (this); elseif (ischar (rowRef) || iscellstr (rowRef) || isa (rowRef, 'string')) rowRef = cellstr (rowRef); if (isempty (this.RowNames)) error ("table: this table has no RowNames."); endif [tf, ixRow] = ismember (rowRef, this.RowNames); if (! all (tf)) error ("table: no such named row in table: '%s'.", ... strjoin (rowRef(! tf), ", ")); endif else error ("table: unsupported row indexing operand type: '%s'.", ... class (rowRef)); endif ixVar = resolveVarRef (this, varRef); endfunction ## Return a subset of rows defined by the numerical or logical vector ixRows function tbl = subsetrows (this, ixRows) tbl = this; s = struct ('type', '()', 'subs', {{ixRows,':'}}); for i = 1:width (this) tbl.VariableValues{i} = subsref (tbl.VariableValues{i}, s); endfor if (! isempty (this.RowNames)) tbl.RowNames = tbl.RowNames(ixRows); endif endfunction ## Build consistent numeric row proxies for two tables sharing the same set ## of variable names, so that equal rows (compared by variable value, in the ## variable order of TBLA) map to equal proxy rows. Returns an errmsg body ## (empty on success) emitted by the caller under its own name. function [proxyA, proxyB, errmsg] = rowProxies (tblA, tblB) proxyA = []; proxyB = []; errmsg = ''; if (width (tblA) != width (tblB) || ! isempty (setdiff (tblA.VariableNames, tblB.VariableNames))) errmsg = "the two tables must have the same variable names."; return; endif for ix = 1:width (tblA) jx = find (strcmp (tblA.VariableNames{ix}, tblB.VariableNames), 1); [pa, pb, e] = key_col_proxy (tblA.VariableValues{ix}, ... tblB.VariableValues{jx}); if (! isempty (e)) errmsg = e; return; endif proxyA = [proxyA, pa]; proxyB = [proxyB, pb]; endfor endfunction ## Build one side of an outer join from a row-index vector IDX (zeros mark ## rows with no match, filled with missing values). Returns an errmsg body ## (empty on success) emitted by the caller under its own name. function [out, errmsg] = joinBuildSide (this, idx) out = this; errmsg = ''; nout = numel (idx); pos = (idx > 0); for j = 1:width (this) p = this.VariableValues{j}; if (any (pos)) src = idx; src(! pos) = idx(find (pos, 1)); col = p(src, :); [col, errmsg] = set_var_missing (col, ! pos); else [col, errmsg] = missing_rows (p, nout); endif if (! isempty (errmsg)) return; endif out.VariableValues{j} = col; endfor out.RowNames = {}; endfunction ## Merge the custom properties of a set of horizontally-combined tables ## (a cell array TABLES whose variables are concatenated in order). Table- ## scoped properties are unioned with the first table winning on a name ## clash; variable-scoped properties are concatenated across the tables' ## variable blocks, filling the block of any table lacking the property with ## NaN (numeric) or an empty cell. Table-scoped properties are listed before ## variable-scoped ones, matching MATLAB. function [cp, cpTypes] = merge_hcat_props (this, tables) widths = cellfun (@width, tables); cp = struct (); cpTypes = {}; ## Pass 1: table-scoped properties (union, first table wins). for t = 1:numel (tables) T = tables{t}; if (isempty (T.CustomProperties)) continue; endif nm = fieldnames (T.CustomProperties); for i = 1:numel (nm) if (strcmp (T.CustomPropTypes{i}, 'table') && ! isfield (cp, nm{i})) cp.(nm{i}) = T.CustomProperties.(nm{i}); cpTypes{end+1} = 'table'; endif endfor endfor ## Pass 2: variable-scoped properties (union of names; per-table blocks ## concatenated, missing blocks filled to match the property's variables). seen = {}; for t = 1:numel (tables) T = tables{t}; if (isempty (T.CustomProperties)) continue; endif nm = fieldnames (T.CustomProperties); for i = 1:numel (nm) if (! strcmp (T.CustomPropTypes{i}, 'variable') ... || any (strcmp (nm{i}, seen))) continue; endif seen{end+1} = nm{i}; proto = T.CustomProperties.(nm{i}); vec = []; for tt = 1:numel (tables) Tt = tables{tt}; if (! isempty (Tt.CustomProperties) ... && isfield (Tt.CustomProperties, nm{i})) blk = reshape (Tt.CustomProperties.(nm{i}), 1, []); elseif (iscell (proto)) blk = cell (1, widths(tt)); else blk = NaN (1, widths(tt)); endif vec = [vec, blk]; endfor cp.(nm{i}) = vec; cpTypes{end+1} = 'variable'; endfor endfor if (isempty (fieldnames (cp))) cp = []; cpTypes = {}; endif endfunction ## Return a subset of variables defined by the numerical vector ixVars function tbl = subsetvars (this, ixVars) tbl = this; ## Copy selected variables tbl.VariableTypes = this.VariableTypes(ixVars); tbl.VariableNames = this.VariableNames(ixVars); tbl.VariableValues = this.VariableValues(ixVars); tbl.VariableDescriptions = this.VariableDescriptions(ixVars); tbl.VariableUnits = this.VariableUnits(ixVars); ## Check for custom variable properties if (! isempty (this.CustomProperties)) cpIdx = strcmp (this.CustomPropTypes, "variable"); if (any (cpIdx)) ## Get the fieldnames of custom variable properties cpNames = fieldnames (this.CustomProperties); cpNames = cpNames(cpIdx); ## Copy custom variable properties from selected variables for i = 1:numel (cpNames) tmp = this.CustomProperties.(cpNames{i}); if (isempty (tmp)) tbl.CustomProperties.(cpNames{i}) = tmp; else tbl.CustomProperties.(cpNames{i}) = tmp(ixVars); endif endfor endif endif endfunction ## Get table properties as a struct for internal use called by subsasgn function out = getProperties (this) out = struct; out.Description = this.Description; out.UserData = this.UserData; out.DimensionNames = this.DimensionNames; out.VariableTypes = this.VariableTypes; out.VariableNames = this.VariableNames; out.VariableDescriptions = this.VariableDescriptions; out.VariableUnits = this.VariableUnits; out.VariableValues = this.VariableValues; out.RowNames = this.RowNames; out.CustomProperties = this.CustomProperties; endfunction ## Get values from a single referenced variable function out = getvar (this, var_ref) [ix_var, ~] = resolveVarRef (this, var_ref); out = this.VariableValues{ix_var}; endfunction ## -*- texinfo -*- ## @deftypefn {table} {@var{out} =} setvar (@var{tbl}, @var{varRef}, @var{value}) ## ## Set values to an existing or a new variable in table. ## ## This sets (adds or replaces) the value for a variable in @var{tbl}. It ## may be used to change the value of an existing variable, or add a new ## variable. ## ## @var{varRef} is a variable reference, either its index or its name. ## If you are adding a new variable, it must be a name, and not an index. ## ## @var{value} is the value to set the variable to. If it is a scalar, it ## is scalar-expanded to match the number of rows in @var{tbl}. ## ## @end deftypefn function tbl = setvar (this, varRef, value) ## Do scalar expansion if necessary n_rows = height (this); val_is_scalar = (isscalar (value) || (ischar (value) && ... (size (value, 1) == 1 || isequal (size (value), [0 0])))); if (n_rows != 1 && (isscalar (value) || (ischar (value) && (size (value, 1) == 1 || isequal (size (value), [0 0]))))) if (ischar (value)) value = {value}; endif value = repmat (value, [n_rows, 1]); endif ## Check input matches table height if (size (value, 1) != n_rows) error ("table.subsasgn: input value and table height mismatch."); endif ## Resolve variable index ixVar = resolveVarRef (this, varRef, 'lenient'); tbl = this; if (ixVar == 0) ## Add new variable ix_new_var = width (this) + 1; tbl.VariableNames{ix_new_var} = varRef; tbl.VariableTypes{ix_new_var} = class (value); tbl.VariableValues{ix_new_var} = value; tbl.VariableDescriptions{ix_new_var} = ""; tbl.VariableUnits{ix_new_var} = ""; ## Check for custom variable properties if (! isempty (this.CustomProperties)) cpIdx = strcmp (this.CustomPropTypes, "variable"); if (any (cpIdx)) ## Get the fieldnames of custom variable properties cpNames = fieldnames (this.CustomProperties); cpNames = cpNames(cpIdx); ## Add default values to custom variable properties for new variable for i = 1:numel (cpNames) tmp = this.CustomProperties.(cpNames{i}); if (! isempty (tmp)) if (isnumeric (tmp)) tmp(end+1) = NaN; elseif (islogical (tmp)) tmp(end+1) = false; elseif (isa (tmp, 'string')) tmp(end+1) = string (NaN); elseif (iscell (tmp)) tmp{end+1} = []; endif tbl.CustomProperties.(cpNames{i}) = tmp; endif endfor endif endif else ## Set existing variable tbl.VariableTypes{ixVar} = class (value); tbl.VariableValues{ixVar} = value; endif endfunction ## Resolve subscripted reference for internal use called by subsasgn function out = single_subref (this, s) switch s.type case '()' if (numel (s.subs) != 2) error (strcat ("table.subsasgn: ()-indexing of table", ... " requires exactly two arguments.")); endif [ixRow, ixVar] = resolveRowVarRefs (this, s.subs{1}, s.subs{2}); out = this; out = subsetrows (out, ixRow); out = subsetvars (out, ixVar); case '.' if (! ischar (s.subs)) error (strcat ("table.subsasgn: .-index argument must be a", ... " character vector.")); endif ## Handle special cases: "Properties" and "DimensionNames" if isequal (s.subs, 'Properties') out = getProperties (this); elseif isequal (s.subs, this.DimensionNames{1}) out = this.RowNames; elseif isequal (s.subs, this.DimensionNames{2}) out = this.VariableNames; ## Everything else is indexing an existing variable name else out = getvar (this, s.subs); endif endswitch endfunction endmethods ## Private methods for displaying (printing) Tables and Properties methods (Access = private) ## Print Table Properties function print_properties (this) ## Gather info D = this.Description; if (isempty (this.UserData)) UD = "[]"; else sz = size (this.UserData); strs = cell (sz); for i = 1:numel (strs) strs{i} = sprintf ("%d", sz(i)); endfor UD = strjoin (strs, "-by-"); UD = [UD, " of type ", class(this.UserData)]; if (iscellstr (this.UserData)) UD = strrep (UD, 'cell', 'cellstr'); endif endif DN = sprintf ("{'%s' '%s'}", this.DimensionNames{:}); VN = strtrim (sprintf ("'%s' ", this.VariableNames{:})); VN = ["{", VN, "}"]; if (all (cellfun (@isempty, this.VariableDescriptions))) VD = "{}"; else VD = strtrim (sprintf ("'%s' ", this.VariableDescriptions{:})); VD = ["{", VD, "}"]; endif if (all (cellfun (@isempty, this.VariableUnits))) VU = "{}"; else VU = strtrim (sprintf ("'%s' ", this.VariableUnits{:})); VU = ["{", VU, "}"]; endif VC = "[]"; if (isempty (this.RowNames)) RN = "{}"; else RN = sprintf ("{%dx%d cell}", height (this), width (this)); endif if (isempty (this.CustomProperties)) CP = [" CustomProperties: No custom properties are set.\n", ... " Use 'addprop' and 'rmprop' methods to modify", ... " CustomProperties."]; else CP = ["\n Custom Properties (access using t.Properties.", ... "CustomProperties.):"]; cpNames = fieldnames (this.CustomProperties); for i = 1:numel (cpNames) cpValue = this.CustomProperties.(cpNames{i}); if (isempty (cpValue)) CP = [CP, sprintf("\n%+24s: []", cpNames{i})]; elseif (islogical (cpValue) || isnumeric (cpValue)) strValue = strtrim (disp (this.CustomProperties.(cpNames{i}))); CP = [CP, sprintf("\n%+24s: [%s]", cpNames{i}, strValue(1:end))]; elseif (iscellstr (cpValue)) strValue = strtrim (sprintf ("'%s' ", cpValue{:})); CP = [CP, sprintf("\n%+24s: {%s}", cpNames{i}, strValue)]; elseif (isa (cpValue, 'string')) cpValue = cellstr (cpValue); strValue = strtrim (sprintf ("""%s"" ", cpValue{:})); CP = [CP, sprintf("\n%+24s: [%s]", cpNames{i}, strValue)]; elseif (ischar (cpValue)) CP = [CP, sprintf("\n%+24s: '%s'", cpNames{i}, cpValue)]; elseif (iscell (cpValue)) strValue = ''; for idx = 1:numel (cpValue) if (isempty (cpValue{idx})) strValue = [strValue, " []"]; elseif (islogical (cpValue{idx}) || isnumeric (cpValue{idx})) tmp = strtrim (disp (cpValue{idx})); strValue = [strValue, sprintf(" %s", tmp)]; elseif (iscellstr (cpValue{idx})) tmp = sprintf ("{'%s'}", cpValue{idx}) strValue = [strValue, sprintf(" %s", tmp)]; elseif (isa (cpValue{idx}, 'string')) tmp = cellstr (cpValue{idx}); tmp = sprintf ("""%s""", tmp{:}) strValue = [strValue, sprintf(" %s", tmp)]; elseif (ischar (cpValue{idx})) strValue = [strValue, sprintf(" '%s'", cpValue{idx})]; endif endfor CP = [CP, sprintf("\n%+24s: {%s}", cpNames{i}, strtrim (strValue))]; endif endfor endif ## Print info fprintf ("\n TableProperties with properties:\n\n"); fprintf ("%+24s: '%s'\n", 'Description', D); fprintf ("%+24s: %s\n", 'UserData', UD); fprintf ("%+24s: %s\n", 'DimensionNames', DN); fprintf ("%+24s: %s\n", 'VariableNames', VN); fprintf ("%+24s: %s\n", 'VariableDescriptions', VD); fprintf ("%+24s: %s\n", 'VariableUnits', VU); fprintf ("%+24s: %s\n", 'VariableContinuity', VC); fprintf ("%+24s: %s\n", 'RowNames', RN); fprintf ("%s\n", CP); endfunction ## Display table internal function function print_table (this) ## Get VariableNames and VariableNames for optimal length of each column var_num = width (this); colData = {}; rowSpat = ""; T.parentV = []; T.nestedV = []; T.varName = {}; T.varNLen = []; T.optLen = []; colgap = " "; [colData, rowSpat, T] = resolve_table_for_printing ... (this, colData, rowSpat, T); ## Check for nested tables if (numel (T.nestedV) > 0) nested = true; ## Prepare nested table header varL_idx = 1; varN_idx = 1; varT_idx = 1; strhead1 = ""; strline1 = ""; strhead2 = ""; strline2 = ""; for v = 1:var_num ## Check for nested table in each variable if (ismember (v, T.parentV)) ## Get name length of variable containing the table and remove it ## from T.varNLen so it is aligned with data columns' T.optLen parVarNLen = T.varNLen(varL_idx); parVarName = T.varName{varN_idx}; T.varNLen(varL_idx) = []; ## Go through variables of nested table sum_optLen = 0; for nv = 1:T.nestedV(varT_idx) varN_idx += 1; pad_nB = floor ((T.optLen(varL_idx) - T.varNLen(varL_idx)) / 2); pad_nA = T.optLen(varL_idx) - (T.varNLen(varL_idx) + pad_nB); srtVarName = sprintf ("%s", T.varName{varN_idx}); strhead2 = [strhead2, repmat(" ", [1, pad_nB]), ... srtVarName, repmat(" ", [1, pad_nA]), colgap]; strline2 = [strline2, repmat("_", [1, T.optLen(varL_idx)]), ... colgap]; sum_optLen += T.optLen(varL_idx) + 4; varL_idx += 1; endfor ## Keep track of indexing sum_optLen -= 4; varL_idx -= 1; ## Fix continuous line for all nested table variables strline1 = [strline1, repmat("_", [1, sum_optLen]), colgap]; ## Position parent variable in the middle of the top header pad_nB = floor ((sum_optLen - parVarNLen) / 2); pad_nA = sum_optLen - (parVarNLen + pad_nB); srtVarName = sprintf ("%s", parVarName); strhead1 = [strhead1, repmat(" ", [1, pad_nB]), ... srtVarName, repmat(" ", [1, pad_nA]), colgap]; ## Increment index for nested table variables varT_idx += 1; else ## Oridinary variable (no nested table) pad_nB = floor ((T.optLen(varL_idx) - T.varNLen(varL_idx)) / 2); pad_nA = T.optLen(varL_idx) - (T.varNLen(varL_idx) + pad_nB); srtVarName = sprintf ("%s", T.varName{varN_idx}); strhead1 = [strhead1, repmat(" ", [1, pad_nB]), ... srtVarName, repmat(" ", [1, pad_nA]), colgap]; strhead2 = [strhead2, repmat(" ", [1, T.optLen(varL_idx)]), colgap]; strline1 = [strline1, repmat("_", [1, T.optLen(varL_idx)]), colgap]; strline2 = [strline2, repmat(" ", [1, T.optLen(varL_idx)]), colgap]; endif varL_idx += 1; varN_idx += 1; endfor else ## No nested table nested = false; ## Prepare table header strhead1 = ""; strline1 = ""; for v = 1:var_num pad_nB = floor ((T.optLen(v) - T.varNLen(v)) / 2); pad_nA = T.optLen(v) - (T.varNLen(v) + pad_nB); srtVarName = sprintf ("%s", T.varName{v}); strhead1 = [strhead1, repmat(" ", [1, pad_nB]), ... srtVarName, repmat(" ", [1, pad_nA]), colgap]; strline1 = [strline1, repmat("_", [1, T.optLen(v)]), colgap]; endfor endif ## Check whether RowNames are available in table and construct ## cell array of strings of size [height(table), 1] to prepad ## the displayed table with if (! isempty (this.RowNames)) rnLen = max (cellfun (@length, this.RowNames)) + 4; padPT = sprintf ("%%-%ds", rnLen); padfn = @(x) sprintf (padPT, x); rowNM = cellfun (padfn, this.RowNames, 'UniformOutput', false); ## Print table header fprintf (" %s%s\n", repmat (" ", [1, rnLen]), strhead1); fprintf (" %s%s\n\n", repmat (" ", [1, rnLen]), strline1); if (nested) fprintf (" %s%s\n", repmat (" ", [1, rnLen]), strhead2); fprintf (" %s%s\n\n", repmat (" ", [1, rnLen]), strline2); endif ## Print table rows for iRow = 1:height (this) strrow = sprintf (rowSpat, colData{iRow,:}); fprintf (" %s%s\n", rowNM{iRow}, strrow); endfor fprintf ("\n"); else ## Print table header fprintf (" %s\n", strhead1); fprintf (" %s\n\n", strline1); if (nested) fprintf (" %s\n", strhead2); fprintf (" %s\n\n", strline2); endif ## Print table rows for iRow = 1:height (this) strrow = sprintf (rowSpat, colData{iRow,:}); fprintf (" %s\n", strrow); endfor fprintf ("\n"); endif endfunction ## Prepare table for printing function [colData, rowSpat, T] = resolve_table_for_printing ... (this, colData, rowSpat, T) ## Get recursion for nested tables #n = numel (T); if (numel (T.nestedV) > 0) nested = true; minLen = T.varNLen(end); else nested = false; minLen = 1; endif colgap = " "; ## Start parsing table variables for v = 1:width (this) ## Get variable name T.varName = [T.varName, this.VariableNames(v)]; ## Get length of variable name varNLen = length (this.VariableNames{v}); T.varNLen = [T.varNLen, varNLen]; ## Get max length from data data = this.VariableValues{v}; cols = size (data)(2); ## Numeric if (isnumeric (data)) numfun = @(x) sprintf ("%g", x); if (cols > 1) colLen = zeros (1, cols); rowSpat_c = ""; for c = 1:cols ## Prepare data values to char vector tmpData = arrayfun (numfun, data(:,c), 'UniformOutput', false); colData = [colData, tmpData]; ## Get max length and append row string pattern colLen(c) = max (cellfun (@length, tmpData)); rowSpat_c = [rowSpat_c, sprintf("%%+%ds", colLen(c)), colgap]; endfor dataLen = sum (colLen + 4) - 4; optLen = max ([varNLen, dataLen, minLen]); T.optLen = [T.optLen, optLen]; prePad = repmat (" ", [1, optLen-dataLen]); rowSpat = [rowSpat, prePad, rowSpat_c]; else ## Prepare data values to char vector tmpData = arrayfun (numfun, data, 'UniformOutput', false); colData = [colData, tmpData]; ## Get max length and append row string pattern dataLen = max (cellfun (@length, tmpData)); optLen = max ([varNLen, dataLen, minLen]); T.optLen = [T.optLen, optLen]; rowSpat = [rowSpat, sprintf("%%+%ds", optLen), colgap]; endif ## Logical elseif (islogical (data)) if (cols > 1) rowSpat_c = ""; for c = 1:cols tmpData = repmat ({'false'}, size (data(:,c))); tmpData(data(:,c)) = "true"; colData = [colData, tmpData]; colLen(c) = 5; rowSpat_c = [rowSpat_c, "%-5s", colgap]; endfor dataLen = sum (colLen + 4) - 4; optLen = max ([varNLen, dataLen, minLen]); T.optLen = [T.optLen, optLen]; prePad = repmat (" ", [1, optLen-dataLen]); rowSpat = [rowSpat, prePad, rowSpat_c]; else tmpData = repmat ({'false'}, size (data)); tmpData(data) = "true"; colData = [colData, tmpData]; dataLen = 5; optLen = max ([varNLen, dataLen, minLen]); T.optLen = [T.optLen, optLen]; rowSpat = [rowSpat, sprintf("%%-%ds", optLen), colgap]; endif ## Categorical elseif (isa (data, {'categorical'})) if (cols > 1) colLen = zeros (1, cols); rowSpat_c = ""; for c = 1:cols tmpData = dispstrings (data(:,c)); colData = [colData, tmpData]; colLen(c) = max (cellfun (@length, tmpData)); rowSpat_c = [rowSpat_c, sprintf("%%+%ds", colLen(c)), colgap]; endfor dataLen = sum (colLen + 4) - 4; optLen = max ([varNLen, dataLen, minLen]); T.optLen = [T.optLen, optLen]; prePad = repmat (" ", [1, optLen-dataLen]); rowSpat = [rowSpat, prePad, rowSpat_c]; else tmpData = dispstrings (data); colData = [colData, tmpData]; dataLen = max (cellfun (@length, tmpData)); optLen = max ([varNLen, dataLen, minLen]); T.optLen = [T.optLen, optLen]; rowSpat = [rowSpat, sprintf("%%+%ds", optLen), colgap]; endif ## Datetime, duration, calendarDuration elseif (any (isa (data, {'datetime', 'duration', 'calendarDuration'}))) if (cols > 1) colLen = zeros (1, cols); rowSpat_c = ""; for c = 1:cols tmpData = dispstrings (data(:,c)); colData = [colData, tmpData]; colLen(c) = max (cellfun (@length, tmpData)); rowSpat_c = [rowSpat_c, sprintf("%%+%ds", colLen(c)), colgap]; endfor dataLen = sum (colLen + 4) - 4; optLen = max ([varNLen, dataLen]); T.optLen = [T.optLen, optLen, minLen]; prePad = repmat (" ", [1, optLen-dataLen]); rowSpat = [rowSpat, prePad, rowSpat_c]; else tmpData = dispstrings (data); colData = [colData, tmpData]; dataLen = max (cellfun (@length, tmpData)); optLen = max ([varNLen, dataLen, minLen]); T.optLen = [T.optLen, optLen]; rowSpat = [rowSpat, sprintf("%%+%ds", optLen), colgap]; endif ## String elseif (isa (data, 'string')) if (cols > 1) colLen = zeros (1, cols); rowSpat_c = ""; for c = 1:cols tmpData = dispstrings (data(:,c)); colData = [colData, tmpData]; colLen(c) = max (cellfun (@length, tmpData)); rowSpat_c = [rowSpat_c, sprintf("%%-%ds", colLen(c)), colgap]; endfor dataLen = sum (colLen + 4) - 4; optLen = max ([varNLen, dataLen, minLen]); T.optLen = [T.optLen, optLen]; prePad = repmat (" ", [1, optLen-dataLen]); rowSpat = [rowSpat, prePad, rowSpat_c]; else tmpData = dispstrings (data); colData = [colData, tmpData]; dataLen = max (cellfun (@length, tmpData)); optLen = max ([varNLen, dataLen, minLen]); T.optLen = [T.optLen optLen]; rowSpat = [rowSpat, sprintf("%%-%ds", optLen), colgap]; endif ## Character vectors elseif (ischar (data)) fcn = @(x) sprintf ("'%s'", x); ## add '' unlike MATLAB display tmpData = cell (rows (data), 1); for r = 1:rows (data) tmpData(r) = fcn (data(r,:)); endfor colData = [colData, tmpData]; dataLen = max (cellfun (@length, tmpData)); optLen = max ([varNLen, dataLen, minLen]); T.optLen = [T.optLen, optLen]; rowSpat = [rowSpat, sprintf("%%-%ds", optLen), colgap]; ## Cell array of character vectors elseif (iscellstr (data)) fcn = @(x) sprintf ("'%s'", x); ## add '' for MATLAB like display if (cols > 1) colLen = zeros (1, cols); rowSpat_c = ""; for c = 1:cols tmpData = cellfun (fcn, data(:,c), 'UniformOutput', false); colData = [colData, tmpData]; colLen(c) = max (cellfun (@length, tmpData)) + 2; rowSpat_c = [rowSpat_c, sprintf("{%%-%ds}", ... colLen(c) - 4), colgap]; endfor dataLen = sum (colLen + 4) - 4; optLen = max ([varNLen, dataLen, minLen]); T.optLen = [T.optLen, optLen]; prePad = repmat (" ", [1, optLen-dataLen]); rowSpat = [rowSpat, prePad, rowSpat_c]; else tmpData = cellfun (fcn, data, 'UniformOutput', false); colData = [colData, tmpData]; dataLen = max (cellfun (@length, tmpData)) + 2; optLen = max ([varNLen, dataLen, minLen]); T.optLen = [T.optLen, optLen]; rowSpat = [rowSpat, sprintf("{%%-%ds}", optLen - 2), colgap]; endif ## Cell array of mixed values elseif (iscell (data)) if (cols > 1) colLen = zeros (1, cols); rowSpat_c = ""; for c = 1:cols [tmpData, colLen(c)] = mixedcell2str (data(:,c), varNLen); colData = [colData, tmpData]; rowSpat_c = [rowSpat_c, sprintf("%%-%ds", colLen(c)), colgap]; endfor dataLen = sum (colLen + 4) - 4; # +2 due to extra {} optLen = max ([varNLen, dataLen, minLen]); T.optLen = [T.optLen, optLen]; prePad = repmat (" ", [1, optLen-dataLen]); rowSpat = [rowSpat, prePad, rowSpat_c]; else [tmpData, optLen] = mixedcell2str (data, varNLen); T.optLen = [T.optLen, max([optLen, minLen])]; colData = [colData, tmpData]; rowSpat = [rowSpat, sprintf("%%-%ds", optLen), colgap]; endif ## Structures elseif (isa (data, 'struct')) if (cols > 1) rowSpat_c = ""; for c = 1:cols tmpData = repmat ({''}, size (data(:,c))); colData = [colData, tmpData]; colLen(c) = 8; rowSpat_c = [rowSpat_c, "%-8s", colgap]; endfor dataLen = sum (colLen + 4) - 4; optLen = max ([TvarNLen, dataLen, minLen]); T.optLen = [T.optLen, optLen]; prePad = repmat (" ", [1, optLen-dataLen]); rowSpat = [rowSpat, prePad, rowSpat_c]; else tmpData = repmat ({''}, size (data)); colData = [colData, tmpData]; optLen = max ([varNLen, 8, minLen]); T.optLen = [T.optLen, optLen]; rowSpat = [rowSpat, sprintf("%%-%ds", optLen), colgap]; endif ## Tables (nested) elseif (isa (data, 'table')) if (nested) tmpData = repmat ({''}, [height(data), 1]); colData = [colData, tmpData]; optLen = max ([varNLen, 7, minLen]); T.optLen = [T.optLen, optLen]; rowSpat = [rowSpat, sprintf("%%-%ds", optLen), colgap]; else ## Increment structure array, add referenced variable, and ## recurse with nested table T.nestedV = [T.nestedV width(data)]; T.parentV = [T.parentV v]; [colData, rowSpat, T] = resolve_table_for_printing ... (data, colData, rowSpat, T); endif endif endfor endfunction ## Summary internal function function s = summary_for_variables (this) for v = 1:width (this) varName = this.VariableNames{v}; val = this.VariableValues{v}; s.(varName).Size = size (val); s.(varName).Type = class (val); if (! isempty (this.VariableDescriptions{v})) s.(varName).Description = this.VariableDescriptions{v}; else s.(varName).Description = ""; endif if (! isempty (this.VariableUnits{v})) s.(varName).Units = this.VariableUnits{v}; else s.(varName).Units = ""; endif s.(varName).Continuity = []; if (islogical (val)) s.(varName).True = sum (val, 1); s.(varName).False = sum (! val, 1); elseif (isa (val, 'duration')) ## Work in seconds (native 'median' does not omit NaN), then ## rebuild durations preserving the variable's display format. sec = seconds (val); fmt = val.Format; mn = seconds (__nanmin__ (sec)); md = seconds (median (sec, 'omitnan')); mx = seconds (__nanmax__ (sec)); mn.Format = fmt; md.Format = fmt; mx.Format = fmt; s.(varName).Min = mn; s.(varName).Median = md; s.(varName).Max = mx; s.(varName).NumMissing = sum (isnan (sec), 1); elseif (isa (val, 'datetime')) ## Operate on datenum-valued doubles (NaT mapped to NaN), then ## rebuild datetimes from the resulting statistics. dn = datetime_to_datenum (val); s.(varName).Min = datetime (__nanmin__ (dn), ... 'ConvertFrom', 'datenum'); s.(varName).Median = datetime (median (dn, 'omitnan'), ... 'ConvertFrom', 'datenum'); s.(varName).Max = datetime (__nanmax__ (dn), ... 'ConvertFrom', 'datenum'); s.(varName).NumMissing = sum (isnan (dn), 1); elseif (isa (val, 'calendarDuration')) ## 'calendarDuration' is not totally ordered (months and days are ## not interconvertible), so Min/Median/Max are undefined; report ## only the count of missing values. s.(varName).NumMissing = sum (ismissing (val), 1); elseif (isnumeric (val)) s.(varName).Min = __nanmin__ (val); s.(varName).Median = median (val, 'omitnan'); s.(varName).Max = __nanmax__ (val); s.(varName).NumMissing = sum (isnan (val), 1); endif ## No need to summarize values in 'cell', 'cellstr', 'string', ## 'categorical', and 'struct' variable types. endfor endfunction endmethods ## Shared helper for the house-format ODS exporters ('table2ods' and the ## standalone 'struct2ods'). Hidden rather than private so 'struct2ods' can ## reuse the exact flattening + metadata assembly. methods (Hidden) ## Build the house-format ODS parts for THIS table: the data grid V (with ## ISO-formatted datetime/duration values), the per-column ODS value types, ## and the metadata block (a descriptive comment row followed by the ## variable types, names, descriptions, and units, mirroring the header ## block that 'table2csv' writes so 'ods2table' can reuse its parser). ## CALLER names the function for error reporting. function [V, vtype, meta] = __ods_parts__ (this, caller) [V, N, T, D, U] = table2cellarrays (this, 'iso'); ## Nested tables and structs carry a multi-row (cell) type entry if (any (cellfun (@iscell, T))) error ("%s: nested tables and structs are not supported; flatten them before writing.", caller); endif Ccols = size (V, 2); vtype = cell (1, Ccols); for c = 1:Ccols vtype{c} = ods_value_type (T{c}); endfor txt = strcat ("# varTypes %d rows; varNames %d rows;", ... " varDescriptions %d rows; varUnits %d rows."); ## A table with no variables carries only the descriptive comment if (Ccols == 0) meta = {sprintf(txt, 0, 0, 0, 0)}; return; endif Trows = cellfun (@(x) size (x, 1), T); Tmaxr = max (Trows); Nrows = cellfun (@(x) size (x, 1), N); Nmaxr = max (Nrows); isvar = cellfun (@(x) ! isempty (x), N(1,:)); Drows = cellfun (@(x) size (x, 1), D); if (any (cellfun (@(x) ! isempty (x), D(isvar)))) Dmaxr = max (Drows(isvar)); else Dmaxr = 0; endif Urows = cellfun (@(x) size (x, 1), U); if (any (cellfun (@(x) ! isempty (x), U(isvar)))) Umaxr = max (Urows(isvar)); else Umaxr = 0; endif Header = repmat ({''}, Nmaxr + Tmaxr + Dmaxr + Umaxr, Ccols); for c = 1:Ccols if (isvar(c)) Header{1,c} = T{c}; Header{1 + Tmaxr,c} = N{c}; if (Dmaxr) Header{1 + Tmaxr + Nmaxr,c} = D{c}; endif if (Umaxr) Header{1 + Tmaxr + Nmaxr + Dmaxr,c} = U{c}; endif else Header{1,c} = 'RowNames'; endif endfor cmt = repmat ({''}, 1, Ccols); cmt{1} = sprintf (txt, Tmaxr, Nmaxr, Dmaxr, Umaxr); meta = [cmt; Header]; endfunction ## Build the MATLAB-interop spreadsheet parts for THIS table: the variable ## names (a header row), the flat data grid V with ISO-formatted ## datetime/duration values, and the per-column ODS value types. No hidden ## metadata (interop format). Shared by 'writetable' and 'struct2xlsx'. ## CALLER names the function for error reporting. function [names, V, vtype] = __interop_parts__ (this, caller) [V, N, T] = table2cellarrays (this, 'iso'); if (any (cellfun (@iscell, T))) error ("%s: nested tables and structs are not supported; flatten multicolumn variables with splitvars before writing.", caller); endif [names, V, T] = writetable_prep (V, N, T, false); vtype = cell (1, numel (T)); for c = 1:numel (T) vtype{c} = ods_value_type (T{c}); endfor endfunction endmethods ## Private methods for exporting (saving) Tables and Properties to files methods (Access = private) ## Export table to cell arrays function [V, N, T, D, U] = table2cellarrays (this, fmt = 'display') V = {}; # variable values N = {}; # variable names T = {}; # variable types D = {}; # variable descriptions U = {}; # variable units ## Process RowNames if (! isempty (this.RowNames)) V = [V, this.RowNames]; N = [N, {''}]; T = [T, 'cellstr']; D = [D, {''}]; U = [U, {''}]; endif ## Process variables for ix = 1:width (this) var_V = this.VariableValues{ix}; ncols = size (var_V, 2); ## Handle each variable type if (iscell (var_V)) for col = 1:ncols V = [V, var_V(:,col)]; N = [N, this.VariableNames{ix}]; T = [T, 'cell']; D = [D, this.VariableDescriptions(ix)]; U = [U, this.VariableUnits(ix)]; endfor elseif (islogical (var_V)) for col = 1:ncols V = [V, num2cell(var_V(:,col))]; N = [N, this.VariableNames{ix}]; T = [T, 'logical']; D = [D, this.VariableDescriptions(ix)]; U = [U, this.VariableUnits(ix)]; endfor elseif (isnumeric (var_V)) for col = 1:ncols V = [V, num2cell(var_V(:,col))]; N = [N, this.VariableNames{ix}]; T = [T, class(var_V(:,col))]; D = [D, this.VariableDescriptions(ix)]; U = [U, this.VariableUnits(ix)]; endfor elseif (isa (var_V, 'calendarDuration')) for col = 1:ncols V = [V, cellstr(var_V(:,col))]; N = [N, this.VariableNames{ix}]; T = [T, 'calendarDuration']; D = [D, this.VariableDescriptions(ix)]; U = [U, this.VariableUnits(ix)]; endfor elseif (isa (var_V, 'categorical')) for col = 1:ncols V = [V, cellstr(var_V(:,col))]; N = [N, this.VariableNames{ix}]; T = [T, 'categorical']; D = [D, this.VariableDescriptions(ix)]; U = [U, this.VariableUnits(ix)]; endfor elseif (isa (var_V, 'datetime')) ## Carry a non-empty TimeZone in the type string ('datetime ') so ## the house readers can restore a zone-aware datetime. tz = var_V.TimeZone; if (isempty (tz)) dttype = 'datetime'; else dttype = ['datetime ', tz]; endif for col = 1:ncols if (strcmp (fmt, 'iso')) V = [V, datetime2iso(var_V(:,col))]; else V = [V, cellstr(var_V(:,col))]; endif N = [N, this.VariableNames{ix}]; T = [T, dttype]; D = [D, this.VariableDescriptions(ix)]; U = [U, this.VariableUnits(ix)]; endfor elseif (isa (var_V, 'duration')) for col = 1:ncols if (strcmp (fmt, 'iso')) V = [V, duration2iso(var_V(:,col))]; else V = [V, cellstr(var_V(:,col))]; endif N = [N, this.VariableNames{ix}]; T = [T, 'duration']; D = [D, this.VariableDescriptions(ix)]; U = [U, this.VariableUnits(ix)]; endfor elseif (isa (var_V, 'string')) for col = 1:ncols V = [V, cellstr(var_V(:,col))]; N = [N, this.VariableNames{ix}]; T = [T, 'string']; D = [D, this.VariableDescriptions(ix)]; U = [U, this.VariableUnits(ix)]; endfor elseif (isa (var_V, 'table')) [tmpV, tmpN, tmpT tmpD, tmpU] = table2cellarrays (var_V, fmt); V = [V, tmpV]; nestedN = {}; nestedT = {}; nestedD = {}; nestedU = {}; for col = 1:size (tmpV, 2) nestedN = [nestedN, {{this.VariableNames{ix}; tmpN{col}}}]; nestedT = [nestedT, {{'table'; tmpT{col}}}]; nestedD = [nestedD, {{this.VariableDescriptions{ix}; tmpD{col}}}]; nestedU = [nestedU, {{this.VariableUnits{ix}; tmpU{col}}}]; endfor N = [N, nestedN]; T = [T, nestedT]; D = [D, nestedD]; U = [U, nestedU]; elseif (isa (var_V, 'struct')) tmpV = squeeze (struct2cell (var_V(:)))'; tmpN = fieldnames (var_V(:))'; tmpT = cellfun ('class', tmpV(1,:), 'UniformOutput', false); V = [V, tmpV]; nestedN = {}; nestedT = {}; nestedD = {}; nestedU = {}; for col = 1:size (tmpV, 2) nestedN = [nestedN, {{this.VariableNames{ix}; tmpN{col}}}]; nestedT = [nestedT, {{'struct'; tmpT{col}}}]; nestedD = [nestedD, {{this.VariableDescriptions{ix}; ''}}]; nestedU = [nestedU, {{this.VariableUnits{ix}; ''}}]; endfor N = [N, nestedN]; T = [T, nestedT]; D = [D, nestedD]; U = [U, nestedU]; endif endfor endfunction endmethods endclassdef ## Convert a datetime array to datenum-valued doubles of the same size, ## mapping NaT to NaN. Used by 'summary'. Core 'datenum' cannot process the ## NaN date components of a NaT, so those rows are substituted with a valid ## placeholder before conversion and set back to NaN afterwards. function dn = datetime_to_datenum (v) sz = size (v); DV = datevec (v); # (numel)-by-6 in column-major order nat = any (isnan (DV), 2); DV(nat,:) = 0; DV(nat,2:3) = 1; # valid month/day placeholder dn = datenum (DV); dn(nat) = NaN; dn = reshape (dn, sz); endfunction ## Return a logical mask, the same size as a table variable V, that flags the ## missing entries. Used by 'fillmissing'. Char arrays have no standard ## missing value and nested tables are treated as non-missing. function M = var_missing_mask (v) if (isa (v, 'table')) M = false (size (v)); elseif (any (isa (v, {'calendarDuration', 'categorical', 'datetime', ... 'duration', 'string'}))) M = ismissing (v); elseif (ischar (v)) M = false (size (v)); else # numeric, logical, cellstr M = __ismissing__ (v); endif endfunction ## Expand the 'constant' fill value into a 1-by-NVARS cell, one value per ## targeted variable (scalar broadcast, per-variable vector, or per-variable ## cell). Used by 'fillmissing'. function fvals = resolve_const_values (constVal, nvars) if (iscell (constVal)) if (isscalar (constVal)) fvals = repmat (constVal, 1, nvars); elseif (numel (constVal) == nvars) fvals = reshape (constVal, 1, nvars); else error (strcat ("table.fillmissing: a cell array of fill values must", ... " have one element per targeted variable.")); endif elseif (ischar (constVal) || isscalar (constVal)) fvals = repmat ({constVal}, 1, nvars); elseif (isvector (constVal) && numel (constVal) == nvars) fvals = num2cell (reshape (constVal, 1, nvars)); else error (strcat ("table.fillmissing: the fill value must be a scalar, a", ... " vector with one element per targeted variable, or a", ... " cell array of per-variable values.")); endif endfunction ## Fill every missing entry of variable V (mask M) with the constant FV. Used ## by 'fillmissing'. VARNAME names the variable for error reporting. function [v, filled] = fill_constant (v, M, fv, varname) filled = M; try if (iscellstr (v)) if (ischar (fv)) fv = {fv}; elseif (! (iscellstr (fv) && isscalar (fv))) error ("incompatible"); endif v(M) = fv; else v(M) = fv; endif catch error (strcat ("table.fillmissing: the fill value is incompatible", ... " with variable '%s'."), varname); end_try_catch endfunction ## For a column with logical missing mask M, return the source row index SI for ## each row: SI(i) is the row whose value should fill row i (0 when none is ## reachable). METHOD is 'previous', 'next', or 'nearest'. Used by ## 'fillmissing'. function si = fill_neighbor_idx (m, method) m = m(:); n = numel (m); idx = (1:n)'; vp = idx; vp(m) = 0; sp = cummax (vp); # previous non-missing index (0 if none) vn = idx; vn(m) = n + 1; sn = flipud (cummin (flipud (vn))); sn(sn == n + 1) = 0; # next non-missing index (0 if none) switch (method) case 'previous' si = sp; case 'next' si = sn; case 'nearest' si = idx; for i = find (m)' if (sp(i) == 0 && sn(i) == 0) si(i) = 0; elseif (sp(i) == 0) si(i) = sn(i); elseif (sn(i) == 0) si(i) = sp(i); elseif (sn(i) - i <= i - sp(i)) si(i) = sn(i); # tie favors the later (next) value else si(i) = sp(i); endif endfor endswitch endfunction ## Linearly interpolate the missing entries of numeric column COL (mask M). ## ENDVALS controls leading/trailing gaps ('extrap', 'none', or a numeric ## scalar). Returns the filled column and a logical mask of filled rows. Used ## by 'fillmissing'. function [col, filled] = fill_linear (col, m, endVals) m = m(:); n = numel (m); filled = false (n, 1); known = ! m; if (sum (known) < 2) return; # need at least two anchors to interpolate endif x = (1:n)'; xk = x(known); yk = double (col(known)); lo = xk(1); hi = xk(end); ## Interior gaps via linear interpolation interior = m & x > lo & x < hi; if (any (interior)) col(interior) = interp1 (xk, yk, x(interior), 'linear'); filled(interior) = true; endif ## Leading and trailing gaps via the 'EndValues' option ends = m & (x < lo | x > hi); if (any (ends)) if (ischar (endVals) || (isa (endVals, 'string') && isscalar (endVals))) switch (lower (char (endVals))) case 'extrap' col(ends) = interp1 (xk, yk, x(ends), 'linear', 'extrap'); filled(ends) = true; case 'none' ## leave the end gaps missing otherwise error (strcat ("table.fillmissing: unsupported 'EndValues'", ... " option '%s'."), lower (char (endVals))); endswitch elseif (isnumeric (endVals) && isscalar (endVals)) col(ends) = endVals; filled(ends) = true; else error (strcat ("table.fillmissing: 'EndValues' must be 'extrap',", ... " 'none', or a numeric scalar.")); endif endif endfunction ## Split a 'standardizeMissing' indicator into a numeric row vector NUMIND and a ## cellstr row TXTIND of text indicators. Used by 'standardizeMissing'. function [numInd, txtInd] = std_normalize_indicator (indicator) numInd = []; txtInd = {}; if (iscell (indicator) && ! iscellstr (indicator)) for i = 1:numel (indicator) e = indicator{i}; if (ischar (e)) txtInd{end+1} = e; elseif (isa (e, 'string')) tmp = cellstr (e); txtInd = [txtInd, tmp(:)']; elseif (iscellstr (e)) txtInd = [txtInd, e(:)']; elseif (isnumeric (e) || islogical (e)) numInd = [numInd, double(e(:)')]; else error (strcat ("table.standardizeMissing: unsupported indicator", ... " element of class '%s'."), class (e)); endif endfor elseif (iscellstr (indicator)) txtInd = indicator(:)'; elseif (ischar (indicator)) txtInd = {indicator}; elseif (isa (indicator, 'string')) tmp = cellstr (indicator); txtInd = tmp(:)'; elseif (isnumeric (indicator) || islogical (indicator)) numInd = double (indicator(:)'); else error (strcat ("table.standardizeMissing: invalid INDICATOR of class", ... " '%s'."), class (indicator)); endif endfunction ## Replace entries of variable V that match a (type-compatible) indicator with ## the standard missing value of V's class. Used by 'standardizeMissing'. function v = std_apply_indicator (v, numInd, txtInd) if (isfloat (v)) if (! isempty (numInd)) v(ismember (v, numInd)) = NaN; endif elseif (iscellstr (v)) if (! isempty (txtInd)) v(ismember (v, txtInd)) = {''}; endif elseif (isa (v, 'string')) if (! isempty (txtInd)) v(ismember (cellstr (v), txtInd)) = string (missing); endif elseif (isa (v, 'categorical')) if (! isempty (txtInd)) v(ismember (cellstr (v), txtInd)) = categorical (missing); endif endif ## logical, integer, duration, datetime, calendarDuration, and nested table ## variables have no compatible standard missing value here; pass through. endfunction ## Special function to convert a mixed cell array to cellstr array ## that keeps MATLAB like formatting for each type of element function [outData, optLen] = mixedcell2str (data, varLen) ## Preallocate indexes to avoid truncation when last elements are 0 idx_cell = logical (zeros (size (data))); idx_charvec = idx_cell; idx_logical = idx_cell; idx_numeric = idx_cell; idx_object = idx_cell; idx_string = idx_cell; idx_struct = idx_cell; ## Find scalars or row vectors se = cell2mat (cellfun (@(x) numel (x), data, 'UniformOutput', false)) == 1; ve = cell2mat (cellfun (@(x) size (x,1), data, 'UniformOutput', false)) == 1; ## Catch 'cell' scalars tmp = cell2mat (cellfun (@iscell, data(se), 'UniformOutput', false)) == 1; idx_cell(se) = tmp; sf = @(x) sprintf ("1x1 cell"); out_str(idx_cell) = (cellfun (sf, data(idx_cell), ... 'UniformOutput', false)); ## Catch 'char' scalars or row vectors tmp = cell2mat (cellfun (@ischar, data(ve), 'UniformOutput', false)); idx_charvec(ve) = tmp; sf = @(x) sprintf ("'%s'", x); out_str(idx_charvec) = (cellfun (sf, data(idx_charvec), ... 'UniformOutput', false)); ## Catch 'logical' scalars or row vectors tmp = cell2mat (cellfun (@islogical, data(ve), 'UniformOutput', false)) == 1; idx_logical(ve) = tmp; sf = @(x) sprintf ("[%s]", strtrim (sprintf ("%d ", x))); out_str(idx_logical) = (cellfun (sf, data(idx_logical), ... 'UniformOutput', false)); ## Catch 'numeric' scalars or row vectors tmp = cell2mat (cellfun (@isnumeric, data(ve), 'UniformOutput', false)) == 1; idx_numeric(ve) = tmp; sf = @(x) sprintf ("[%s]", strtrim (sprintf ("%g ", x))); out_str(idx_numeric) = (cellfun (sf, data(idx_numeric), ... 'UniformOutput', false)); ## Catch 'object' scalars tmp = cell2mat (cellfun (@isobject, data(se), 'UniformOutput', false)) == 1; idx_struct(se) = tmp; sf = @(x) sprintf ("1x1 %s", class (x)); out_str(idx_struct) = (cellfun (sf, data(idx_struct), ... 'UniformOutput', false)); ## Catch 'string' scalars or row vectors tmp = cell2mat (cellfun (@isstring, data(ve), 'UniformOutput', false)) == 1; idx_string(ve) = tmp; sf = @(x) sprintf ("[%s]", strtrim (sprintf ("%s ", dispstrings (x){:}))); out_str(idx_string) = (cellfun (sf, data(idx_string), ... 'UniformOutput', false)); ## Catch scalar elements of struct type tmp = cell2mat (cellfun (@isstruct, data(se), 'UniformOutput', false)) == 1; idx_struct(se) = tmp; sf = @(x) sprintf ("1x1 struct"); out_str(idx_struct) = (cellfun (sf, data(idx_struct), ... 'UniformOutput', false)); ## Keep indexes for numerical and logical values to right alignment pad_B = idx_numeric | idx_logical; # pad before: sprintf("{%%-%ds}" pad_A = ! pad_B; # pad after: sprintf("{%%+%ds}" ## Catch remaining elements me = ! (idx_cell | idx_charvec | idx_logical | idx_numeric | ... idx_object | idx_string | idx_struct); ## Preallocate indexes to avoid truncation when last elements are 0 idx_cell = logical (zeros (size (data))); idx_charvec = idx_cell; idx_logical = idx_cell; idx_numeric = idx_cell; idx_object = idx_cell; idx_string = idx_cell; idx_struct = idx_cell; if (any (me)) ## 'cell' arrays tmp = cell2mat (cellfun (@iscell, data(me), 'UniformOutput', false)) == 1; idx_cell(me) = tmp; sf = @(x) sprintf (strcat ([strjoin(repmat ({'%d'}, 1, ndims (x)), 'x'), ... ' cell']), size (x)); out_str(idx_cell) = (cellfun (sf, data(idx_cell), ... 'UniformOutput', false)); ## 'char' arrays tmp = cell2mat (cellfun (@ischar, data(me), 'UniformOutput', false)); idx_charvec(me) = tmp; sf = @(x) sprintf (strcat (strjoin (repmat ({'%d'}, 1, ndims (x)), 'x'), ... ' char'), size (x)); out_str(idx_charvec) = (cellfun (sf, data(idx_charvec), ... 'UniformOutput', false)); ## 'logical' arrays tmp = cell2mat (cellfun (@islogical, data(me), ... 'UniformOutput', false)) == 1; idx_logical(me) = tmp; sf = @(x) sprintf (strcat (strjoin (repmat ({'%d'}, 1, ndims (x)), 'x'), ... ' logical'), size (x)); out_str(idx_logical) = (cellfun (sf, data(idx_logical), ... 'UniformOutput', false)); ## 'numeric' arrays tmp = cell2mat (cellfun (@isnumeric, data(me), ... 'UniformOutput', false)) == 1; idx_numeric(me) = tmp; sf = @(x) sprintf (strcat (strjoin (repmat ({'%d'}, 1, ndims (x)), 'x'), ... ' array'), size (x)); out_str(idx_numeric) = (cellfun (sf, data(idx_numeric), ... 'UniformOutput', false)); ## 'object' arrays tmp = cell2mat (cellfun (@isstring, data(me), 'UniformOutput', false)) == 1; idx_string(me) = tmp; sf = @(x) sprintf (strcat (strjoin (repmat ({'%d'}, 1, ndims (x)), 'x'), ... ' %s'), size (x), class (x)); out_str(idx_string) = (cellfun (sf, data(idx_string), ... 'UniformOutput', false)); ## 'string' arrays tmp = cell2mat (cellfun (@isstring, data(me), 'UniformOutput', false)) == 1; idx_string(me) = tmp; sf = @(x) sprintf (strcat (strjoin (repmat ({'%d'}, 1, ndims (x)), 'x'), ... ' string'), size (x)); out_str(idx_string) = (cellfun (sf, data(idx_string), ... 'UniformOutput', false)); ## 'struct' arrays tmp = cell2mat (cellfun (@isstruct, data(me), 'UniformOutput', false)) == 1; idx_struct(me) = tmp; sf = @(x) sprintf (strcat (strjoin (repmat ({'%d'}, 1, ndims (x)), 'x'), ... ' struct'), size (x)); out_str(idx_struct) = (cellfun (sf, data(idx_struct), ... 'UniformOutput', false)); endif ## Get optimal length strLen = max (cellfun (@length, out_str)) + 2; optLen = max ([varLen, strLen]); ## Pad data according to optimal length ## numeric and logical is right aligned, everything else is left aligned Ra = sprintf ("{%%+%ds}", optLen - 2); La = sprintf ("{%%-%ds}", optLen - 2); fcn = @(x) sprintf (Ra, x); outData(pad_B) = cellfun (fcn, out_str(pad_B), 'UniformOutput', false); fcn = @(x) sprintf (La, x); outData(pad_A) = cellfun (fcn, out_str(pad_A), 'UniformOutput', false); outData = outData(:); endfunction ## Helper function for unstack method to get default aggregation function ## and missing values according to the data type of the stacked variable function [mcvec, aggrFcn] = get_default_aggrFcn (vvals, nrows, aggrFcn) ## Get columns of stacked variable vcols = size (vvals, 2); ## Handle each specific data type if (any (isa (vvals, {'single', 'double'}))) mcvec = NaN (nrows, vcols, 'like', vvals); if (isempty (aggrFcn)) # add default aggrevation function aggrFcn = @sum; else # check that it produces correct output tmpval = 1:5; try tmpval = aggrFcn (tmpval); catch aggrFcn = strcat ("table.unstack: invalid 'AggregationFunction'", ... " for numeric data."); end_try_catch if (! isscalar (tmpval)) aggrFcn = strcat ("table.unstack: 'AggregationFunction'", ... " must return a scalar value."); endif endif elseif (isnumeric (vvals)) # integer types have no missing value, use 0 mcvec = zeros (nrows, vcols, 'like', vvals); if (isempty (aggrFcn)) # add default aggrevation function aggrFcn = @sum; else # check that it produces correct output tmpval = 1:5; try tmpval = aggrFcn (tmpval); catch aggrFcn = strcat ("table.unstack: invalid 'AggregationFunction'", ... " for numeric data."); end_try_catch if (! isscalar (tmpval)) aggrFcn = strcat ("table.unstack: 'AggregationFunction'", ... " must return a scalar value."); endif endif elseif (isa (vvals, 'calendarDuration')) mcvec = repmat (calendarDuration ([0, 0, 0]), nrows, vcols); if (isempty (aggrFcn)) # add default aggrevation function aggrFcn = @unique; else # check that it produces correct output tmpval = calendarDuration (1:5, 0, 0); try tmpval = aggrFcn (tmpval); catch aggrFcn = strcat ("table.unstack: invalid 'AggregationFunction'", ... " for calendarDuration data."); end_try_catch if (! isscalar (tmpval)) aggrFcn = strcat ("table.unstack: 'AggregationFunction'", ... " must return a scalar value."); endif endif elseif (isa (vvals, 'duration')) mcvec = repmat (duration ([0, 0, 0]), nrows, vcols); if (isempty (aggrFcn)) # add default aggrevation function aggrFcn = @unique; else # check that it produces correct output tmpval = duration (1:5, 0, 0); try tmpval = aggrFcn (tmpval); catch aggrFcn = strcat ("table.unstack: invalid 'AggregationFunction'", ... " for duration data."); end_try_catch if (! isscalar (tmpval)) aggrFcn = strcat ("table.unstack: 'AggregationFunction'", ... " must return a scalar value."); endif endif elseif (islogical (vvals)) mcvec = false (nrows, vcols); if (isempty (aggrFcn)) # add default aggrevation function aggrFcn = @unique; else # check that it produces correct output tmpval = [false, false, true, true, false]; try tmpval = aggrFcn (tmpval); catch aggrFcn = strcat ("table.unstack: invalid 'AggregationFunction'", ... " for logical data."); end_try_catch if (! isscalar (tmpval)) aggrFcn = strcat ("table.unstack: 'AggregationFunction'", ... " must return a scalar value."); endif endif elseif (isa (vvals, 'categorical')) mcvec = repmat (categorical (NaN), nrows, vcols); if (isempty (aggrFcn)) # add default aggrevation function aggrFcn = @unique; else # check that it produces correct output tmpval = categorical (1:5); try tmpval = aggrFcn (tmpval); catch aggrFcn = strcat ("table.unstack: invalid 'AggregationFunction'", ... " for categorical data."); end_try_catch if (! isscalar (tmpval)) aggrFcn = strcat ("table.unstack: 'AggregationFunction'", ... " must return a scalar value."); endif endif else # all other data types (string, cellstr, datetime, ...) if (iscellstr (vvals)) vt = 'cellstr'; else vt = class (vvals); endif tmpl = table ('Size', [nrows, 1], 'VariableTypes', {vt}); mcvec = repmat (tmpl.Var1, 1, vcols); if (isempty (aggrFcn)) # add default aggrevation function aggrFcn = @unique; endif endif ## Enforce a scalar aggregation result, erroring on e.g. conflicting ## non-numeric values under the default @unique, matching MATLAB. if (! ischar (aggrFcn)) baseFcn = aggrFcn; aggrFcn = @(x) enforce_scalar_aggr (baseFcn (x)); endif endfunction ## Error out when an unstack aggregation function returns a non-scalar value. function val = enforce_scalar_aggr (val) if (size (val, 1) > 1) error (strcat ("table.unstack: 'AggregationFunction' must return", ... " a scalar value.")); endif endfunction ## Map a key variable kind to a comparison category. Returns an empty ## character vector for types that cannot be used as keys. function k = key_kind (col) if (isa (col, 'categorical') || isa (col, 'string') || iscellstr (col) || ischar (col)) k = 'text'; elseif (isa (col, 'datetime')) k = 'datetime'; elseif (isa (col, 'duration')) k = 'duration'; elseif (isa (col, 'calendarDuration')) k = 'calendarDuration'; elseif (isnumeric (col) || islogical (col)) k = 'numeric'; else k = ''; endif endfunction ## Validate the optional SETORDER argument shared by the set operations. ## Returns the lower-cased order ('sorted' default) and an errmsg body (empty on ## success) emitted by the caller under its own name. function [order, errmsg] = parse_set_order (args) order = 'sorted'; errmsg = ''; if (! isempty (args)) if (numel (args) > 1) errmsg = "too many input arguments."; elseif (! (ischar (args{1}) && isrow (args{1}) && any (strcmpi (args{1}, {'sorted', 'stable'})))) errmsg = "SETORDER must be either 'sorted' or 'stable'."; else order = lower (args{1}); endif endif endfunction ## Build the disambiguation suffixes used by the join methods when a variable ## name is shared by both tables. MATLAB derives them from the input argument ## names (e.g. inputs L and R give '_L'/'_R'); fall back to '_left'/'_right' ## when an input has no workspace name. function [lsuf, rsuf] = join_suffixes (leftName, rightName) if (isempty (leftName)) leftName = 'left'; endif if (isempty (rightName)) rightName = 'right'; endif lsuf = ['_', leftName]; rsuf = ['_', rightName]; endfunction ## Encode two cellstr key columns into consistent integer codes so that equal ## strings (across both columns) map to the same code. function [lp, rp] = text_codes (lc, rc) nl = numel (lc); [~, ~, ic] = unique ([lc(:); rc(:)]); lp = ic(1:nl); rp = ic(nl+1:end); endfunction ## Build consistent numeric key proxies for the same key variable taken from ## two tables, so that equal key values map to equal proxy rows. Returns an ## errmsg body (empty on success) emitted by the caller under its own name. function [lp, rp, errmsg] = key_col_proxy (lcol, rcol) lp = []; rp = []; errmsg = ''; kl = key_kind (lcol); kr = key_kind (rcol); if (isempty (kl)) errmsg = sprintf ("unsupported key variable type '%s'.", class (lcol)); return; elseif (isempty (kr)) errmsg = sprintf ("unsupported key variable type '%s'.", class (rcol)); return; elseif (! strcmp (kl, kr)) errmsg = "key variables have incompatible types."; return; endif switch (kl) case 'text' [lp, rp] = text_codes (cellstr (lcol), cellstr (rcol)); case 'datetime' lp = datetime_to_datenum (lcol); rp = datetime_to_datenum (rcol); case 'duration' lp = days (lcol); rp = days (rcol); case 'calendarDuration' lp = lcol.proxyArray; rp = rcol.proxyArray; case 'numeric' lp = double (lcol); rp = double (rcol); endswitch if (size (lp, 2) != size (rp, 2)) lp = []; rp = []; errmsg = "key variables have incompatible sizes."; endif endfunction ## Build a single-column grouping proxy for one grouping variable COL: a numeric ## matrix P (one row per element) whose sort order matches COL's value order, so ## that 'unique (P, "rows")' recovers the sorted unique groups, together with a ## logical MISS mask flagging the elements that findgroups treats as missing ## (NaN/NaT//''/). Returns an errmsg body (empty on success) ## emitted by the caller under its own name. function [p, miss, errmsg] = group_col_proxy (col) p = []; miss = []; errmsg = ''; if (isa (col, 'categorical')) ## Categorical groups follow category order (ordinal or reordered), which the ## underlying category codes encode; maps to NaN. p = double (col)(:); miss = isnan (p); return; endif k = key_kind (col); if (isempty (k)) errmsg = sprintf ("unsupported grouping variable type '%s'.", class (col)); return; endif switch (k) case 'text' c = cellstr (col); c = c(:); miss = cellfun (@isempty, c); [~, ~, ic] = unique (c); p = ic(:); case 'datetime' p = datetime_to_datenum (col)(:); miss = isnan (p); case 'duration' p = days (col)(:); miss = isnan (p); case 'calendarDuration' p = col.proxyArray; miss = any (isnan (p), 2); case 'numeric' p = double (col)(:); miss = isnan (p); endswitch endfunction ## Group table rows by the grouping-variable columns GRPCOLS (a cell array of ## variable values, one per grouping variable), using 'group_col_proxy' on each. ## Returns G, an n-by-1 vector of group numbers (NaN for rows holding a missing ## value in any grouping variable), NGROUPS, the number of groups, REPROWS, a ## representative row index per group in sorted group order, and an errmsg body ## (empty on success) emitted by the caller. function [G, ngroups, repRows, errmsg] = group_table_rows (grpCols) errmsg = ''; ngroups = 0; repRows = []; n = size (grpCols{1}, 1); P = []; miss = false (n, 1); for j = 1:numel (grpCols) [p, m, e] = group_col_proxy (grpCols{j}); if (! isempty (e)) G = []; errmsg = e; return; endif P = [P, p]; miss = miss | m; endfor G = NaN (n, 1); keep = find (! miss); if (! isempty (keep)) [~, ia, ic] = unique (P(keep,:), "rows"); G(keep) = ic; repRows = keep(ia); ngroups = numel (ia); endif endfunction ## Build the grouping-variable columns of a grouped apply output from the ## grouping-variable values GRPCOLS: GCOLS holds the value of each grouping ## variable at the representative rows REPROWS, and GCOUNT the number of rows in ## each group, derived from the group-number vector G. function [gcols, gcount] = group_output_cols (grpCols, G, repRows) ngroups = numel (repRows); gcols = cell (1, numel (grpCols)); for p = 1:numel (grpCols) gcols{p} = grpCols{p}(repRows,:); endfor gcount = accumarray (G(! isnan (G)), 1, [ngroups, 1]); endfunction ## Build the row keep-mask for 'groupfilter' by applying the filter function ## METHOD to each data variable's per-group slice. DATACOLS is a cell array of ## data-variable values; G the n-by-1 group numbers (1..NG), every row assigned ## to a group. For each group METHOD receives the variable's slice and must ## return a logical scalar (keep/drop the whole group) or a logical vector with ## one element per group row. The per-variable masks are combined with logical ## AND, so a row is kept only when the condition holds across all data variables. ## Returns KEEP (n-by-1 logical) and an errmsg body emitted by the caller. function [keep, errmsg] = gf_keep_mask (method, dataCols, G, ng) errmsg = ''; n = numel (G); keep = true (n, 1); for d = 1:numel (dataCols) col = dataCols{d}; for g = 1:ng rows = find (G == g); if (isempty (rows)) continue; endif r = method (col(rows,:)); if (! (islogical (r) || isnumeric (r))) errmsg = "the filter function must return a logical result."; return; endif r = logical (r(:)); if (isscalar (r)) m = repmat (r, numel (rows), 1); elseif (numel (r) == numel (rows)) m = r; else errmsg = strcat ("the filter function must return a logical scalar", ... " or a logical vector with one element per group", ... " row."); return; endif keep(rows) = keep(rows) & m; endfor endfor endfunction ## Transform one data variable COL group by group for 'grouptransform', applying ## METHOD (a transform-name char vector or a function handle) to each group's ## slice and returning OUT, the transformed values the same size as COL. G is ## the n-by-1 group-number vector (1..NG), every row assigned to a group. A ## function handle must return a single row (broadcast) or one row per group row. ## Returns an errmsg body (empty on success) emitted by the caller. function [out, errmsg] = gt_transform_col (method, col, G, ng) out = []; errmsg = ''; if (! (isnumeric (col) || islogical (col))) errmsg = sprintf (strcat ("grouptransform requires numeric or logical", ... " data; got '%s'"), class (col)); return; endif x = double (col); out = x; for g = 1:ng rows = find (G == g); if (isempty (rows)) continue; endif slice = x(rows,:); if (is_function_handle (method)) r = method (slice); if (! (isnumeric (r) || islogical (r))) errmsg = "the transform function must return a numeric result."; out = []; return; endif if (size (r, 1) == 1) r = repmat (r, numel (rows), 1); endif if (! isequal (size (r), size (slice))) errmsg = strcat ("the transform function must return a result the", ... " same size as the group, or a single row."); out = []; return; endif out(rows,:) = r; else for c = 1:columns (slice) out(rows,c) = gt_apply_named (method, slice(:,c)); endfor endif endfor endfunction ## Apply a single named transform METHOD to the column vector X (a group's slice ## of one data variable), returning the transformed values V the same size as X. ## NaN values are omitted when computing the group statistics; the centring and ## scaling methods leave NaN in place, while 'meanfill'/'linearfill' fill them. function v = gt_apply_named (method, x) nan = isnan (x); xo = x(! nan); switch (method) case 'meancenter' v = x - mean (xo); case 'zscore' v = (x - mean (xo)) / std (xo); case 'norm' v = x / norm (xo); case 'rescale' mn = min (xo); mx = max (xo); v = (x - mn) / (mx - mn); case 'meanfill' v = x; v(nan) = mean (xo); case 'linearfill' v = gt_linearfill (x); endswitch endfunction ## Fill the missing values of the column vector X by linear interpolation over ## the non-missing positions, leaving leading and trailing missing values (and ## any group with fewer than two non-missing values) unchanged. function v = gt_linearfill (x) v = x; idx = find (! isnan (x)); if (numel (idx) >= 2) pos = (1:numel (x))'; vi = interp1 (idx, x(idx), pos, "linear"); fill = isnan (x) & pos > idx(1) & pos < idx(end); v(fill) = vi(fill); endif endfunction ## Normalise the 'groupsummary' METHOD argument into a cell array of method specs ## METHODS (each a method-name char vector or a function handle) and a parallel ## cell array of display names METHNAMES used to build output variable names ## (the method name, or 'fun' for the n-th function handle). Returns an ## errmsg body (empty on success) emitted by the caller. function [methods, methNames, errmsg] = gs_normalise_methods (method) methods = {}; methNames = {}; errmsg = ''; if (isempty (method) && ! iscell (method) && ! ischar (method) && ! is_function_handle (method)) return; # no method requested: counts only endif if (is_function_handle (method) || (ischar (method) && isrow (method)) || isa (method, 'string')) items = {method}; elseif (iscell (method)) items = method(:)'; else errmsg = strcat ("METHOD must be a method name, a function handle, or a", ... " cell array of method names and function handles."); return; endif known = {'sum', 'mean', 'median', 'mode', 'var', 'std', 'min', 'max', ... 'range', 'nnz', 'nummissing', 'numunique'}; nfun = 0; for k = 1:numel (items) it = items{k}; if (is_function_handle (it)) nfun++; methods{end+1} = it; methNames{end+1} = sprintf ("fun%d", nfun); elseif ((ischar (it) && isrow (it)) || (isa (it, 'string') && isscalar (it))) nm = lower (char (it)); if (! any (strcmp (nm, known))) errmsg = sprintf ("'%s' is not a supported method name.", char (it)); return; endif methods{end+1} = nm; methNames{end+1} = nm; else errmsg = strcat ("each method must be a method name or a function", ... " handle."); return; endif endfor endfunction ## Group table rows for 'groupsummary' by the grouping-variable values GRPCOLS, ## treating each grouping variable's missing values as a single group value. ## Returns G, an n-by-1 vector of group numbers (1..NGROUPS); NGROUPS; REPROWS, ## a representative row index per group; and an errmsg body emitted by the ## caller. Groups are sorted by grouping value with missing groups last. When ## INCMISS is false, rows holding a missing grouping value are dropped (labelled ## NaN in G and excluded from NGROUPS/REPROWS). function [G, ngroups, repRows, errmsg] = gs_group_rows (grpCols, incMiss) errmsg = ''; G = []; ngroups = 0; repRows = []; n = size (grpCols{1}, 1); KEY = []; SORT = []; anyMiss = false (n, 1); for j = 1:numel (grpCols) [p, m, e] = group_col_proxy (grpCols{j}); if (! isempty (e)) errmsg = e; return; endif pc = p; pc(m,:) = 0; # collapse all missing values of this variable KEY = [KEY, pc, double(m)]; sp = p; sp(m,:) = Inf; # sort missing groups last SORT = [SORT, sp]; anyMiss = anyMiss | m; endfor [~, ia, ic] = unique (KEY, "rows"); ng = numel (ia); grpMiss = anyMiss(ia); [~, ord] = sortrows (SORT(ia,:)); reps = ia(ord); grpMiss = grpMiss(ord); pos = zeros (ng, 1); pos(ord) = 1:ng; G = pos(ic); if (! incMiss && any (grpMiss)) keep = find (! grpMiss); newId = NaN (ng, 1); newId(keep) = 1:numel (keep); G = newId(G); reps = reps(keep); ng = numel (keep); endif ngroups = ng; repRows = reps; endfunction ## Group the rows of 'groupsummary'/'groupcounts' by the grouping-variable values ## GRPCOLS (already binned when a GROUPBINS argument was given). Returns G, the ## n-by-1 group numbers (NaN for an excluded row), NG the number of groups, GCOLS ## a 1-by-K cell of the typed grouping-variable output columns (one value per ## group), and an errmsg body emitted by the caller. When INCEMPTY is true the ## unused categories of a categorical (or binned) grouping variable contribute ## empty groups, built from the full level machinery; otherwise only the observed ## groups are returned, in ascending grouping-value order with missing groups ## last. function [G, ng, gcols, errmsg] = gs_grouping (grpCols, incMiss, incEmpty) errmsg = ''; gcols = {}; K = numel (grpCols); n = size (grpCols{1}, 1); if (incEmpty) [G, ng, lvlOf, levVals, ~, errmsg] = ... pivot_dimension (grpCols, n, incMiss, true); if (! isempty (errmsg)) G = []; ng = 0; return; endif gcols = cell (1, K); for j = 1:K gcols{j} = levVals{j}(lvlOf(:,j), :); endfor else [G, ng, repRows, errmsg] = gs_group_rows (grpCols, incMiss); if (! isempty (errmsg)) return; endif gcols = cell (1, K); for j = 1:K gcols{j} = grpCols{j}(repRows, :); endfor endif endfunction ## Apply a single 'groupsummary' method M (a method-name char vector or a ## function handle) to the column slice X of one group, returning a row result V. ## Named methods omit missing values (except 'nummissing'); a function handle ## receives X unchanged and must return a single row. Returns an errmsg body ## (empty on success) emitted by the caller. function [v, errmsg] = gs_apply_method (m, x) v = []; errmsg = ''; if (is_function_handle (m)) v = m (x); if (size (v, 1) != 1) errmsg = "a function handle method must return a single row"; endif return; endif ## Type-agnostic counting methods. if (strcmp (m, 'nummissing')) v = sum (gs_missing_mask (x), 1); return; endif if (strcmp (m, 'numunique')) miss = gs_missing_mask (x); if (size (x, 2) == 1) v = numel (unique (x(! miss,:))); else v = zeros (1, size (x, 2)); for c = 1:size (x, 2) col = x(:,c); v(c) = numel (unique (col(! miss(:,c)))); endfor endif return; endif ## The remaining named methods require numeric or logical data. if (! (isnumeric (x) || islogical (x))) errmsg = sprintf (strcat ("named method '%s' is not supported for", ... " variables of type '%s'; use a function", ... " handle"), m, class (x)); return; endif x = double (x); ## An empty group (e.g. an unused IncludeEmptyGroups category) takes the ## method's empty value: 0 for the additive 'sum'/'nnz', NaN otherwise. if (rows (x) == 0) if (any (strcmp (m, {'sum', 'nnz'}))) v = zeros (1, columns (x)); else v = NaN (1, columns (x)); endif return; endif nan = isnan (x); cnt = sum (! nan, 1); z = x; z(nan) = 0; switch (m) case 'sum' v = sum (z, 1); case 'mean' v = sum (z, 1) ./ cnt; case 'min' v = min (x, [], 1); case 'max' v = max (x, [], 1); case 'range' v = max (x, [], 1) - min (x, [], 1); case 'nnz' v = sum (x != 0 & ! nan, 1); case {'median', 'mode', 'var', 'std'} v = NaN (1, size (x, 2)); for c = 1:size (x, 2) col = x(! nan(:,c), c); if (! isempty (col)) switch (m) case 'median' v(c) = median (col); case 'mode' v(c) = mode (col); case 'var' v(c) = var (col); case 'std' v(c) = std (col); endswitch endif endfor endswitch endfunction ## Return a logical mask the size of X flagging its missing elements, used by the ## type-agnostic 'groupsummary' methods. Supports the numeric, logical, text, ## datetime, duration, calendarDuration, and categorical variable types. function mask = gs_missing_mask (x) if (isa (x, 'datetime')) mask = isnan (datetime_to_datenum (x)); elseif (isa (x, 'duration')) mask = isnan (days (x)); elseif (isa (x, 'calendarDuration')) mask = any (isnan (x.proxyArray), 2); elseif (isa (x, 'categorical') || isa (x, 'string')) mask = ismissing (x); elseif (iscellstr (x)) mask = cellfun (@isempty, x); elseif (islogical (x)) mask = false (size (x)); elseif (isnumeric (x)) mask = isnan (x); else mask = false (size (x)); endif endfunction ## Build the level structure of one 'pivot' grouping variable COL. Returns IDX, ## an n-by-1 vector of level indices (1..L) for the rows of COL (NaN for a row ## holding a missing value when INCMISS is false, so that row is excluded from ## every group), LEVVALS, a typed column vector with one representative value per ## level used to build row labels and column names, MISSLVL, a 1-by-L logical ## flagging the missing level, and an errmsg body (empty on success). Levels are ## the sorted unique values of COL; a categorical variable uses its category ## order, and when INCEMPTY is true every category is a level even if unused in ## the data. A missing value forms one extra level, sorted last, when INCMISS. function [idx, levVals, missLvl, errmsg] = pivot_levels (col, incMiss, incEmpty) idx = []; levVals = []; missLvl = []; errmsg = ''; n = size (col, 1); [p, miss, errmsg] = group_col_proxy (col); if (! isempty (errmsg)) return; endif if (isa (col, 'categorical') && incEmpty) ## Every category is a level, in category order; codes are the proxy. cats = categories (col); L = numel (cats); idx = double (col)(:); levVals = categorical (cats(:), cats, 'Ordinal', isordinal (col)); missLvl = false (1, L); else ## Observed levels only, sorted by proxy value ascending. idx = NaN (n, 1); keep = find (! miss); if (isempty (keep)) levVals = col([], :); L = 0; missLvl = []; else [~, ia, ic] = unique (p(keep,:), "rows"); idx(keep) = ic; levVals = col(keep(ia), :); L = numel (ia); missLvl = false (1, L); endif endif ## A missing value forms one extra level, sorted last, when included. if (any (miss)) if (incMiss) L = L + 1; idx(miss) = L; mrow = find (miss, 1); levVals = [levVals; col(mrow, :)]; missLvl = [missLvl, true]; else idx(miss) = NaN; endif endif endfunction ## Group the rows of one 'pivot' dimension (rows or columns) defined by the ## grouping-variable columns GRPCOLS (a cell array, empty for an omitted ## dimension). N is the table height. Returns GID, an n-by-1 group index per ## row (NaN when the row is excluded), NG, the number of groups, LVLOF, an ## ng-by-K matrix of per-variable level indices for each group, LEVVALS, a ## 1-by-K cell of the per-variable typed level values from 'pivot_levels', ## MISSLVLS, a 1-by-K cell of the per-variable missing-level logical flags, and ## an errmsg body (empty on success). When INCEMPTY is true the groups span the ## full Cartesian product of the variables' levels (so unused combinations appear ## as empty groups); otherwise only the observed combinations are kept, sorted in ## ascending level order with the first variable varying slowest. function [gid, ng, lvlOf, levVals, missLvls, errmsg] = ... pivot_dimension (grpCols, n, incMiss, incEmpty) errmsg = ''; K = numel (grpCols); if (K == 0) ## An omitted dimension is a single group holding every row. gid = ones (n, 1); ng = 1; lvlOf = zeros (1, 0); levVals = {}; missLvls = {}; return; endif idxAll = NaN (n, K); levVals = cell (1, K); missLvls = cell (1, K); sizes = zeros (1, K); for j = 1:K [idx, lv, ml, errmsg] = pivot_levels (grpCols{j}, incMiss, incEmpty); if (! isempty (errmsg)) gid = []; ng = 0; lvlOf = []; return; endif idxAll(:,j) = idx; levVals{j} = lv; missLvls{j} = ml; sizes(j) = size (lv, 1); endfor gid = NaN (n, 1); if (incEmpty) ## Full Cartesian product, first variable slowest (most significant). ng = prod (sizes); lvlOf = ones (ng, K); period = 1; for j = K:-1:1 lvlOf(:,j) = mod (floor ((0:ng-1)' / period), sizes(j)) + 1; period = period * sizes(j); endfor valid = all (! isnan (idxAll), 2); lin = zeros (n, 1); period = 1; for j = K:-1:1 col = idxAll(:,j); col(isnan (col)) = 1; lin = lin + (col - 1) * period; period = period * sizes(j); endfor gid(valid) = lin(valid) + 1; else ## Observed combinations only, in ascending level order. valid = all (! isnan (idxAll), 2); if (! any (valid)) ng = 0; lvlOf = zeros (0, K); return; endif [u, ~, ic] = unique (idxAll(valid,:), "rows"); ng = size (u, 1); lvlOf = u; gid(valid) = ic; endif endfunction ## Map a scalar grouping value VAL to the character vector used as a 'pivot' ## column variable name (or part of one). Logical values render as 'true' or ## 'false', numeric values through 'num2str', and text, categorical, datetime, ## duration, and calendarDuration values through their displayed text. A missing ## value renders as '' so the resulting name is never empty. function s = pivot_value_name (val) if (isa (val, 'categorical')) if (ismissing (val)) s = ''; else c = cellstr (val); s = c{1}; endif elseif (isa (val, 'string')) if (ismissing (val)) s = ''; else s = char (val); endif elseif (iscellstr (val)) s = val{1}; elseif (ischar (val)) s = val; elseif (islogical (val)) if (val) s = 'true'; else s = 'false'; endif elseif (isa (val, 'datetime') || isa (val, 'duration') ... || isa (val, 'calendarDuration')) s = char (val); elseif (isnumeric (val)) s = num2str (val); else s = char (val); endif if (isempty (s)) s = ''; endif endfunction ## Build nested-table column variables for pivot 'OutputFormat','nested': group ## the flat data columns COLS (1-by-nC) by the column-grouping level hierarchy in ## LVLOF (nC-by-K level indices), producing one outer variable per level of the ## first column grouping variable -- each a nested table (recursively) whose ## variables are the next grouping variable's levels; the innermost level holds ## the data columns. LEVVALS/MISSLVLS/GNAMES supply the level labels. function [vars, names] = pivot_nest (cols, lvlOf, levVals, missLvls, gnames) [vars, names] = pivot_nest_level (cols, lvlOf, 1, levVals, missLvls, gnames); endfunction function [vars, names] = pivot_nest_level (cols, lvlOf, p, levVals, missLvls, gnames) K = size (lvlOf, 2); vars = {}; names = {}; for L = unique (lvlOf(:,p)(:)') mask = lvlOf(:,p) == L; if (missLvls{p}(L)) nm = sprintf ("", gnames{p}); else nm = pivot_value_name (levVals{p}(L,:)); endif if (p == K) sub = cols(mask); vars{end+1} = sub{1}; # leaf: one data column per full tuple else [sv, sn] = pivot_nest_level (cols(mask), lvlOf(mask,:), p + 1, ... levVals, missLvls, gnames); vars{end+1} = table (sv{:}, 'VariableNames', sn); endif names{end+1} = nm; endfor endfunction ## Return the value 'pivot' places in an empty cell for the named method M. function val = pivot_empty_value (m) if (any (strcmp (m, {'sum', 'nnz', 'nummissing', 'numunique'}))) val = 0; else val = NaN; endif endfunction ## Return a one-row missing value MV of the same type as X for 'pivot' Method ## 'none' empty cells; OK is false when the type has no missing value. function [mv, ok] = pivot_missing_scalar (x) ok = true; mv = []; mm = any (gs_missing_mask (x), 2); if (any (mm)) mv = x(find (mm, 1), :); elseif (isnumeric (x)) mv = nan (1, size (x, 2)); else ok = false; endif endfunction ## Build one 'pivot' output column for Method 'none' (rearrange without ## aggregating): column C of the pivot, length NR, of the same type as DATAVALS. ## Each cell must hold at most one value; an empty cell is filled with a missing ## value. RGID and CGID are the per-row group indices. Returns an errmsg body ## (empty on success) emitted by the caller. function [col, errmsg] = pivot_none_column (dataVals, rGid, cGid, c, nR) errmsg = ''; col = []; srcRow = ones (nR, 1); hasVal = false (nR, 1); for r = 1:nR rows = find (rGid == r & cGid == c); if (numel (rows) > 1) errmsg = strcat ("Method 'none' allows at most one value per cell, but", ... " a group holds several; specify an aggregating method"); return; elseif (numel (rows) == 1) srcRow(r) = rows; hasVal(r) = true; endif endfor col = dataVals(srcRow, :); if (! all (hasVal)) [mv, ok] = pivot_missing_scalar (dataVals); if (! ok) errmsg = sprintf (strcat ("Method 'none' with empty cells is not", ... " supported for data of type '%s'"), ... class (dataVals)); col = []; return; endif col(! hasVal, :) = mv; endif endfunction ## Build the cellstr RowNames for a 'pivot' with RowLabelPlacement 'rownames' ## from the row-label columns ROWLABELCOLS (a 1-by-K cell of typed columns), ## joining the per-variable displayed values of each row with '_'. function rn = pivot_row_names (rowLabelCols) K = numel (rowLabelCols); nR = size (rowLabelCols{1}, 1); rn = cell (nR, 1); for r = 1:nR parts = cell (1, K); for j = 1:K parts{j} = pivot_value_name (rowLabelCols{j}(r, :)); endfor rn{r} = strjoin (parts, '_'); endfor endfunction ## Append the 'Overall_' totals label LABEL as one extra element of the ## row-label column COL for 'pivot' IncludeTotals, keeping its type. OK is false ## when COL cannot hold the text label (a numeric, datetime, or duration row ## label), so the caller falls back to row-name labelling. function [col, ok] = pivot_append_label (col, label) ok = true; if (isa (col, 'categorical')) if (! iscategory (col, label)) col = addcats (col, label); endif col(end+1, 1) = label; elseif (isa (col, 'string')) col(end+1, 1) = label; elseif (iscellstr (col)) col(end+1, 1) = {label}; else ok = false; endif endfunction ## Validate an 'IncludedEdge' binning option VAL for method CALLER, returning it ## lowercased as 'left' or 'right'. function e = check_included_edge (caller, val) if (isa (val, 'string') && isscalar (val)) val = char (val); endif if (! (ischar (val) && isrow (val) ... && any (strcmpi (val, {'left', 'right'})))) error ("table.%s: 'IncludedEdge' must be 'left' or 'right'.", caller); endif e = lower (val); endfunction ## Validate a logical-scalar 'pivot' option VAL named NAME, returning it as a ## logical scalar. function tf = pivot_logical_opt (name, val) if (! (isscalar (val) && (islogical (val) || isnumeric (val)))) error ("table.pivot: '%s' must be a logical scalar.", name); endif tf = logical (val); endfunction ## Compute one 'pivot' cell value: apply METHOD to the data of the rows ROWS of ## one row-and-column group. HASDV flags whether a data variable is given; ## DATAVALS holds its values. 'count' counts rows (or, with a data variable, the ## non-missing data values), 'percentage' is the row count as a percentage of ## TOTALASSIGNED, an empty cell takes the named method's empty value, and every ## other named method or function handle is applied through 'gs_apply_method'. ## Returns an errmsg body (empty on success) emitted by the caller. function [v, errmsg] = pivot_cell_value (method, hasDV, dataVals, rows, ... totalAssigned) errmsg = ''; if (ischar (method) && strcmp (method, 'count')) if (! hasDV) v = numel (rows); else v = sum (! any (gs_missing_mask (dataVals(rows,:)), 2)); endif elseif (ischar (method) && strcmp (method, 'percentage')) v = 100 * numel (rows) / totalAssigned; elseif (isempty (rows)) ## An empty cell takes the method's empty value; a function handle is not ## invoked on an empty slice, the cell is left missing. if (ischar (method)) v = pivot_empty_value (method); else v = NaN; endif else [v, errmsg] = gs_apply_method (method, dataVals(rows,:)); endif endfunction ## Return the name used to prefix 'varfun' output variables: the name of FUNC, ## or 'Fun' when FUNC is an anonymous function handle. function fname = apply_func_name (func) fstr = func2str (func); if (isempty (fstr) || fstr(1) == '@') fname = 'Fun'; else fname = fstr; endif endfunction ## Validate and normalise an OutputFormat value for the apply methods: map ## 'auto' to 'table' and accept 'table', 'uniform', and 'cell'. CALLER names ## the method for error messages. function fmt = check_output_format (caller, fmt) if (isa (fmt, 'string')) fmt = char (fmt); endif if (! (ischar (fmt) && isrow (fmt))) error ("table.%s: 'OutputFormat' must be a character vector.", caller); endif switch (lower (fmt)) case {'auto', 'table'} fmt = 'table'; case 'uniform' fmt = 'uniform'; case 'cell' fmt = 'cell'; case 'timetable' error ("table.%s: 'timetable' OutputFormat is not supported.", caller); otherwise error ("table.%s: invalid 'OutputFormat' value '%s'.", caller, fmt); endswitch endfunction ## Build the cell array of input arguments passed to FUNC for the rows selected ## by the logical mask ROWS, taken from the input-variable values INCOLS (a cell ## array of variable values). When SEPIN is true each variable's selected rows ## form a separate argument; otherwise they are horizontally concatenated into a ## single argument. When EXTRACTCELL is true the contents of cell-valued ## variables are extracted. function args = build_row_args (inCols, rows, sepIn, extractCell) vals = cell (1, numel (inCols)); for k = 1:numel (inCols) col = inCols{k}; if (extractCell && iscell (col)) sub = col(rows); if (numel (sub) == 1) vals{k} = sub{1}; else vals{k} = vertcat (sub{:}); endif else vals{k} = col(rows,:); endif endfor if (sepIn) args = vals; else args = {horzcat(vals{:})}; endif endfunction ## Call FUNC with the arguments ARGS, requesting NOUT outputs, and return them ## in a 1-by-max(NOUT,1) cell row. When ERRHANDLER is non-empty it is called ## with a struct describing any error thrown by FUNC (fields 'identifier', ## 'message', and 'index' set to IDX) followed by ARGS, and its outputs are ## used instead. function out = apply_func (func, errHandler, idx, nout, args) out = cell (1, max (nout, 1)); if (isempty (errHandler)) [out{1:nout}] = func (args{:}); else try [out{1:nout}] = func (args{:}); catch err S = struct ('identifier', err.identifier, 'message', err.message, ... 'index', idx); [out{1:nout}] = errHandler (S, args{:}); end_try_catch endif endfunction ## Assemble the output of an apply method from the R-by-C cell array of per-row ## (or per-group) results RES with output names OUTNAMES. For grouped output ## the grouping columns GCOLS (named GNAMES) and the GCOUNT counts are ## prepended; for ungrouped output these are empty. FMT selects the 'table', ## 'uniform', or 'cell' return format; CALLER names the method for error ## messages. function out = build_apply_result (caller, fmt, res, outNames, gcols, ... gnames, gcount, rowNames) if (nargin < 8) rowNames = {}; endif C = size (res, 2); switch (fmt) case 'table' rescols = cell (1, C); for c = 1:C rescols{c} = vertcat (res{:,c}); endfor if (isempty (gcols) && isempty (gcount)) vars = rescols; names = outNames; else vars = [gcols, {gcount}, rescols]; names = [gnames, {'GroupCount'}, outNames]; endif if (isempty (rowNames)) out = table (vars{:}, 'VariableNames', names); else out = table (vars{:}, 'VariableNames', names, 'RowNames', rowNames); endif case 'uniform' out = []; for c = 1:C colvals = res(:,c); if (! all (cellfun (@isscalar, colvals))) error (strcat ("table.%s: OutputFormat 'uniform' requires FUNC", ... " to return a scalar for each call."), caller); endif out = [out, vertcat(colvals{:})]; endfor case 'cell' out = res; endswitch endfunction ## Assemble the output of a grouped 'rowfun' or 'varfun' from the NG-by-C cell ## array of per-group results RES. Unlike an aggregating apply, FUNC may return ## several rows for a group; each group g therefore contributes ## 'size (RES{g,1}, 1)' rows and the grouping columns GCOLS (named GNAMES) and the ## GCOUNT counts are replicated to match before the per-group results are ## stacked. FMT selects the 'table', 'uniform', or 'cell' return format; CALLER ## names the method for error messages. function out = build_grouped_apply_result (caller, fmt, res, outNames, gcols, ... gnames, gcount) ng = size (res, 1); C = size (res, 2); switch (fmt) case 'table' repIdx = []; for g = 1:ng repIdx = [repIdx; repmat(g, size (res{g,1}, 1), 1)]; endfor rescols = cell (1, C); for c = 1:C rescols{c} = vertcat (res{:,c}); endfor gcolsR = cell (1, numel (gcols)); for p = 1:numel (gcols) gcolsR{p} = gcols{p}(repIdx,:); endfor vars = [gcolsR, {gcount(repIdx)}, rescols]; names = [gnames, {'GroupCount'}, outNames]; out = table (vars{:}, 'VariableNames', names); case 'uniform' out = []; for c = 1:C colvals = res(:,c); if (! all (cellfun (@isscalar, colvals))) error (strcat ("table.%s: OutputFormat 'uniform' requires FUNC", ... " to return a scalar for each call."), caller); endif out = [out, vertcat(colvals{:})]; endfor case 'cell' out = res; endswitch endfunction ## Detect the cell/non-cell mix of variable values VALS that cannot form a ## homogeneous array. Returns the column indices [LO, HI] (in column order) of ## the first cell and first non-cell variable, or [] when VALS are not such a ## mix. Callers emit the incompatibility error under their own method name. function pair = mixed_cell_pair (vals) isCellVar = cellfun (@iscell, vals); if (any (isCellVar) && ! all (isCellVar)) pair = sort ([find(isCellVar, 1), find(! isCellVar, 1)]); else pair = []; endif endfunction ## Set the rows of a variable V selected by the logical MASK to the standard ## missing value for V's type. Returns an errmsg body for unsupported types. function [v, errmsg] = set_var_missing (v, mask) errmsg = ''; if (! any (mask)) return; endif if (isa (v, 'string')) v(mask) = string (missing); elseif (isa (v, 'categorical')) v(mask) = categorical (missing); elseif (isa (v, 'datetime')) v(mask) = NaT; elseif (isa (v, 'duration')) v(mask) = missing; elseif (isa (v, 'calendarDuration')) v(mask,:) = NaN; elseif (iscellstr (v)) v(mask) = {''}; elseif (islogical (v)) v(mask,:) = false; elseif (isinteger (v)) v(mask,:) = 0; elseif (isfloat (v)) v(mask,:) = NaN; else errmsg = sprintf (strcat ("cannot create missing values for a variable", ... " of type '%s'."), class (v)); endif endfunction ## Create an N-row array of standard missing values matching the type and width ## of PROTO. Used when one input table has no rows to replicate from. Returns ## an errmsg body for unsupported types. function [col, errmsg] = missing_rows (proto, n) errmsg = ''; col = []; w = max (size (proto, 2), 1); if (isa (proto, 'string')) col = repmat (string (missing), n, w); elseif (isa (proto, 'categorical')) col = repmat (categorical (missing), n, w); elseif (isa (proto, 'datetime')) col = repmat (NaT, n, w); elseif (isa (proto, 'duration')) col = hours (NaN (n, w)); elseif (isa (proto, 'calendarDuration')) col = calmonths (NaN (n, w)); elseif (iscellstr (proto)) col = repmat ({''}, n, w); elseif (islogical (proto)) col = false (n, w); elseif (isinteger (proto)) col = zeros (n, w, class (proto)); elseif (isfloat (proto)) col = NaN (n, w); else errmsg = sprintf (strcat ("cannot create missing values for a variable", ... " of type '%s'."), class (proto)); endif endfunction ## Map a variable type name to the ODS cell value type used by 'table2ods'. ## Numeric types become 'float', logical becomes 'boolean', datetime and ## duration map to the native 'date' and 'time' types, and everything else ## (text, categorical, calendarDuration, cell) is written as a 'string'. function vt = ods_value_type (typestr) ## A zone-aware datetime carries its TimeZone in the type ('datetime '). if (strncmp (typestr, 'datetime', 8)) vt = 'date'; return; endif switch (typestr) case 'logical' vt = 'boolean'; case 'datetime' vt = 'date'; case 'duration' vt = 'time'; case {'double', 'single', 'int8', 'int16', 'int32', 'int64', ... 'uint8', 'uint16', 'uint32', 'uint64'} vt = 'float'; otherwise vt = 'string'; endswitch endfunction ## Add, replace, or append table T to the struct of tables S (read from an ## existing house workbook) for the sheet named SHEET, per WRITEMODE. The ## struct is later written back with 'struct2ods'; sheet names that are not ## valid field names ride along as the 'ActualSheetName' custom property. function s = merge_table_into_struct (s, T, sheet, writeMode) ## Find the field whose sheet name (ActualSheetName, else field name) matches. fields = fieldnames (s); targetField = ''; for i = 1:numel (fields) fsheet = fields{i}; cp = s.(fields{i}).Properties.CustomProperties; if (isstruct (cp) && isfield (cp, 'ActualSheetName') ... && ! isempty (cp.ActualSheetName)) fsheet = cp.ActualSheetName; endif if (strcmp (fsheet, sheet)) targetField = fields{i}; break; endif endfor if (strcmp (writeMode, 'append') && ! isempty (targetField)) ## Append the rows; table vertcat errors if the variables are incompatible. combined = [s.(targetField); T]; s.(targetField) = copy_actual_sheet_name (combined, s.(targetField)); elseif (! isempty (targetField)) ## Replace the sheet, keeping its resolved name. s.(targetField) = copy_actual_sheet_name (T, s.(targetField)); else ## A new sheet: canonicalise SHEET to a unique field name and stash the ## original name when it had to change. fn = matlab.lang.makeValidName (sheet); base = fn; j = 1; while (isfield (s, fn)) fn = sprintf ("%s_%d", base, j); j += 1; endwhile if (! strcmp (fn, sheet)) T = addprop (T, 'ActualSheetName', 'table'); T.Properties.CustomProperties.ActualSheetName = sheet; endif s.(fn) = T; endif endfunction ## Copy the 'ActualSheetName' custom property from SRC onto T, if SRC carries it. function T = copy_actual_sheet_name (T, src) cp = src.Properties.CustomProperties; if (isstruct (cp) && isfield (cp, 'ActualSheetName') ... && ! isempty (cp.ActualSheetName)) tcp = T.Properties.CustomProperties; if (! (isstruct (tcp) && isfield (tcp, 'ActualSheetName'))) T = addprop (T, 'ActualSheetName', 'table'); endif T.Properties.CustomProperties.ActualSheetName = cp.ActualSheetName; endif endfunction ## Prepare the flat value/name/type cell arrays produced by 'table2cellarrays' ## for the MATLAB-compatible 'writetable' output: strip or keep the leading row ## names column (which carries an empty variable name) per WRITEROWNAMES, and ## de-duplicate the shared names of a multicolumn variable with _1, _2, ... ## suffixes, matching MATLAB. function [names, V, T] = writetable_prep (V, N, T, writeRowNames) hasRN = (! isempty (N) && isempty (N{1})); rnCol = {}; if (hasRN) rnCol = V(:,1); V(:,1) = []; N(:,1) = []; T(:,1) = []; endif names = {}; c = 1; n = numel (N); while (c <= n) c2 = c; while (c2 < n && strcmp (N{c2+1}, N{c})) c2 += 1; endwhile k = c2 - c + 1; if (k == 1) names{end+1} = N{c}; else for j = 1:k names{end+1} = sprintf ("%s_%d", N{c}, j); endfor endif c = c2 + 1; endwhile if (writeRowNames && hasRN) V = [rnCol, V]; names = [{'Row'}, names]; T = [{'cellstr'}, T]; endif endfunction ## Translate a MATLAB delimiter (named or literal) into a single character for ## 'writetable'. function d = wt_resolve_delimiter (delim) if (isa (delim, 'string')) delim = char (delim); endif if (! ischar (delim)) error ("table.writetable: 'Delimiter' must be a character vector or string."); endif switch (lower (delim)) case {'comma', ','} d = ','; case {'space', ' '} d = ' '; case {'tab', "\t"} d = "\t"; case {'semi', ';'} d = ';'; case {'bar', '|'} d = '|'; otherwise if (isscalar (delim)) d = delim; else error ("table.writetable: unsupported 'Delimiter' value '%s'.", delim); endif endswitch endfunction ## Format a datetime column as a column cell of ISO 8601 strings for 'table2ods'. ## NaT values yield an empty string, which the writer records as a missing (empty) ## cell. The wall-clock components are used; any TimeZone is not encoded in the ## value (mirroring the datetime display round-trip of the CSV path). function C = datetime2iso (dt) [Y, M, D] = ymd (dt(:)); [h, m, s] = hms (dt(:)); n = numel (Y); C = cell (n, 1); for i = 1:n if (isnan (Y(i))) C{i} = ''; else C{i} = sprintf ("%04d-%02d-%02dT%02d:%02d:%s", ... Y(i), M(i), D(i), h(i), m(i), iso_seconds (s(i))); endif endfor endfunction ## Format a duration column as a column cell of ISO 8601 duration strings ## (@code{PTnHnMnS}) for 'table2ods'. NaN values yield an empty string (written ## as a missing cell). Hours are not wrapped at 24, so durations of any ## magnitude are preserved; negative durations carry a leading minus sign. function C = duration2iso (du) tot = seconds (du(:)); n = numel (tot); C = cell (n, 1); for i = 1:n if (isnan (tot(i))) C{i} = ''; else a = abs (tot(i)); H = floor (a / 3600); MI = floor (mod (a, 3600) / 60); S = mod (a, 60); sgn = ''; if (tot(i) < 0) sgn = '-'; endif C{i} = sprintf ("%sPT%dH%dM%sS", sgn, H, MI, iso_seconds (S)); endif endfor endfunction ## Format a seconds value for an ISO 8601 string: a two-digit integer when whole, ## otherwise a fractional part (up to microseconds) with trailing zeros trimmed. function str = iso_seconds (s) si = floor (s); frac = round ((s - si) * 1e6); if (frac >= 1e6) # rounded up to a whole second si += 1; frac = 0; endif if (frac == 0) str = sprintf ("%02d", si); else fs = regexprep (sprintf ("%06d", frac), '0+$', ''); str = sprintf ("%02d.%s", si, fs); endif endfunction pr0m1th3as-datatypes-9c9a8d3/inst/tests/000077500000000000000000000000001522766574100201745ustar00rootroot00000000000000pr0m1th3as-datatypes-9c9a8d3/inst/tests/calendarDuration.m-tst000066400000000000000000002177401522766574100244540ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the datatypes package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ################################################################################ ## ** Create and convert 'calendarDuration' type ** ## ################################################################################ ## Available Methods ## ## ## ## 'duration' 'dispstrings' 'cellstr' 'char' ## ## 'datevec' 'time' 'split' 'caldays' ## ## 'calweeks' 'calmonths' 'calquarters' 'calyears' ## ## ## ################################################################################ %!assert_equal (isscalar (calendarDuration ()), true) %!assert_equal (isempty (calendarDuration ()), false) %!assert_equal (class (calendarDuration), 'calendarDuration') %!assert_equal (dispstrings (calendarDuration ()), {'0d'}) %!assert_equal (size (calendarDuration (zeros (4, 3))), [4, 1]) %!assert_equal (size (calendarDuration (ones (5, 6))), [5, 1]) %!test %! X = calendarDuration ([0, 0, 1]); %! assert_equal (caldays (X), 1); %! assert_equal (dispstrings (X), {'1d'}); %! assert_equal (char (X), '1d'); %!test %! X = calendarDuration ([0, 2, 14]); %! assert_equal (caldays (X), 14); %! assert_equal (calweeks (X), 2); %! assert_equal (calmonths (X), 2); %! assert_equal (char (X), '2mo 14d'); %!test %! X = calendarDuration ([1, 5, 15]); %! assert_equal (caldays (X), 15); %! assert_equal (calweeks (X), 2); %! assert_equal (calmonths (X), 17); %! assert_equal (calquarters (X), 5); %! assert_equal (dispstrings (X), {'1y 5mo 15d'}); %! assert_equal (char (X, 'mdt'), '17mo 15d'); %! assert_equal (cellstr (X, 'mwdt'), {'17mo 2w 1d'}); %!test %! X = calendarDuration ([1, 0, 0, 12, 30, 6.5]); %! assert_equal (caldays (X), 0); %! assert_equal (calweeks (X), 0); %! assert_equal (calmonths (X), 12); %! assert_equal (calquarters (X), 4); %! assert_equal (dispstrings (X), {'1y 12h 30m 6.5s'}); %! assert_equal (class (time (X)), 'duration'); %! assert_equal (hours (time (X)), 12.5018, 1e-4); %! assert_equal (minutes (time (X)), 750.1083, 1e-4); %! assert_equal (seconds (time (X)), 45006.5); %!assert_equal (size (calendarDuration ([1, 2; 9, 8], 0, 1)), [2, 2]) %!assert_equal (size (calendarDuration (1, zeros (2, 3, 4), 1)), [2, 3, 4]) %!test %! X = calendarDuration (0, 0, 1); %! assert_equal (caldays (X), 1); %! assert_equal (dispstrings (X), {'1d'}); %! assert_equal (char (X), '1d'); %!test %! X = calendarDuration ([0, 2, 14]); %! assert_equal (caldays (X), 14); %! assert_equal (calweeks (X), 2); %! assert_equal (calmonths (X), 2); %! assert_equal (char (X), '2mo 14d'); %!test %! X = calendarDuration (1, 5, 15); %! assert_equal (caldays (X), 15); %! assert_equal (calweeks (X), 2); %! assert_equal (calmonths (X), 17); %! assert_equal (calquarters (X), 5); %! assert_equal (dispstrings (X), {'1y 5mo 15d'}); %! assert_equal (char (X, 'mdt'), '17mo 15d'); %! assert_equal (cellstr (X, 'mwdt'), {'17mo 2w 1d'}); %!test %! X = calendarDuration ([1;NaN], 2, 3); %! assert_equal (char (X(1)), '1y 2mo 3d'); %! assert_equal (char (X(2)), 'NaN'); %!assert_equal (size (calendarDuration (1, 2, 1, days (ones (2, 3, 4)))), [2, 3, 4]) %!test %! X = calendarDuration (1, 0, 0, duration (12, 30, 6.5)); %! assert_equal (caldays (X), 0); %! assert_equal (calweeks (X), 0); %! assert_equal (calmonths (X), 12); %! assert_equal (calquarters (X), 4); %! assert_equal (dispstrings (X), {'1y 12h 30m 6.5s'}); %! assert_equal (class (time (X)), 'duration'); %! assert_equal (hours (time (X)), 12.5018, 1e-4); %! assert_equal (minutes (time (X)), 750.1083, 1e-4); %! assert_equal (seconds (time (X)), 45006.5); %!test %! X = calendarDuration (1, 0, 0, 12, 30, 6.5); %! assert_equal (caldays (X), 0); %! assert_equal (calweeks (X), 0); %! assert_equal (calmonths (X), 12); %! assert_equal (calquarters (X), 4); %! assert_equal (dispstrings (X), {'1y 12h 30m 6.5s'}); %! assert_equal (class (time (X)), 'duration'); %! assert_equal (hours (time (X)), 12.5018, 1e-4); %! assert_equal (minutes (time (X)), 750.1083, 1e-4); %! assert_equal (seconds (time (X)), 45006.5); %!test %! X = calendarDuration ([1, NaN], 0, 0, 12, 30, 6.5); %! assert_equal (caldays (X(1)), 0); %! assert_equal (calweeks (X(1)), 0); %! assert_equal (calmonths (X(1)), 12); %! assert_equal (calquarters (X(1)), 4); %! assert_equal (dispstrings (X(1)), {'1y 12h 30m 6.5s'}); %! assert_equal (class (time (X(1))), 'duration'); %! assert_equal (hours (time (X(1))), 12.5018, 1e-4); %! assert_equal (minutes (time (X(1))), 750.1083, 1e-4); %! assert_equal (seconds (time (X(1))), 45006.5); %! assert_equal (caldays (X(2)), NaN); %! assert_equal (calweeks (X(2)), NaN); %! assert_equal (calmonths (X(2)), NaN); %! assert_equal (calquarters (X(2)), NaN); %! assert_equal (char (X(2)), 'NaN'); %! assert_equal (class (time (X(2))), 'duration'); %! assert_equal (hours (time (X(2))), NaN); %! assert_equal (minutes (time (X(2))), NaN); %! assert_equal (seconds (time (X(2))), NaN); %!error ... %! calendarDuration ([0, 0, 1], 'Format', 1) %!error ... %! calendarDuration ([0, 0, 1], 'Format', {'YY-MM-DD'}) %!error ... %! calendarDuration ([0, 0, 1], 'Format', 'yymd') %!error ... %! calendarDuration ([0, 0, 1], 'Format', 'ymd') %!error ... %! calendarDuration ([0, 0, 1], 'Format', 'mtd') %!error calendarDuration ({0, 0}) %!error calendarDuration ('asd') %!error calendarDuration (1+i) %!error ... %! calendarDuration ([0.5, 1, 1]) %!error ... %! calendarDuration ([1, 0.5, 1]) %!error ... %! calendarDuration ([1, 1, 0.5]) %!error ... %! calendarDuration ([0.5, 0, 0, 0, 0, 0]) %!error ... %! calendarDuration ([0, 0.5, 0, 0, 0, 0]) %!error ... %! calendarDuration ([0, 0, 0.5, 0, 0, 0]) %!error ... %! calendarDuration ([0, 0, 0, 0.5, 0, 0]) %!error ... %! calendarDuration ([0, 0, 0, 0, 0.5, 0]) %!error ... %! calendarDuration ([1, 1]) %!error ... %! calendarDuration ([1, 1, 1, 0.5, 0]) %!error ... %! calendarDuration ('a', 2, 3) %!error ... %! calendarDuration (1, {2}, 3) %!error ... %! calendarDuration (1, 2, {'s'}) %!error calendarDuration (i, 2, 3) %!error calendarDuration (1, i, 3) %!error calendarDuration (1, 2, i) %!error ... %! calendarDuration ([1, 2], 2, [1; 2]) %!error ... %! calendarDuration (1.2, 2, 3) %!error ... %! calendarDuration (1, 2.5, 3) %!error ... %! calendarDuration (1, 2, 0.3) %!error ... %! calendarDuration ('a', 2, 3, days (1)) %!error ... %! calendarDuration (1, {2}, 3, days (1)) %!error ... %! calendarDuration (1, 2, {'s'}, days (1)) %!error ... %! calendarDuration (i, 2, 3, days (1)) %!error ... %! calendarDuration (1, i, 3, days (1)) %!error ... %! calendarDuration (1, 2, i, days (1)) %!error ... %! calendarDuration (1, 2, 3, 2) %!error ... %! calendarDuration ([1, 2], 2, 1, days ([1; 2])) %!error ... %! calendarDuration (1.2, 2, 3, days (1)) %!error ... %! calendarDuration (1, 2.5, 3, days (1)) %!error ... %! calendarDuration (1, 2, 0.3, days (1)) %!error ... %! calendarDuration ('a', 0, 0, 12, 30, 6.5) %!error ... %! calendarDuration (1, {0}, 0, 12, 30, 6.5) %!error ... %! calendarDuration (1, 0, string (0), 12, 30, 6.5) %!error ... %! calendarDuration (1, 0, 0, {12}, 30, 6.5) %!error ... %! calendarDuration (1, 0, 0, 12, '30', 6.5) %!error ... %! calendarDuration (1, 0, 0, 12, 30, struct ('s', 6.5)) %!error ... %! calendarDuration (i, 0, 0, 12, 30, 6.5) %!error ... %! calendarDuration (1, i, 0, 12, 30, 6.5) %!error ... %! calendarDuration (1, 0, i, 12, 30, 6.5) %!error ... %! calendarDuration (1, 0, 0, i, 30, 6.5) %!error ... %! calendarDuration (1, 0, 0, 12, i, 6.5) %!error ... %! calendarDuration (1, 0, 0, 12, 30, i) %!error ... %! calendarDuration ([1, 2], 0, [0; 0], 12, 30, 6.5) %!error ... %! calendarDuration (1.2, 0, 1, 12, 30, 6.5) %!error ... %! calendarDuration (1, [0.1, NaN], 1, 12, 30, 6.5) %!error ... %! calendarDuration (1, 0, 1.5, 12, 30, 6.5) %!error ... %! calendarDuration (1, 0, 1, 1.2, 30, 6.5) %!error ... %! calendarDuration (1, 0, 1, 12, 29.5, 6.5) %!error ... %! calendarDuration (1, [0.1, NaN]) %!error ... %! calendarDuration (1, [0.1, NaN], 1, 12, 30) %!assert_equal (dispstrings (calendarDuration ([0, 0, NaN])), {'NaN'}) %!assert_equal (dispstrings (calendarDuration ([0, 0, Inf])), {'Inf'}) %!assert_equal (dispstrings (calendarDuration ([0, NaN, Inf])), {'NaN'}) %!assert_equal (dispstrings (calendarDuration ([0, 0, -Inf])), {'-Inf'}) %!assert_equal (dispstrings (calendarDuration ([0, Inf, -Inf])), {'NaN'}) %!assert_equal (char (calendarDuration (0, 0, NaN)), 'NaN') %!assert_equal (char (calendarDuration (0, 0, Inf)), 'Inf') %!assert_equal (char (calendarDuration (0, NaN, Inf)), 'NaN') %!assert_equal (char (calendarDuration (0, 0, -Inf)), '-Inf') %!assert_equal (char (calendarDuration (0, Inf, -Inf)), 'NaN') %!assert_equal (dispstrings (calendarDuration (1, 3, 1, 'Format', 'mwdt')), {'15mo 1d'}) %!assert_equal (dispstrings (calendarDuration (1, 3, 1, 'Format', 'ymwdt')), {'1y 3mo 1d'}) %!assert_equal (dispstrings (calendarDuration (1, 3, 15, 'Format', 'ymwdt')), {'1y 3mo 2w 1d'}) %!assert_equal (dispstrings (calendarDuration (1, 3, 15, 'Format', 'mwdt')), {'15mo 2w 1d'}) %!assert_equal (dispstrings (calendarDuration (1, 3, 15, 'Format', 'ymdt')), {'1y 3mo 15d'}) %!assert_equal (dispstrings (calendarDuration (1, 3, 15, 'Format', 'mdt')), {'15mo 15d'}) %!assert_equal (dispstrings (calendarDuration (0, 15, 15, 'Format', 'ymdt')), {'1y 3mo 15d'}) %!assert_equal (dispstrings (calendarDuration (0, 0, 0)), {'0d'}) %!assert_equal (dispstrings (calendarDuration (1, 0, 0)), {'1y'}) %!assert_equal (dispstrings (calendarDuration (0, 1, 0)), {'1mo'}) %!assert_equal (dispstrings (calendarDuration (0, 12, 0)), {'1y'}) %!assert_equal (dispstrings (calendarDuration (0, 0, 1)), {'1d'}) %!assert_equal (dispstrings (calendarDuration (0, 0, 14)), {'14d'}) %!assert_equal (dispstrings (calendarDuration (0, 0, 14, 'Format', 'mwdt')), {'2w'}) %!assert_equal (dispstrings (calendarDuration (0, 1, 0, minutes (5))), {'1mo 0h 5m 0s'}) %!assert_equal (dispstrings (calendarDuration (0, 0, 0, minutes (5))), {'0h 5m 0s'}) %!assert_equal (dispstrings (calendarDuration (0, 0, 0, minutes (50.05))), {'0h 50m 3s'}) %!assert_equal (dispstrings (calendarDuration (0, 0, 0, minutes (5.005))), {'0h 5m 0.3s'}) %!assert_equal (dispstrings (calendarDuration (0, 0, 0, minutes (5.000005))), {'0h 5m 3e-04s'}) %!assert_equal (dispstrings (calendarDuration (0, 0, 0, duration (3, 5, 0.3))), {'3h 5m 0.3s'}) %!assert_equal (cellstr (calendarDuration (1, 3, 1), 'mwdt'), {'15mo 1d'}) %!assert_equal (cellstr (calendarDuration (1, 3, 1), 'ymwdt'), {'1y 3mo 1d'}) %!assert_equal (cellstr (calendarDuration (1, 3, 15), 'ymwdt'), {'1y 3mo 2w 1d'}) %!assert_equal (cellstr (calendarDuration (1, 3, 15), 'mwdt'), {'15mo 2w 1d'}) %!assert_equal (cellstr (calendarDuration (1, 3, 15), 'ymdt'), {'1y 3mo 15d'}) %!assert_equal (cellstr (calendarDuration (1, 3, 15), 'mdt'), {'15mo 15d'}) %!assert_equal (cellstr (calendarDuration (0, 15, 15), 'ymdt'), {'1y 3mo 15d'}) %!assert_equal (cellstr (calendarDuration (0, 0, 0)), {'0d'}) %!assert_equal (cellstr (calendarDuration (1, 0, 0)), {'1y'}) %!assert_equal (cellstr (calendarDuration (0, 1, 0)), {'1mo'}) %!assert_equal (cellstr (calendarDuration (0, 12, 0)), {'1y'}) %!assert_equal (cellstr (calendarDuration (0, 0, 1)), {'1d'}) %!assert_equal (cellstr (calendarDuration (0, 0, 14)), {'14d'}) %!assert_equal (cellstr (calendarDuration (0, 0, 14), 'mwdt'), {'2w'}) %!assert_equal (cellstr (calendarDuration (0, 1, 0, minutes (5))), {'1mo 0h 5m 0s'}) %!assert_equal (cellstr (calendarDuration (0, 0, 0, minutes (5))), {'0h 5m 0s'}) %!assert_equal (cellstr (calendarDuration (0, 0, 0, minutes (50.05))), {'0h 50m 3s'}) %!assert_equal (cellstr (calendarDuration (0, 0, 0, minutes (5.005))), {'0h 5m 0.3s'}) %!assert_equal (cellstr (calendarDuration (0, 0, 0, minutes (5.000005))), {'0h 5m 3e-04s'}) %!assert_equal (cellstr (calendarDuration (0, 0, 0, duration (3, 5, 0.3))), {'3h 5m 0.3s'}) %!error ... %! cellstr (calendarDuration ([0, 0, 1]), 1) %!error ... %! cellstr (calendarDuration ([0, 0, 1]), {'YY-MM-DD'}) %!error ... %! cellstr (calendarDuration ([0, 0, 1]), 'yymd') %!error ... %! cellstr (calendarDuration ([0, 0, 1]), 'ymd') %!error ... %! cellstr (calendarDuration ([0, 0, 1]), 'mtd') %!assert_equal (char (calendarDuration (1, 3, 1), 'mwdt'), '15mo 1d') %!assert_equal (char (calendarDuration (1, 3, 1), 'ymwdt'), '1y 3mo 1d') %!assert_equal (char (calendarDuration (1, 3, 15), 'ymwdt'), '1y 3mo 2w 1d') %!assert_equal (char (calendarDuration (1, 3, 15), 'mwdt'), '15mo 2w 1d') %!assert_equal (char (calendarDuration (1, 3, 15), 'ymdt'), '1y 3mo 15d') %!assert_equal (char (calendarDuration (1, 3, 15), 'mdt'), '15mo 15d') %!assert_equal (char (calendarDuration (0, 15, 15), 'ymdt'), '1y 3mo 15d') %!assert_equal (char (calendarDuration (0, 0, 0)), '0d') %!assert_equal (char (calendarDuration (1, 0, 0)), '1y') %!assert_equal (char (calendarDuration (0, 1, 0)), '1mo') %!assert_equal (char (calendarDuration (0, 12, 0)), '1y') %!assert_equal (char (calendarDuration (0, 0, 1)), '1d') %!assert_equal (char (calendarDuration (0, 0, 14)), '14d') %!assert_equal (char (calendarDuration (0, 0, 14), 'mwdt'), '2w') %!assert_equal (char (calendarDuration (0, 1, 0, minutes (5))), '1mo 0h 5m 0s') %!assert_equal (char (calendarDuration (0, 0, 0, minutes (5))), '0h 5m 0s') %!assert_equal (char (calendarDuration (0, 0, 0, minutes (50.05))), '0h 50m 3s') %!assert_equal (char (calendarDuration (0, 0, 0, minutes (5.005))), '0h 5m 0.3s') %!assert_equal (char (calendarDuration (0, 0, 0, minutes (5.000005))), '0h 5m 3e-04s') %!assert_equal (char (calendarDuration (0, 0, 0, duration (3, 5, 0.3))), '3h 5m 0.3s') %!error ... %! char (calendarDuration ([0, 0, 1]), 1) %!error ... %! char (calendarDuration ([0, 0, 1]), {'YY-MM-DD'}) %!error ... %! char (calendarDuration ([0, 0, 1]), 'yymd') %!error ... %! char (calendarDuration ([0, 0, 1]), 'ymd') %!error ... %! char (calendarDuration ([0, 0, 1]), 'mtd') %!test %! X = calendarDuration ([1:5]', 3, 15); %! DV = datevec (X); %! assert_equal (size (X), [5, 1]); %! assert_equal (size (DV), [5, 6]); %! assert_equal (DV(:,1:3), [1, 3, 15; 2, 3, 15; 3, 3, 15; 4, 3, 15; 5, 3, 15]); %! assert_equal (DV(:,4:6), zeros (5, 3)); %!test %! X = calendarDuration ([1:5]', 3, 15); %! [Y, MO, D] = datevec (X); %! assert_equal (size (Y), [5, 1]); %! assert_equal (size (MO), [5, 1]); %! assert_equal (size (D), [5, 1]); %! assert_equal (Y, [1:5]'); %! assert_equal (MO, [3; 3; 3; 3; 3]); %! assert_equal (D, 15 * ones (5, 1)); %!test %! X = calendarDuration ([1:5]', 3, 15); %! [~, ~, ~, H, MI, S] = datevec (X); %! assert_equal (size (H), [5, 1]); %! assert_equal (size (MI), [5, 1]); %! assert_equal (size (S), [5, 1]); %! assert_equal (H, zeros (5, 1)); %! assert_equal (MI, zeros (5, 1)); %! assert_equal (S, zeros (5, 1)); %!test %! X = calendarDuration (reshape (1:24, 2, 3, 4), 0, reshape (25:48, 2, 3, 4)); %! DV = datevec (X); %! assert_equal (size (X), [2, 3, 4]); %! assert_equal (size (DV), [24, 6]); %! assert_equal (DV(:,1), [1:24]'); %! assert_equal (DV(:,2), zeros (24, 1)); %! assert_equal (DV(:,3), [25:48]'); %! assert_equal (DV(:,4:6), zeros (24, 3)); %!test %! X = calendarDuration (reshape (1:24, 2, 3, 4), 0, reshape (25:48, 2, 3, 4)); %! [Y, MO, D] = datevec (X); %! assert_equal (size (Y), [2, 3, 4]); %! assert_equal (size (MO), [2, 3, 4]); %! assert_equal (size (D), [2, 3, 4]); %! assert_equal (Y, reshape (1:24, 2, 3, 4)); %! assert_equal (MO, zeros (2, 3, 4)); %! assert_equal (D, reshape (25:48, 2, 3, 4)); %!test %! X = calendarDuration (reshape (1:24, 2, 3, 4), 0, reshape (25:48, 2, 3, 4)); %! [~, ~, ~, H, MI, S] = datevec (X); %! assert_equal (size (H), [2, 3, 4]); %! assert_equal (size (MI), [2, 3, 4]); %! assert_equal (size (S), [2, 3, 4]); %! assert_equal (H, zeros (2, 3, 4)); %! assert_equal (MI, zeros (2, 3, 4)); %! assert_equal (S, zeros (2, 3, 4)); %!test %! D = days (0.51); %! X = calendarDuration (1, 0, 0, D); %! [Y, ~, ~, H, MI, S] = datevec (X); %! [h, mi, s] = hms (D); %! assert_equal (Y, 1); %! assert_equal (H, h); %! assert_equal (MI, mi); %! assert_equal (S, s); %!test %! D = days (repmat ([0.51, 0.46; 0.2, 1.23], 1, 2, 3)); %! X = calendarDuration (1, 0, 0, D); %! [Y, ~, ~, H, MI, S] = datevec (X); %! [h, mi, s] = hms (D); %! assert_equal (Y, ones (2, 4, 3)); %! assert_equal (H, h); %! assert_equal (MI, mi); %! assert_equal (S, s); %!test %! X = calendarDuration (1, 0, 1, 0, reshape (1:24, 2, 3, 4), 0); %! [Y, MO, D, H, MI, S] = datevec (X); %! assert_equal (Y, ones (2, 3, 4)); %! assert_equal (MO, zeros (2, 3, 4)); %! assert_equal (D, ones (2, 3, 4)); %! assert_equal (H, zeros (2, 3, 4)); %! assert_equal (MI, reshape (1:24, 2, 3, 4)); %! assert_equal (S, zeros (2, 3, 4)); %! DV = datevec (X); %! assert_equal (DV(:,[1, 3]), ones (24, 2)); %! assert_equal (DV(:,[2, 4, 6]), zeros (24, 3)); %! assert_equal (DV(:,5), [1:24]'); %!error ... %! [Y, MO, D, H, MI, S, MS] = datevec (calendarDuration) %!assert_equal (size (time (calendarDuration (1, 1, 1))), [1, 1]) %!assert_equal (size (time (calendarDuration (ones (2, 3, 4), 1, 1))), [2, 3, 4]) %!assert_equal (size (time (calendarDuration (1, 1, 1, days (1)))), [1, 1]) %!assert_equal (size (time (calendarDuration (0, 0, 0, ones (2, 3, 4), 1, 1))), [2, 3, 4]) %!test %! t = time (calendarDuration (1, 1, 1)); %! assert_equal (class (t), 'duration'); %! [h, m, s] = hms (t); %! assert_equal (h, 0); %! assert_equal (m, 0); %! assert_equal (s, 0); %!test %! t = time (calendarDuration (1, ones (2), 1, hours (1.5))); %! assert_equal (size (t), [2, 2]); %! [h, m, s] = hms (t); %! assert_equal (h, ones (2)); %! assert_equal (m, 30 * ones (2)); %! assert_equal (s, zeros (2)); %!test %! t = time (calendarDuration (1, ones (2), 1, 0, 0, 0.0251)); %! assert_equal (size (t), [2, 2]); %! [h, m, s] = hms (t); %! assert_equal (s, [0.0251, 0.0251; 0.0251, 0.0251]); %!assert_equal (seconds (time (calendarDuration (0, 0, 0, 1, 0, 1))), 3601) %!assert_equal (seconds (time (calendarDuration (0, 0, 1, 1, 0, 1))), 3601) %!assert_equal (seconds (time (calendarDuration (0, 0, 0, 1, 30, 2.5))), 5402.5) %!assert_equal (seconds (time (calendarDuration (0, 1, 0, 1, 30, 2.5))), 5402.5) %!assert_equal (minutes (time (calendarDuration (0, 0, 0, 1, 0, 1))), 60.017, 1e-3) %!assert_equal (minutes (time (calendarDuration (0, 1, 1, 1, 0, 1))), 60.017, 1e-3) %!assert_equal (minutes (time (calendarDuration (0, 0, 1, 1, 30, 2.5))), 90.042, 1e-3) %!assert_equal (minutes (time (calendarDuration (1, 1, 0, 1, 30, 62.5))), 91.042, 1e-3) %!assert_equal (fix (minutes (time (calendarDuration (0, 0, 0, 1, 0, 1)))), 60) %!assert_equal (fix (minutes (time (calendarDuration (0, 1, 1, 1, 0, 1)))), 60) %!assert_equal (fix (minutes (time (calendarDuration (0, 0, 1, 1, 30, 2.5)))), 90) %!assert_equal (fix (minutes (time (calendarDuration (1, 1, 0, 1, 30, 62.5)))), 91) %!assert_equal (hours (time (calendarDuration (0, 0, 1, 1, 0, 0))), 1) %!assert_equal (hours (time (calendarDuration (0, 0, 1, 1, 30, 0))), 1.5) %!assert_equal (hours (time (calendarDuration (0, 0, 1, 1, 0, 1800))), 1.5) %!assert_equal (hours (time (calendarDuration (0, [1, 2], 1, 1, 0, 0))), [1, 1]) %!assert_equal (hours (time (calendarDuration (0, 0, 1, [1; 2], 30, 0))), [1.5; 2.5]) %!assert_equal (hours (time (calendarDuration (0, 0, 1, 1, [0, 0], 1800))), [1.5, 1.5]) %!test %! X = calendarDuration (1, 0, 15); %! assert_equal (split (X, 'years'), 1); %! [Y, Q, W, D] = split (X, {'years', 'quarters', 'weeks', 'days'}); %! assert_equal (Y, 1); %! assert_equal (Q, 0); %! assert_equal (W, 2); %! assert_equal (D, 1); %!test %! X = calendarDuration (1, 0, 15); %! T = split (X, 'time'); %! assert_equal (isduration (T), true); %! assert_equal (hours (T), 0); %! assert_equal (minutes (T), 0); %! assert_equal (seconds (T), 0); %! assert_equal (dispstrings (T), {'00:00:00'}); %!test %! X = calendarDuration (1, ones (2), 15); %! T = split (X, 'time'); %! assert_equal (isduration (T), true); %! assert_equal (hours (T), zeros (2)); %! assert_equal (minutes (T), zeros (2)); %! assert_equal (seconds (T), zeros (2)); %! assert_equal (dispstrings (T), repmat ({'00:00:00'}, 2, 2)); %!test %! X = calendarDuration (1, 0, 15); %! assert_equal (split (X, 'years'), 1); %! [Q, W, D] = split (X, {'quarters', 'weeks', 'days'}); %! assert_equal (Q, 4); %! assert_equal (W, 2); %! assert_equal (D, 1); %!test %! X = calendarDuration (1, zeros (2, 3, 4), 15); %! assert_equal (split (X, 'years'), ones (2, 3, 4)); %! [Q, W, D] = split (X, {'quarters', 'weeks', 'days'}); %! assert_equal (Q, 4 * ones (2, 3, 4)); %! assert_equal (W, 2 * ones (2, 3, 4)); %! assert_equal (D, 1 * ones (2, 3, 4)); %!test %! X = calendarDuration (1, 0, 15); %! assert_equal (split (X, 'years'), 1); %! [Q, D] = split (X, {'quarters', 'days'}); %! assert_equal (Q, 4); %! assert_equal (D, 15); %!test %! X = calendarDuration (1, 4, 64); %! assert_equal (split (X, 'years'), 1); %! [Q, M, D] = split (X, {'quarters', 'months', 'days'}); %! assert_equal (Q, 5); %! assert_equal (M, 1); %! assert_equal (D, 64); %!test %! X = calendarDuration (1, 4, 64:69); %! assert_equal (split (X, 'years'), ones (1, 6)); %! [Q, M, D] = split (X, {'quarters', 'months', 'days'}); %! assert_equal (Q, [5, 5, 5, 5, 5, 5]); %! assert_equal (M, [1, 1, 1, 1, 1, 1]); %! assert_equal (D, [64:69]); %!test %! X = calendarDuration (1, 4, 64, 0, 35, 30); %! [Q, M, W, T] = split (X, {'quarters', 'months', 'weeks', 'time'}); %! assert_equal (Q, 5); %! assert_equal (M, 1); %! assert_equal (W, 9); %! assert_equal (minutes (T), 35.5); %! assert_equal (seconds (T), 2130); %!test %! X = calendarDuration (1, 4, 64, 0, 35, [30; 60]); %! [Q, M, W, T] = split (X, {'quarters', 'months', 'weeks', 'time'}); %! assert_equal (Q, [5; 5]); %! assert_equal (M, [1; 1]); %! assert_equal (W, [9; 9]); %! assert_equal (minutes (T), [35.5; 36]); %! assert_equal (seconds (T), [2130; 2160]); %!error split (calendarDuration) %!error ... %! split (calendarDuration, 1) %!error ... %! split (calendarDuration, {1}) %!error ... %! split (calendarDuration, 'ert') %!error ... %! split (calendarDuration, {'time', 'ert'}) %!error ... %! split (calendarDuration, string ({'time', 'ert'})) %!error ... %! split (calendarDuration, {'time', 'years'}) %!error ... %! split (calendarDuration, string ({'months', 'weeks', 'years'})) %!error ... %! split (calendarDuration, {'years', 'months'}) %!error ... %! [Y, M, D] = split (calendarDuration, {'years', 'months'}) %!assert_equal (caldays (caldays (5)), 5) %!assert_equal (caldays (caldays (1:10)), 1:10) %!assert_equal (caldays (caldays (ones (2, 3, 4))), ones (2, 3, 4)) %!assert_equal (caldays (calendarDuration (0, 0, [1, 2; 3, 4])), [1, 2; 3, 4]) %!test %! x = randi (20, 2, 3); %! assert_equal (caldays (calendarDuration (0, 0, x)), x); %!assert_equal (calweeks (calweeks (5)), 5) %!assert_equal (calweeks (calweeks (1:10)), 1:10) %!assert_equal (calweeks (calweeks (ones (2, 3, 4))), ones (2, 3, 4)) %!assert_equal (calweeks (calendarDuration (0, 0, 7 * [1, 2; 3, 4])), [1, 2; 3, 4]) %!test %! x = randi (20, 2, 3, 4); %! assert_equal (calweeks (calendarDuration (0, 0, x * 7)), x); %!assert_equal (calmonths (calmonths (5)), 5) %!assert_equal (calmonths (calmonths (1:10)), 1:10) %!assert_equal (calmonths (calmonths (ones (2, 3, 4))), ones (2, 3, 4)) %!assert_equal (calmonths (calendarDuration (0, [1, 2; 3, 4], 0)), [1, 2; 3, 4]) %!test %! x = randi (20, 2, 3); %! assert_equal (calmonths (calendarDuration (0, x, 0)), x); %!assert_equal (calquarters (calquarters (5)), 5) %!assert_equal (calquarters (calquarters (1:10)), 1:10) %!assert_equal (calquarters (calquarters (ones (2, 3, 4))), ones (2, 3, 4)) %!assert_equal (calquarters (calendarDuration (0, 3 * [1, 2; 3, 4], 0)), [1, 2; 3, 4]) %!test %! x = randi (20, 2, 3, 4); %! assert_equal (calquarters (calendarDuration (0, x * 3, 0)), x); %!assert_equal (calyears (calyears (5)), 5) %!assert_equal (calyears (calyears (1:10)), 1:10) %!assert_equal (calyears (calyears (ones (2, 3, 4))), ones (2, 3, 4)) %!assert_equal (calyears (calendarDuration ([1, 2; 3, 4], 0, 0)), [1, 2; 3, 4]) %!test %! x = randi (20, 2, 3); %! assert_equal (calyears (calendarDuration (x, 0, 0)), x); ################################################################################ ## ** Summary Information ** ## ################################################################################ ## Available Methods ## ## ## ## 'size' 'ndims' 'numel' 'nnz' ## ## 'length' 'keyHash' ## ## ## ################################################################################ %!assert_equal (size (calendarDuration), [1, 1]) %!assert_equal (size (calendarDuration ([], [], [])), [0, 0]) %!assert_equal (size (calendarDuration (ones (0, 3))), [0, 1]) %!assert_equal (size (calendarDuration (ones (0, 1), ones (0, 1), ones (0, 1))), [0, 1]) %!assert_equal (size (calendarDuration (ones (0, 3), ones (0, 3), ones (0, 3))), [0, 3]) %!assert_equal (size (calendarDuration (ones (1, 0), ones (1, 0), ones (1, 0))), [1, 0]) %!assert_equal (size (calendarDuration (ones (5, 3))), [5, 1]) %!assert_equal (size (calendarDuration (ones (5, 6))), [5, 1]) %!assert_equal (size (calendarDuration (ones (2, 3, 4), 0, 0)), [2, 3, 4]) %!assert_equal (size (calendarDuration (ones (1:10), 0, 0)), 1:10) %!test %! X = calendarDuration (ones (2, 3, 4), 0, 0); %! [d1, d2, d3] = size (X); %! assert_equal (d1, 2); %! assert_equal (d2, 3); %! assert_equal (d3, 4); %!test %! X = calendarDuration (0, 0, 0, minutes (randi (30, 2, 3, 4))); %! [d1, d2, d3] = size (X); %! assert_equal (d1, 2); %! assert_equal (d2, 3); %! assert_equal (d3, 4); %!test %! X = calendarDuration (0, 0, 0, minutes (randi (30, 2, 3, 4))); %! [d1, d2, d3] = size (X); %! assert_equal (size (X, 1), 2); %! assert_equal (size (X, 2), 3); %! assert_equal (size (X, 3), 4); %!error ... %! [d1, d2] = size (calendarDuration (ones (2, 3, 4), 0, 0)) %!assert_equal (ndims (calendarDuration ([], [], [])), 2) %!assert_equal (ndims (calendarDuration (1, 1, 1)), 2) %!assert_equal (ndims (calendarDuration), 2) %!assert_equal (ndims (calendarDuration (ones (2), 1, 1)), 2) %!assert_equal (ndims (calendarDuration (ones (2, 3, 4), 1, 1)), 3) %!assert_equal (ndims (calendarDuration (ones (1:10), 1, 1)), 10) %!assert_equal (numel (calendarDuration ([], [], [])), 0) %!assert_equal (numel (calendarDuration (1, 1, 1)), 1) %!assert_equal (numel (calendarDuration), 1) %!assert_equal (numel (calendarDuration (ones (2), 1, 1)), 4) %!assert_equal (numel (calendarDuration (ones (2, 3, 4), 1, 1)), 24) %!assert_equal (numel (calendarDuration (ones (1, 10), 1, 1)), 10) %!assert_equal (nnz (calendarDuration (1, 1, 1)), 1) %!assert_equal (nnz (calendarDuration (0, 0, 0)), 0) %!assert_equal (nnz (calendarDuration (0:5, 0, 0)), 5) %!assert_equal (nnz (calendarDuration ([0, 0, 0, 1, 1], 0, 0)), 2) %!assert_equal (nnz (calendarDuration (ones (2, 3, 4), 0, 0)), 24) %!assert_equal (length (calendarDuration (1, 1, 1)), 1) %!assert_equal (length (calendarDuration (0, 0, 0)), 1) %!assert_equal (length (calendarDuration (0:5, 0, 0)), 6) %!assert_equal (length (calendarDuration ([0, 0, 0, 1, 1], 0, 0)), 5) %!assert_equal (length (calendarDuration (ones (2, 3, 4), 0, 0)), 4) %!assert_equal (length (calendarDuration (ones (2, 10, 4), 0, 0)), 10) %!assert_equal (keyHash (calendarDuration), uint64 (9404037721690811995)) %!assert_equal (keyHash (calendarDuration (0, 0, 0)), uint64 (9404037721690811995)) %!assert_equal (keyHash (calendarDuration ([], [], [])), uint64 (10790283023733344549)) %!assert_equal (keyHash (calendarDuration (1, 1, 1)), uint64 (11245130726649642670)) %!test %! key = keyHash (calendarDuration ([1, 2; 3, 4], 1, 1)); %! assert_equal (key, uint64 (3011565863366139772)); %!test %! key = keyHash (calendarDuration (0, 0, 0, days ([1, 2, 3]))); %! assert_equal (key, uint64 (10002591001239839942)); %!test %! base_key = uint64 (2342124352342344234); %! key = keyHash (calendarDuration (0, 0, 0, days ([1, 2, 3])), base_key); %! assert_equal (key, uint64 (2231107818551636405)); %!error ... %! keyHash (calendarDuration (0, 0, 0), uint64 ([1, 2])) %!error ... %! keyHash (calendarDuration (0, 0, 0), 2231107818551636405) ################################################################################ ## ** Query Operations ** ## ################################################################################ ## Available Methods ## ## ## ## 'iscolumn' 'isempty' 'isequal' 'isequaln' ## ## 'isfinite' 'isinf' 'ismatrix' 'ismissing' ## ## 'isnan' 'isreal' 'isrow' 'isscalar' ## ## 'isvector' ## ## ## ################################################################################ %!assert_equal (iscolumn (calendarDuration (0, 0, 0)), true) %!assert_equal (iscolumn (calendarDuration ([], [], [])), false) %!assert_equal (iscolumn (calendarDuration (ones (1, 0), ones (1, 0), ones (1, 0))), false) %!assert_equal (iscolumn (calendarDuration (ones (0, 1), ones (0, 1), ones (0, 1))), true) %!assert_equal (iscolumn (calendarDuration ([1, 1], 1, 1)), false) %!assert_equal (iscolumn (calendarDuration ([1; 1], 1, 1)), true) %!assert_equal (iscolumn (calendarDuration (ones (2), 1, 1)), false) %!assert_equal (iscolumn (calendarDuration (1, 1, zeros (2, 3, 4))), false) %!assert_equal (isempty (calendarDuration (0, 0, 0)), false) %!assert_equal (isempty (calendarDuration ([], [], [])), true) %!assert_equal (isempty (calendarDuration (ones (1, 0), ones (1, 0), ones (1, 0))), true) %!assert_equal (isempty (calendarDuration (ones (0, 1), ones (0, 1), ones (0, 1))), true) %!test %! x = ones ([2, 3, 0, 2]); %! assert_equal (isempty (calendarDuration (x, x, x)), true); %! assert_equal (isempty (calendarDuration (x, x, x, x, x, x)), true) %! assert_equal (isempty (calendarDuration (x, x, x, hours (x))), true); %!test %! x = ones ([2, 3, 1, 2]); %! assert_equal (isempty (calendarDuration (x, x, x)), false); %! assert_equal (isempty (calendarDuration (x, x, x, x, x, x)), false) %! assert_equal (isempty (calendarDuration (x, x, x, hours (x))), false); %!assert_equal (isequal (caldays (5), caldays (5)), true) %!assert_equal (isequal (caldays (NaN), caldays (NaN)), false) %!assert_equal (isequal (caldays (5), caldays (6)), false) %!assert_equal (isequal (caldays ([5, NaN]), caldays ([5, NaN])), false) %!assert_equal (isequal (caldays (5), caldays (5), caldays (5)), true) %!assert_equal (isequal (caldays (5), caldays (6), calweeks (1)), false) %!assert_equal (isequal (caldays (7), caldays (7), calweeks (1)), true) %!assert_equal (isequal (caldays ([1:3]), caldays ([1, 2, 3])), true) %!assert_equal (isequal (caldays ([1:3]), caldays ([1, NaN, 3])), false) %!assert_equal (isequal (calmonths (5), calmonths (5)), true) %!assert_equal (isequal (calmonths (5), calmonths (6)), false) %!assert_equal (isequal (calmonths (NaN), calmonths (NaN)), false) %!assert_equal (isequal (calmonths (5), calmonths (5), calmonths (5)), true) %!assert_equal (isequal (calmonths (5), calmonths (6), calquarters (2)), false) %!assert_equal (isequal (calmonths (6), calmonths (6), calquarters (2)), true) %!assert_equal (isequal (calmonths ([1:3]), calmonths ([1, 2, 3])), true) %!assert_equal (isequal (calmonths ([1:3]), calmonths ([1, NaN, 3])), false) %!assert_equal (isequal (calmonths ([1, NaN, 3]), calmonths ([1, NaN, 3])), false) %!assert_equal (isequal (calendarDuration (0, 0, 0, hours (ones (3))), ... %! calendarDuration (0, 0, 0, hours (ones (3)))), true) %!assert_equal (isequal (calendarDuration (0, 0, 0, hours (nan (3))), ... %! calendarDuration (0, 0, 0, hours (nan (3)))), false) %!assert_equal (isequal (calendarDuration (0, 0, 0, hours (ones (3, 3, 2))), ... %! calendarDuration (0, 0, 0, hours (ones (3, 3, 3)))), false) %!error isequal (caldays (3)) %!error ... %! isequal (caldays (3), 1) %!error ... %! isequal (caldays (3), caldays (3), hours (1)) %!error ... %! isequal (caldays (3), caldays (3), days (1)) %!assert_equal (isequaln (caldays (5), caldays (5)), true) %!assert_equal (isequaln (caldays (NaN), caldays (NaN)), true) %!assert_equal (isequaln (caldays (5), caldays (6)), false) %!assert_equal (isequaln (caldays ([5, NaN]), caldays ([5, NaN])), true) %!assert_equal (isequaln (caldays (5), caldays (5), caldays (5)), true) %!assert_equal (isequaln (caldays (5), caldays (6), calweeks (1)), false) %!assert_equal (isequaln (caldays (7), caldays (7), calweeks (1)), true) %!assert_equal (isequaln (caldays ([1:3]), caldays ([1, 2, 3])), true) %!assert_equal (isequaln (caldays ([1:3]), caldays ([1, NaN, 3])), false) %!assert_equal (isequaln (calmonths (5), calmonths (5)), true) %!assert_equal (isequaln (calmonths (NaN), calmonths (NaN)), true) %!assert_equal (isequaln (calmonths (5), calmonths (6)), false) %!assert_equal (isequaln (calmonths (5), calmonths (5), calmonths (5)), true) %!assert_equal (isequaln (calmonths (5), calmonths (6), calquarters (2)), false) %!assert_equal (isequaln (calmonths (6), calmonths (6), calquarters (2)), true) %!assert_equal (isequaln (calmonths ([1:3]), calmonths ([1, 2, 3])), true) %!assert_equal (isequaln (calmonths ([1:3]), calmonths ([1, NaN, 3])), false) %!assert_equal (isequaln (calmonths ([1, NaN, 3]), calmonths ([1, NaN, 3])), true) %!assert_equal (isequaln (calendarDuration (0, 0, 0, hours (ones (3))), ... %! calendarDuration (0, 0, 0, hours (ones (3)))), true) %!assert_equal (isequaln (calendarDuration (0, 0, 0, hours (nan (3))), ... %! calendarDuration (0, 0, 0, hours (nan (3)))), true) %!assert_equal (isequaln (calendarDuration (0, 0, 0, hours (ones (3, 3, 2))), ... %! calendarDuration (0, 0, 0, hours (ones (3, 3, 3)))), false) %!assert_equal (isequaln (calendarDuration (0, NaN, 0, hours (ones (3))), ... %! calendarDuration (0, NaN, 0, hours (ones (3)))), true) %!assert_equal (isequaln (calendarDuration (0, 0, NaN, hours (nan (3, 3, 3))), ... %! calendarDuration (0, 0, NaN, hours (nan (3, 3, 3)))), true) %!error isequaln (caldays (3)) %!error ... %! isequaln (caldays (3), 1) %!error ... %! isequaln (caldays (3), caldays (3), hours (1)) %!error ... %! isequaln (caldays (3), caldays (3), days (1)) %!assert_equal (isfinite (caldays (1)), true) %!assert_equal (isfinite (caldays (NaN)), false) %!assert_equal (isfinite (caldays (Inf)), false) %!assert_equal (isfinite (caldays (-Inf)), false) %!assert_equal (isfinite (calendarDuration ([1, 0, 1])), true) %!assert_equal (isfinite (calendarDuration (nan (2), 1, 0)), false (2)) %!assert_equal (isfinite (calendarDuration (Inf (3), 0, 0)), false (3)) %!assert_equal (isfinite (calendarDuration (1, -Inf, [0; 0])), false (2, 1)) %!assert_equal (size (isfinite (caldays ([]))), [0, 0]) %!assert_equal (size (isfinite (caldays (ones (1, 2, 0, 2)))), [1, 2, 0, 2]) %!assert_equal (isinf (caldays (1)), false) %!assert_equal (isinf (caldays (NaN)), false) %!assert_equal (isinf (caldays (Inf)), true) %!assert_equal (isinf (caldays (-Inf)), true) %!assert_equal (isinf (calendarDuration ([1, 0, NaN])), false) %!assert_equal (isinf (calendarDuration ([1, Inf, -Inf])), false) %!assert_equal (isinf (calendarDuration (nan (2), 1, 0)), false (2)) %!assert_equal (isinf (calendarDuration (Inf (3), 0, 0)), true (3)) %!assert_equal (isinf (calendarDuration (1, -Inf, [0; 0])), true (2, 1)) %!assert_equal (size (isinf (caldays ([]))), [0, 0]) %!assert_equal (size (isinf (caldays (Inf (1, 2, 0, 2)))), [1, 2, 0, 2]) %!assert_equal (ismatrix (caldays (3)), true) %!assert_equal (ismatrix (caldays ([3, 3; 2, 1])), true) %!assert_equal (ismatrix (caldays ([3, 3, 2, 1])), true) %!assert_equal (ismatrix (caldays (ones (3, 3, 2))), false) %!assert_equal (ismatrix (calmonths (3)), true) %!assert_equal (ismatrix (calmonths ([3, 3; 2, 1])), true) %!assert_equal (ismatrix (calmonths ([3, 3, 2, 1])), true) %!assert_equal (ismatrix (calmonths (ones (3, 3, 2))), false) %!assert_equal (ismatrix (calendarDuration (0, 0, 3)), true) %!assert_equal (ismatrix (calendarDuration ([3, 3; 2, 1], 0, 0)), true) %!assert_equal (ismatrix (calendarDuration ([3, 3, 2, 1], 0, 1)), true) %!assert_equal (ismatrix (calendarDuration (ones (3, 3, 2), 0, 0)), false) %!assert_equal (ismatrix (calendarDuration (0, 0, 1, hours (ones (3, 3)))), true) %!assert_equal (ismatrix (calendarDuration (0, 0, 1, hours (ones (3, 3, 2)))), false) %!assert_equal (ismissing (caldays (1)), false) %!assert_equal (ismissing (caldays (NaN)), true) %!assert_equal (ismissing (caldays (Inf)), false) %!assert_equal (ismissing (caldays (-Inf)), false) %!assert_equal (ismissing (calendarDuration ([1, 0, NaN])), true) %!assert_equal (ismissing (calendarDuration ([1, Inf, -Inf])), true) %!assert_equal (ismissing (calendarDuration (nan (2), 1, 0)), true (2)) %!assert_equal (ismissing (calendarDuration (Inf (3), 0, 0)), false (3)) %!assert_equal (ismissing (calendarDuration (1, -Inf, [0; NaN])), [false; true]) %!assert_equal (size (ismissing (caldays ([]))), [0, 0]) %!assert_equal (ismissing (caldays (nan (1, 2, 0, 2))), true (1, 2, 0, 2)) %!assert_equal (size (ismissing (caldays (nan (1, 2, 0, 2)))), [1, 2, 0, 2]) %!error ... %! ismissing (caldays (3), 3) %!assert_equal (isnan (caldays (1)), false) %!assert_equal (isnan (caldays (NaN)), true) %!assert_equal (isnan (caldays (Inf)), false) %!assert_equal (isnan (caldays (-Inf)), false) %!assert_equal (isnan (calendarDuration ([1, 0, NaN])), true) %!assert_equal (isnan (calendarDuration ([1, Inf, -Inf])), true) %!assert_equal (isnan (calendarDuration (nan (2), 1, 0)), true (2)) %!assert_equal (isnan (calendarDuration (Inf (3), 0, 0)), false (3)) %!assert_equal (isnan (calendarDuration (1, -Inf, [0; NaN])), [false; true]) %!assert_equal (size (isnan (caldays ([]))), [0, 0]) %!assert_equal (isnan (caldays (nan (1, 2, 0, 2))), true (1, 2, 0, 2)) %!assert_equal (size (isnan (caldays (nan (1, 2, 0, 2)))), [1, 2, 0, 2]) %!assert_equal (isscalar (isreal (caldays (ones (2, 2, 2)))), true) %!assert_equal (isreal (calendarDuration ([1, 1, 1])), true) %!assert_equal (isrow (calendarDuration (0, 0, 0)), true) %!assert_equal (isrow (calendarDuration ([], [], [])), false) %!assert_equal (isrow (calendarDuration (ones (1, 0), ones (1, 0), ones (1, 0))), true) %!assert_equal (isrow (calendarDuration (ones (0, 1), ones (0, 1), ones (0, 1))), false) %!assert_equal (isrow (calmonths (1)), true) %!assert_equal (isrow (calmonths ([1, 2, 3])), true) %!assert_equal (isrow (calyears ([1; 2; 3])), false) %!assert_equal (isrow (calendarDuration (1, 1, 1, 1, 1, 1)), true) %!assert_equal (isrow (calendarDuration (1, 1, 1, 1, 1, [1, 2, 3])), true) %!assert_equal (isrow (calendarDuration (1, 1, 1, 1, 1, ones (2))), false) %!assert_equal (isrow (calendarDuration (1, 1, ones (1, 1, 2))), false) %!assert_equal (isrow (calendarDuration (1, 1, ones (1, 2, 1))), true) %!assert_equal (isscalar (calmonths (1)), true) %!assert_equal (isscalar (calmonths ([1, 2, 3])), false) %!assert_equal (isscalar (calyears ([1; 2; 3])), false) %!assert_equal (isscalar (calendarDuration (0, 0, 0)), true) %!assert_equal (isscalar (calendarDuration ([], [], [])), false) %!assert_equal (isscalar (calendarDuration (1, 1, 1, 1, 1, 1)), true) %!assert_equal (isscalar (calendarDuration (1, 1, 1, 1, 1, [1, 2, 3])), false) %!assert_equal (isscalar (calendarDuration (1, 1, 1, 1, 1, ones (2))), false) %!assert_equal (isscalar (calendarDuration (1, 1, ones (1, 1, 2))), false) %!assert_equal (isscalar (calendarDuration (1, 1, ones (1, 2, 1))), false) %!assert_equal (isscalar (calendarDuration (1, 1, ones (1, 1, 1))), true) %!assert_equal (isscalar (calendarDuration (1, 1, 1, hours (5))), true) %!assert_equal (isvector (calendarDuration (0, 0, 0)), true) %!assert_equal (isvector (calendarDuration ([], [], [])), false) %!assert_equal (isvector (calendarDuration (ones (1, 0), ones (1, 0), ones (1, 0))), true) %!assert_equal (isvector (calendarDuration (ones (0, 1), ones (0, 1), ones (0, 1))), true) %!assert_equal (isvector (calmonths (1)), true) %!assert_equal (isvector (calmonths ([1, 2, 3])), true) %!assert_equal (isvector (calyears ([1; 2; 3])), true) %!assert_equal (isvector (calendarDuration (1, 1, 1, 1, 1, 1)), true) %!assert_equal (isvector (calendarDuration (1, 1, 1, 1, 1, [1, 2, 3])), true) %!assert_equal (isvector (calendarDuration (1, 1, 1, 1, 1, ones (2))), false) %!assert_equal (isvector (calendarDuration (1, 1, ones (1, 1, 2))), false) %!assert_equal (isvector (calendarDuration (1, 1, ones (2, 1, 2))), false) %!assert_equal (isvector (calendarDuration (1, 1, ones (1, 2, 1))), true) %!assert_equal (isvector (calendarDuration (1, 1, ones (2, 1, 1))), true) ################################################################################ ## ** Mathematical Operations ** ## ################################################################################ ## Available Methods ## ## ## ## 'minus' 'uminus' 'plus' 'uplus' ## ## 'times' 'mtimes' ## ## ## ################################################################################ %!test %! A = caldays ([1, 2, 3]); %! B = caldays (1); %! assert_equal (caldays (A - B), [0, 1, 2]); %! assert_equal (caldays (B - A), [0, -1, -2]); %!test %! A = calweeks (1); %! B = days ([2, 2]); %! [c1, c2] = split (A - B, {'days', 'time'}); %! assert_equal (c1, [7, 7]); %! assert_equal (days (c2), [-2, -2]); %!test %! A = calweeks (1); %! B = [2, 2]; %! [c1, c2] = split (A - B, {'days', 'time'}); %! assert_equal (c1, [7, 7]); %! assert_equal (days (c2), [-2, -2]); %!test %! A = days ([2, 2]); %! B = calweeks (1); %! [c1, c2] = split (A - B, {'days', 'time'}); %! assert_equal (c1, [-7, -7]); %! assert_equal (days (c2), [2, 2]); %!test %! A = [2, 2]; %! B = calweeks (1); %! [c1, c2] = split (A - B, {'days', 'time'}); %! assert_equal (c1, [-7, -7]); %! assert_equal (days (c2), [2, 2]); %!error ... %! calweeks (1) - 'a' %!error ... %! calweeks (1) - {1} %!error ... %! calweeks (1) - struct ('a', 1) %!test %! A = caldays ([1, 2, 3]); %! assert_equal (caldays (-A), [-1, -2, -3]); %!test %! A = randi (10, 3, 4, 5); %! B = caldays (A); %! C = - caldays (A); %! assert_equal (caldays (B + C), zeros (3, 4, 5)); %!test %! A = caldays ([1, 2, 3]); %! B = caldays (1); %! assert_equal (caldays (A + B), [2, 3, 4]); %! assert_equal (caldays (B + A), [2, 3, 4]); %!test %! A = calweeks (1); %! B = days ([2, 2]); %! [c1, c2] = split (A + B, {'days', 'time'}); %! assert_equal (c1, [7, 7]); %! assert_equal (days (c2), [2, 2]); %!test %! A = calweeks (1); %! B = [2, 2]; %! [c1, c2] = split (A + B, {'days', 'time'}); %! assert_equal (c1, [7, 7]); %! assert_equal (days (c2), [2, 2]); %!test %! A = days ([2, 2]); %! B = calweeks (1); %! [c1, c2] = split (A + B, {'days', 'time'}); %! assert_equal (c1, [7, 7]); %! assert_equal (days (c2), [2, 2]); %!test %! A = [2, 2]; %! B = calweeks (1); %! [c1, c2] = split (A + B, {'days', 'time'}); %! assert_equal (c1, [7, 7]); %! assert_equal (days (c2), [2, 2]); %!error ... %! calweeks (1) + 'a' %!error ... %! calweeks (1) + {1} %!error ... %! calweeks (1) + struct ('a', 1) %!test %! A = calendarDuration ([1, 1, 5, 4, 30, 2]); %! B = A .* [1, 3]; %! [y, m, d, t] = split (B, {'years', 'months', 'days', 'time'}); %! assert_equal (y, [1, 3]); %! assert_equal (m, [1, 3]); %! assert_equal (d, [5, 15]); %! assert_equal (seconds (t), [16202, 48606], 4e-12); %!test %! A = calendarDuration ([1, 1, 5, 4, 30, 2]); %! B = A .* [1; 3]; %! [y, m, d, t] = split (B, {'years', 'months', 'days', 'time'}); %! assert_equal (y, [1; 3]); %! assert_equal (m, [1; 3]); %! assert_equal (d, [5; 15]); %! assert_equal (seconds (t), [16202; 48606], 4e-12); %!test %! A = calendarDuration ([1, 1, 5, 4, 30, 2]); %! B = [1; 3] .* A; %! [y, m, d, t] = split (B, {'years', 'months', 'days', 'time'}); %! assert_equal (y, [1; 3]); %! assert_equal (m, [1; 3]); %! assert_equal (d, [5; 15]); %! assert_equal (seconds (t), [16202; 48606], 4e-12); %!test %! A = calendarDuration ([1, 1, 5, 4, 30, 2]); %! B = 2 .* A; %! [y, m, d, t] = split (B, {'years', 'months', 'days', 'time'}); %! assert_equal (y, 2); %! assert_equal (m, 2); %! assert_equal (d, 10); %! assert_equal (seconds (t), 32404, 4e-12); %!error ... %! calweeks (1) .* 'a' %!error ... %! calweeks (1) .* {1} %!error ... %! calweeks (1) .* struct ('a', 1) %!test %! A = calendarDuration ([1, 1, 5, 4, 30, 2]); %! B = 2 * A; %! [y, m, d, t] = split (B, {'years', 'months', 'days', 'time'}); %! assert_equal (y, 2); %! assert_equal (m, 2); %! assert_equal (d, 10); %! assert_equal (seconds (t), 32404, 4e-12); %!test %! A = calendarDuration ([1, 2], 0, [4, 5]); %! B = A * [1; 2]; %! [y, d] = split (B, {'years', 'days'}); %! assert_equal (y, [1, 2] * [1; 2]); %! assert_equal (d, [4, 5] * [1; 2]); %!test %! A = calendarDuration ([1, 2], 0, [4, 5]); %! B = A * [4; 2]; %! [y, d] = split (B, {'years', 'days'}); %! assert_equal (y, [1, 2] * [4; 2]); %! assert_equal (d, [4, 5] * [4; 2]); %!test %! A = calendarDuration ([1, 2], 0, [4, 5]); %! B = [4; 2] * A; %! [y, d] = split (B, {'years', 'days'}); %! assert_equal (y, [4; 2] * [1, 2]); %! assert_equal (d, [4; 2] * [4, 5]); %!error ... %! calweeks (1) * 'a' %!error ... %! calweeks (1) * {1} %!error ... %! calweeks (1) * struct ('a', 1) ################################################################################ ## ** Equality and Filter Operations ** ## ################################################################################ ## Available Methods ## ## ## ## 'eq' 'ne' 'unique' ## ## ## ################################################################################ %!assert_equal (caldays (3) == caldays (3), true) %!assert_equal (caldays (3) == caldays (4), false) %!assert_equal (caldays (14) == calweeks (2), true) %!assert_equal (calmonths (15) == calquarters (5), true) %!assert_equal (calmonths ([15, 12, NaN]) == calquarters ([5, 4, 3]), [true, true, false]) %!assert_equal (calyears (ones (2)) == calmonths ([12, 15]), logical ([1, 0; 1, 0])) %!assert_equal (calyears (ones (2)) == calmonths ([12; 15]), logical ([1, 1; 0, 0])) %!assert_equal (calmonths ([3, 4]) == calmonths ([3, 4]), [true, true]) %!assert_equal (calmonths ([3, NaN]) == calmonths ([3, 4]), [true, false]) %!assert_equal (calmonths ([3; NaN]) == calmonths ([3; NaN]), [true; false]) %!assert_equal (calendarDuration ([1; 2], 0, 0) == calyears ([1, 2]), logical (eye (2))) %!error ... %! caldays (3) == NaN %!error ... %! calendarDuration (0, 0, 0, hours (5)) == hours (5) %!error ... %! 5 == calyears (5) %!error ... %! '1y' == calyears(1) %!assert_equal (caldays (3) != caldays (3), false) %!assert_equal (caldays (3) != caldays (4), true) %!assert_equal (caldays (14) != calweeks (2), false) %!assert_equal (calmonths (15) != calquarters (5), false) %!assert_equal (calmonths ([15, 12, NaN]) != calquarters ([5, 4, 3]), [false, false, true]) %!assert_equal (calyears (ones (2)) != calmonths ([12, 15]), logical ([0, 1; 0, 1])) %!assert_equal (calyears (ones (2)) != calmonths ([12; 15]), logical ([0, 0; 1, 1])) %!assert_equal (calmonths ([3, 4]) != calmonths ([3, 4]), [false, false]) %!assert_equal (calmonths ([3, NaN]) != calmonths ([3, 4]), [false, true]) %!assert_equal (calmonths ([3; NaN]) != calmonths ([3; NaN]), [false; true]) %!assert_equal (calendarDuration ([1; 2], 0, 0) != calyears ([2, 1]), logical (eye (2))) %!error ... %! caldays (3) != NaN %!error ... %! calendarDuration (0, 0, 0, hours (5)) != hours (5) %!error ... %! 5 != calyears (5) %!error ... %! '1y' != calyears(1) %!test %! U = unique (caldays ([1, 2, 2, 3])); %! assert_equal (caldays (U), [1, 2, 3]); %!test %! U = unique (caldays ([1, 2; 2, 3; 1, 2]), 'rows'); %! assert_equal (caldays (U), [1, 2; 2, 3]); %!test %! U = unique (caldays ([1, 3, 2, 2])); %! assert_equal (caldays (U), [1, 2, 3]); %!test %! U = unique (caldays ([1, 3, 2, 2]), 'sorted'); %! assert_equal (caldays (U), [1, 2, 3]); %!test %! U = unique (caldays ([1, 3, 2, 2]), 'stable'); %! assert_equal (caldays (U), [1, 3, 2]); %!test %! [U, idx] = unique (caldays ([1, 3, 2, 2]), 'first'); %! assert_equal (caldays (U), [1, 2, 3]); %! assert_equal (idx, [1; 3; 2]); %!test %! [U, idx] = unique (caldays ([1, 3, 2, 2]), 'last'); %! assert_equal (caldays (U), [1, 2, 3]); %! assert_equal (idx, [1; 4; 2]); %!test %! [U, idx] = unique (caldays ([1, 3, 2, 2]), 'sorted', 'first'); %! assert_equal (caldays (U), [1, 2, 3]); %! assert_equal (idx, [1; 3; 2]); %!test %! [U, idx] = unique (caldays ([1, 3, 2, 2]), 'stable', 'first'); %! assert_equal (caldays (U), [1, 3, 2]); %! assert_equal (idx, [1; 2; 3]); %!test %! [U, idx] = unique (caldays ([1, 3, 2, 2]), 'sorted', 'last'); %! assert_equal (caldays (U), [1, 2, 3]); %! assert_equal (idx, [1; 4; 2]); %!test %! [U, idx] = unique (caldays ([1, 3, 2, 2]), 'stable', 'last'); %! assert_equal (caldays (U), [1, 3, 2]); %! assert_equal (idx, [1; 2; 4]); %!test %! [B, ixA, ixB] = unique (caldays ([1, 2; 2, 3; 1, 2]), 'rows', 'sorted', 'first'); %! assert_equal (caldays (B), [1, 2; 2, 3]); %! assert_equal (ixA, [1; 2]); %! assert_equal (ixB, [1; 2; 1]); %!test %! [B, ixA, ixB] = unique (caldays ([1, 2; 2, 3; 1, 2]), 'rows', 'stable', 'first'); %! assert_equal (caldays (B), [1, 2; 2, 3]); %! assert_equal (ixA, [1; 2]); %! assert_equal (ixB, [1; 2; 1]); %!test %! [B, ixA, ixB] = unique (caldays ([1, 2; 2, 3; 1, 2]), 'rows', 'sorted', 'last'); %! assert_equal (caldays (B), [1, 2; 2, 3]); %! assert_equal (ixA, [3; 2]); %! assert_equal (ixB, [1; 2; 1]); %!test %! [B, ixA, ixB] = unique (caldays ([1, 2; 2, 3; 1, 2]), 'rows', 'stable', 'last'); %! assert_equal (caldays (B), [2, 3; 1, 2]); %! assert_equal (ixA, [2; 3]); %! assert_equal (ixB, [2; 1; 2]); %!error ... %! unique (caldays ([1, 2; 2, 3; 1, 2]), 'legacy') ################################################################################ ## ** Array Operations ** ## ################################################################################ ## Available Methods ## ## ## ## 'cat' 'horzcat' 'vertcat' 'repmat' ## ## 'repelem' 'repelems' 'reshape' 'circshift' ## ## 'permute' 'ipermute' 'transpose' 'ctranspose' ## ## ## ################################################################################ %!test %! cD = cat (1, 2, caldays ([3; 4])); %! assert_equal (caldays (cD), [0; 3; 4]); %! assert_equal (days (time (cD)), [2; 0; 0]); %!test %! cD = cat (1, caldays ([3; 4]), 5); %! assert_equal (caldays (cD), [3; 4; 0]); %! assert_equal (days (time (cD)), [0; 0; 5]); %!test %! cD = cat (2, 2, caldays ([3, 4])); %! assert_equal (caldays (cD), [0, 3, 4]); %! assert_equal (days (time (cD)), [2, 0, 0]); %!test %! cD = cat (2, caldays ([3, 4]), 5); %! assert_equal (caldays (cD), [3, 4, 0]); %! assert_equal (days (time (cD)), [0, 0, 5]); %!test %! cD = cat (1, [0; 1; 2], caldays ([3; 4])); %! assert_equal (caldays (cD), [0; 0; 0; 3; 4]); %! assert_equal (days (time (cD)), [0; 1; 2; 0; 0]); %!test %! cD = cat (1, caldays ([3; 4]), [0; 2; 5]); %! assert_equal (caldays (cD), [3; 4; 0; 0; 0]); %! assert_equal (days (time (cD)), [0; 0; 0; 2; 5]); %!test %! cD = cat (2, [0, 1, 2], caldays ([3, 4])); %! assert_equal (caldays (cD), [0, 0, 0, 3, 4]); %! assert_equal (days (time (cD)), [0, 1, 2, 0, 0]); %!test %! cD = cat (2, caldays ([3, 4]), [0, 2, 5]); %! assert_equal (caldays (cD), [3, 4, 0, 0, 0]); %! assert_equal (days (time (cD)), [0, 0, 0, 2, 5]); %!test %! cD = cat (1, caldays ([1, 2]), caldays ([3, 4])); %! assert_equal (caldays (cD), [1, 2; 3, 4]); %!test %! cD = cat (2, caldays ([1, 2]), caldays ([3, 4])); %! assert_equal (caldays (cD), [1, 2, 3, 4]); %!test %! cD = cat (3, caldays ([1, 2]), caldays ([3, 4])); %! assert_equal (caldays (cD), reshape ([1, 2, 3, 4], 1, 2, 2)); %!test %! cD = cat (4, caldays ([1, 2]), caldays ([3, 4])); %! assert_equal (caldays (cD), reshape ([1, 2, 3, 4], 1, 2, 1, 2)); %!error cat (1, caldays ([3, 4]), [0; 2; 5]) %!error cat (1, caldays ([3; 4]), [0, 2; 5, 2]) %!error cat (2, caldays ([3; 4]), [0; 2; 5]) %!error cat (2, caldays ([3, 4]), [0, 2; 5, 2]) %!error ... %! cat (1, caldays ([3; 4]), {}) %!error ... %! cat (1, caldays ([3; 4]), {'o'}) %!error ... %! cat (1, caldays ([3; 4]), 'o') %!error ... %! cat (1, caldays ([3; 4]), string ('o')) %!test %! cD = [days(1), caldays(2)]; %! assert_equal (caldays (cD), [0, 2]); %! assert_equal (days (time (cD)), [1, 0]); %!test %! cD = [days(1), 1, caldays(2)]; %! assert_equal (caldays (cD), [0, 0, 2]); %! assert_equal (days (time (cD)), [1, 1, 0]); %!test %! cD = [caldays(1), days(2)]; %! assert_equal (caldays (cD), [1, 0]); %! assert_equal (days (time (cD)), [0, 2]); %!test %! cD = [caldays(1), 1, days(2)]; %! assert_equal (caldays (cD), [1, 0, 0]); %! assert_equal (days (time (cD)), [0, 1, 2]); %!test %! cD = [days(1), [], caldays(2)]; %! assert_equal (caldays (cD), [0, 2]); %! assert_equal (days (time (cD)), [1, 0]); %!test %! cD = [caldays(1), [], days(2)]; %! assert_equal (caldays (cD), [1, 0]); %! assert_equal (days (time (cD)), [0, 2]); %!test %! cD = [days(1), ones(0, 1), caldays(2)]; %! assert_equal (caldays (cD), [0, 2]); %! assert_equal (days (time (cD)), [1, 0]); %!test %! cD = [caldays(1), ones(0, 1), days(2)]; %! assert_equal (caldays (cD), [1, 0]); %! assert_equal (days (time (cD)), [0, 2]); %!test %! cD = [days(1), ones(0, 1), caldays(2), ones(1, 0)]; %! assert_equal (caldays (cD), [0, 2]); %! assert_equal (days (time (cD)), [1, 0]); %!test %! cD = [caldays(1), ones(0, 1), days(2), ones(1, 0)]; %! assert_equal (caldays (cD), [1, 0]); %! assert_equal (days (time (cD)), [0, 2]); %!test %! cD = [days(1), ones(2, 0, 1), caldays(2)]; %! assert_equal (caldays (cD), [0, 2]); %! assert_equal (days (time (cD)), [1, 0]); %!test %! cD = [caldays(1), ones(2, 0, 1), days(2)]; %! assert_equal (caldays (cD), [1, 0]); %! assert_equal (days (time (cD)), [0, 2]); %!test %! cD = [days(1); caldays(2)]; %! assert_equal (caldays (cD), [0; 2]); %! assert_equal (days (time (cD)), [1; 0]); %!test %! cD = [days(1); 1; caldays(2)]; %! assert_equal (caldays (cD), [0; 0; 2]); %! assert_equal (days (time (cD)), [1; 1; 0]); %!test %! cD = [caldays(1); days(2)]; %! assert_equal (caldays (cD), [1; 0]); %! assert_equal (days (time (cD)), [0; 2]); %!test %! cD = [caldays(1); 1; days(2)]; %! assert_equal (caldays (cD), [1; 0; 0]); %! assert_equal (days (time (cD)), [0; 1; 2]); %!test %! cD = [days(1); []; caldays(2)]; %! assert_equal (caldays (cD), [0; 2]); %! assert_equal (days (time (cD)), [1; 0]); %!test %! cD = [caldays(1); []; days(2)]; %! assert_equal (caldays (cD), [1; 0]); %! assert_equal (days (time (cD)), [0; 2]); %!test %! cD = [days(1); ones(0, 1); caldays(2)]; %! assert_equal (caldays (cD), [0; 2]); %! assert_equal (days (time (cD)), [1; 0]); %!test %! cD = [caldays(1); ones(0, 1); days(2)]; %! assert_equal (caldays (cD), [1; 0]); %! assert_equal (days (time (cD)), [0; 2]); %!test %! cD = [days(1); ones(0, 1); caldays(2); ones(1, 0)]; %! assert_equal (caldays (cD), [0; 2]); %! assert_equal (days (time (cD)), [1; 0]); %!test %! cD = [caldays(1); ones(0, 1); days(2); ones(1, 0)]; %! assert_equal (caldays (cD), [1; 0]); %! assert_equal (days (time (cD)), [0; 2]); %!test %! cD = [days(1); ones(2, 0, 1); caldays(2)]; %! assert_equal (caldays (cD), [0; 2]); %! assert_equal (days (time (cD)), [1; 0]); %!test %! cD = [caldays(1); ones(2, 0, 1); days(2)]; %! assert_equal (caldays (cD), [1; 0]); %! assert_equal (days (time (cD)), [0; 2]); %!shared x1, x2 %! x1 = [2, 3]; %! x2 = magic (3); %!assert_equal (caldays (repmat (caldays (x1), 3)), repmat (x1, 3)) %!assert_equal (caldays (repmat (caldays (x1), 2, 3)), repmat (x1, 2, 3)) %!assert_equal (caldays (repmat (caldays (x1), 1, 1, 3)), repmat (x1, 1, 1, 3)) %!assert_equal (caldays (repmat (caldays (x1), [1, 1, 3])), repmat (x1, [1, 1, 3])) %!assert_equal (caldays (repmat (caldays (x1), 1, 0, 3)), repmat (x1, 1, 0, 3)) %!assert_equal (caldays (repmat (caldays (x1), [1, 0, 3])), repmat (x1, 1, 0, 3)) %!assert_equal (caldays (repelem (caldays (x1), 3)), repelem (x1, 3)) %!assert_equal (caldays (repelem (caldays (x1), 2, 3)), repelem (x1, 2, 3)) %!assert_equal (caldays (repelem (caldays (x1), [1, 3])), repelem (x1, [1, 3])) %!assert_equal (caldays (repelem (caldays (x2), 1, [1, 1, 3])), repelem (x2, 1, [1, 1, 3])) %!assert_equal (caldays (repelem (caldays (x2), [2, 3, 4], [1, 0, 3])), repelem (x2, [2, 3, 4], [1, 0, 3])) %!assert_equal (caldays (repelem (caldays (x2), 2, [1, 0, 3])), repelem (x2, 2, [1, 0, 3])) %!assert_equal (caldays (repelems (caldays (x2), [1, 3, 4; 4, 2, 5])), repelems (x2, [1, 3, 4; 4, 2, 5])) %!assert_equal (isempty (repelems (caldays (x2), [1, 3, 4; 0, 0, 0])), true) %!assert_equal (size (reshape (caldays (x2), 1, 9)), [1, 9]) %!assert_equal (caldays (reshape (caldays (x2), 1, 9)), reshape (x2, 1, 9)) %!assert_equal (caldays (reshape (caldays (x2), 1, 1, 9)), reshape (x2, 1, 1, 9)) %!assert_equal (caldays (reshape (caldays (x2), 1, 1, 3, 1, 3)), reshape (x2, 1, 1, 3, 1, 3)) %!assert_equal (caldays (circshift (caldays (x2), 1)), circshift (x2, 1)) %!assert_equal (caldays (circshift (caldays (x2), -2)), circshift (x2, -2)) %!assert_equal (caldays (circshift (caldays (x2), [1, -2])), circshift (x2, [1, -2])) %!assert_equal (caldays (circshift (caldays (x2), -1, 2)), circshift (x2, -1, 2)) %!test %! cD = permute (caldays (x2), [2, 1]); %! assert_equal (caldays (cD), permute (x2, [2, 1])); %!test %! cD = ipermute (permute (caldays (x2), [2, 1]), [2, 1]); %! assert_equal (caldays (cD), x2); %!assert_equal (caldays (caldays (x2)'), x2'); %!assert_equal (caldays (caldays (x2).'), x2'); %!assert_equal (caldays (ctranspose (caldays (x2))), x2'); %!assert_equal (caldays (ctranspose (caldays (x2))), caldays (transpose (caldays (x2)))); ################################################################################ ## ** Reference and Assignment Operations ** ## ################################################################################ ## Available Methods ## ## ## ## 'end' 'subsref' 'subsasgn' ## ## ## ################################################################################ %!test %! cD = caldays (x2); %! assert_equal (caldays (cD(end)), 2); %! assert_equal (caldays (cD(1:end)), x2(:)'); %! assert_equal (caldays (cD(1:end)'), x2(:)); %!test %! cD = caldays ([x2, x2]); %! assert_equal (caldays (cD(1, 2:end-1)), [1, 6, 8, 1]); %! assert_equal (caldays (cD(1:end-1, 2:end-1)), [1, 6, 8, 1; 5, 7, 3, 5]); %! assert_equal (size (cD(1:end, 2:end-1)), [3, 4]); %!test %! cD = caldays ([x2, x2]); %! assert_equal (class (cD(1)), 'calendarDuration'); %! assert_equal (length (cD(1)), 1); %! assert_equal (class (cD(1,:)), 'calendarDuration'); %! assert_equal (length (cD(1,:)), 6); %! assert_equal (length (cD(:,2)), 3); %!test %! cD = caldays (x1); %! assert_equal (cD.proxyArray, [0, 2, 0, 0, 3, 0]); %! assert_equal (cD.Format, 'ymdt'); %!test %! cD = caldays (x1'); %! assert_equal (cD.proxyArray, [0, 2, 0; 0, 3, 0]); %! assert_equal (cD.Format, 'ymdt'); %!test %! cD = calendarDuration ([1; 2], 4, [2; 4]); %! assert_equal (cD.proxyArray, [16, 2, 0; 28, 4, 0]); %! assert_equal (cD.Format, 'ymdt'); %!test %! cD = calendarDuration ([1; 2], 4, [2; 4], hours ([8.5; 14.2]), 'Format', 'qmdt'); %! assert_equal (cD.proxyArray, [16, 2, 8.5; 28, 4, 14.2]); %! assert_equal (cD.Format, 'qmdt'); %!error ... %! caldays (3){1} %!error ... %! subsref (caldays (3), struct ('type', '.', 'subs', 123)); %!error ... %! caldays (3).fdasd %!test %! cD = calendarDuration ([1; 2], 4, [2; 4]); %! cD(1) = calyears (2) + calmonths (1) + caldays (14); %! assert_equal (calyears (cD), [2; 2]); %! assert_equal (calmonths (cD), [25; 28]); %! assert_equal (caldays (cD), [14; 4]); %! assert_equal (days (time (cD)), [0; 0]) %!test %! cD = calendarDuration ([1; 2], 4, [2; 4]); %! cD(end) = 180; %! assert_equal (calyears (cD), [1; 0]); %! assert_equal (calmonths (cD), [16; 0]); %! assert_equal (caldays (cD), [2; 0]); %! assert_equal (days (time (cD)), [0; 180]) %!test %! cD = calendarDuration ([1; 2], 4, [2; 4]); %! cD(end) = hours (12); %! assert_equal (calyears (cD), [1; 0]); %! assert_equal (calmonths (cD), [16; 0]); %! assert_equal (caldays (cD), [2; 0]); %! assert_equal (days (time (cD)), [0; 0.5]) %!test %! cD = calendarDuration ([1; 2], 4, [2; 4]); %! cD(1) = []; %! assert_equal (calyears (cD), 2); %! assert_equal (calmonths (cD), 28); %! assert_equal (caldays (cD), 4); %!test %! cD = calendarDuration ([1, 3; 2, 2], 4, 0); %! cD(1,:) = []; %! assert_equal (calyears (cD), [2, 2]); %! assert_equal (calmonths (cD), [28, 28]); %! assert_equal (caldays (cD), [0, 0]); %! cD(end+1, :) = caldays ([2; 3]); %! assert_equal (caldays (cD(2,:)), [2, 3]); %!test %! cD = calendarDuration ([1, 3; 2, 2], 4, 0); %! cD(:, 2) = []; %! assert_equal (calyears (cD), [1; 2]); %! assert_equal (calmonths (cD), [16; 28]); %! assert_equal (caldays (cD), [0; 0]); %! cD(:, end+1) = calmonths ([2; 3]); %! assert_equal (calmonths (cD(:,2)), [2; 3]); %! cD(:, end+1) = calmonths ([2, 3]); %! assert_equal (calmonths (cD(2,:)), [28, 3, 3]); %! assert_equal (calmonths (cD(:,[2,3])), [2, 2; 3, 3]); %!test %! cD = calendarDuration ([1; 2], 4, [2; 4]); %! cD(2) = []; %! assert_equal (calyears (cD), 1); %! assert_equal (calmonths (cD), 16); %! assert_equal (caldays (cD), 2); %!test %! cD = calendarDuration ([1; 2], 4, [2; 4]); %! assert_equal (cD.Format, 'ymdt'); %! cD.Format = 'qmdt'; %! assert_equal (cD.Format, 'qmdt'); %! cD.Format = 'qmwdt'; %! assert_equal (cD.Format, 'qmwdt'); %!error ... %! cD = caldays([1:5]); cD(2) = {1} %!error ... %! cD = caldays([1:5]); cD{2} = 1 %!error ... %! subsasgn (caldays (3), struct ('type', '.', 'subs', 123)); %!error ... %! cD = caldays([1:5]); cD.Format = ['asd';'ert'] %!error ... %! cD = caldays([1:5]); cD.Format = 'mmdt' %!error ... %! cD = caldays([1:5]); cD.Format = 'ymd' %!error ... %! cD = caldays([1:5]); cD.Format = 'mydt' %!error ... %! cD = caldays([1:5]); cD.fdasd = 1 pr0m1th3as-datatypes-9c9a8d3/inst/tests/categorical.m-tst000066400000000000000000005544641522766574100234610ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the datatypes package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ################################################################################ ## ** Create and convert 'categorical' type ** ## ################################################################################ ## Available Methods ## ## ## ## 'categorical' 'dispstrings' 'cellstr' 'char' ## ## 'double' 'single' 'int64' 'int32' ## ## 'int16' 'int8' 'uint64' 'uint32' ## ## 'uint16' 'uint8' ## ## ## ################################################################################ %!assert_equal (isscalar (categorical ()), false) %!assert_equal (isempty (categorical ()), true) %!assert_equal (class (categorical), 'categorical') %!assert_equal (dispstrings (categorical ()), {}) %!assert_equal (size (categorical), [0, 0]) %!assert_equal (size (categorical (NaN (2, 3, 4))), [2, 3, 4]) %!assert_equal (categories (categorical), {}) %!assert_equal (categories (categorical (NaN)), {}) %!assert_equal (categories (categorical (NaN (3))), {}) %!assert_equal (double (categorical), []) %!assert_equal (double (categorical (NaN)), NaN) %!assert_equal (double (categorical (NaN (3))), NaN (3)) %!test %! stations = categorical (string ({'S1', 'S2', 'S1', 'S3', 'S2'})); %! assert_equal (stations, categorical ({'S1', 'S2', 'S1', 'S3', 'S2'})); %! assert_equal (dispstrings (stations), {'S1', 'S2', 'S1', 'S3', 'S2'}); %! assert_equal (categories (stations), {'S1'; 'S2'; 'S3'}); %!test %! stations = categorical ({'S1', 'S2', 'S1'; 'S2', 'S3', 'S2'}); %! assert_equal (categories (stations), {'S1'; 'S2'; 'S3'}); %! assert_equal (double (stations), [1, 2, 1; 2, 3, 2]); %! assert_equal (size (stations), [2, 3]); %!test %! stations = categorical ({'S1', 'S2', ''; 'S2', 'S3', 'S2'}); %! assert_equal (categories (stations), {'S1'; 'S2'; 'S3'}); %! assert_equal (dispstrings (stations), {'S1', 'S2', ''; 'S2', 'S3', 'S2'}); %! assert_equal (ismissing (stations), isundefined (stations)); %! assert_equal (double (stations), [1, 2, NaN; 2, 3, 2]); %!test %! stations = categorical ({'S1', 'S2', 'S1'; 'S2', 'S3', 'S2'}, 'Ordinal', true); %! assert_equal (categories (stations), {'S1'; 'S2'; 'S3'}); %! assert_equal (double (stations), [1, 2, 1; 2, 3, 2]); %!test %! stations = categorical ({'S3', 'S2', 'S1'; 'S2', 'S3', 'S2'}, 'Ordinal', true); %! assert_equal (categories (stations), {'S1'; 'S2'; 'S3'}); %! assert_equal (double (stations), [3, 2, 1; 2, 3, 2]); %!test %! C = categorical ([3, 2, 3; 2, 1, 2]); %! assert_equal (dispstrings (C), {'3', '2', '3'; '2', '1', '2'}); %! assert_equal (categories (C), {'1'; '2'; '3'}); %! assert_equal (double (C), [3, 2, 3; 2, 1, 2]); %!test %! C = categorical ([1, 2, 3; 2, 1, 2]); %! assert_equal (dispstrings (C), {'1', '2', '3'; '2', '1', '2'}); %! assert_equal (categories (C), {'1'; '2'; '3'}); %! assert_equal (double (C), [1, 2, 3; 2, 1, 2]); %!test %! C = categorical ([3, 2, 3; 2, 1, 2], 'Ordinal', true); %! assert_equal (dispstrings (C), {'3', '2', '3'; '2', '1', '2'}); %! assert_equal (categories (C), {'1'; '2'; '3'}); %! assert_equal (double (C), [3, 2, 3; 2, 1, 2]); %!test %! C = categorical ([1, 2, 3; 2, 1, 2], 'Ordinal', true); %! assert_equal (dispstrings (C), {'1', '2', '3'; '2', '1', '2'}); %! assert_equal (categories (C), {'1'; '2'; '3'}); %! assert_equal (double (C), [1, 2, 3; 2, 1, 2]); %!test %! C = categorical ([3, NaN, 3; 2, 1, 2]); %! assert_equal (dispstrings (C), {'3', '', '3'; '2', '1', '2'}); %! assert_equal (categories (C), {'1'; '2'; '3'}); %! assert_equal (ismissing (C), isundefined (C)); %! assert_equal (double (C), [3, NaN, 3; 2, 1, 2]); %!test %! C = categorical ([1, NaN, 3; 5, 1, 2]); %! assert_equal (dispstrings (C), {'1', '', '3'; '5', '1', '2'}); %! assert_equal (categories (C), {'1'; '2'; '3'; '5'}); %! assert_equal (ismissing (C), isundefined (C)); %! assert_equal (double (C), [1, NaN, 3; 4, 1, 2]); %!test %! C = categorical ([3, NaN, 3; 2, 1, 2], 'Ordinal', true); %! assert_equal (dispstrings (C), {'3', '', '3'; '2', '1', '2'}); %! assert_equal (categories (C), {'1'; '2'; '3'}); %! assert_equal (ismissing (C), isundefined (C)); %! assert_equal (double (C), [3, NaN, 3; 2, 1, 2]); %!test %! C = categorical ([1, NaN, 3; 2, 1, 2], 'Ordinal', true); %! assert_equal (dispstrings (C), {'1', '', '3'; '2', '1', '2'}); %! assert_equal (categories (C), {'1'; '2'; '3'}); %! assert_equal (ismissing (C), isundefined (C)); %! assert_equal (double (C), [1, NaN, 3; 2, 1, 2]); %!test %! C = categorical ({'medium', 'small', 'large'}, 'Ordinal', true); %! assert_equal (dispstrings (C), {'medium', 'small', 'large'}); %! assert_equal (categories (C), {'large'; 'medium'; 'small'}); %! assert_equal (double (C), [2, 3, 1]); %!test %! S = {'medium', 'small', 'large'; 'large', 'medium', 'small'}; %! C = categorical (S, 'Ordinal', true); %! assert_equal (dispstrings (C), S); %! assert_equal (categories (C), {'large'; 'medium'; 'small'}); %! assert_equal (double (C), [2, 3, 1; 1, 2, 3]); %!test %! allsizes = {'medium', 'large', 'small', 'small', 'medium', 'large', 'medium', 'small'}; %! valueset = {'small', 'medium', 'large'}; %! C = categorical (allsizes, valueset, 'Ordinal', true); %! assert_equal (categories (C), {'small'; 'medium'; 'large'}); %! assert_equal (double (C), [2, 3, 1, 1, 2, 3, 2, 1]); %!test %! allsizes = {'medium', 'large', 'small', 'small', 'medium', 'large', 'medium', 'small'}; %! valueset = string ({'small', 'medium', 'large'}); %! C = categorical (allsizes, valueset, 'Ordinal', true); %! assert_equal (categories (C), {'small'; 'medium'; 'large'}); %! assert_equal (double (C), [2, 3, 1, 1, 2, 3, 2, 1]); %!test %! allsizes = string ({'medium', 'large', 'small', 'small', ... %! 'medium', 'large', 'medium', 'small'}); %! valueset = string ({'small', 'medium', 'large'}); %! C = categorical (allsizes, valueset, 'Ordinal', true); %! assert_equal (categories (C), {'small'; 'medium'; 'large'}); %! assert_equal (double (C), [2, 3, 1, 1, 2, 3, 2, 1]); %!test %! allsizes = string ({'medium', 'large', 'small', 'small', ... %! 'medium', 'large', 'medium', 'small'}); %! valueset = {'small', 'medium', 'large'}; %! C = categorical (allsizes, valueset, 'Ordinal', true); %! assert_equal (categories (C), {'small'; 'medium'; 'large'}); %! assert_equal (double (C), [2, 3, 1, 1, 2, 3, 2, 1]); %!test %! C = categorical (days ([3, NaN, 3; 2, 1, 2])); %! assert_equal (dispstrings (C), {'3 days', '', '3 days'; '2 days', '1 day', '2 days'}); %! assert_equal (categories (C), {'1 day'; '2 days'; '3 days'}); %! assert_equal (double (C), [3, NaN, 3; 2, 1, 2]); %!test %! C = categorical (days ([3, NaN, 3; 2, 1, 2]), 'Ordinal', true); %! assert_equal (dispstrings (C), {'3 days', '', '3 days'; '2 days', '1 day', '2 days'}); %! assert_equal (categories (C), {'1 day'; '2 days'; '3 days'}); %! assert_equal (double (C), [3, NaN, 3; 2, 1, 2]); %!test %! C = categorical (days ([3, NaN, 3; 2, 1, 2]), days (1:3), 'Ordinal', true); %! assert_equal (dispstrings (C), {'3 days', '', '3 days'; '2 days', '1 day', '2 days'}); %! assert_equal (categories (C), {'1 day'; '2 days'; '3 days'}); %! assert_equal (double (C), [3, NaN, 3; 2, 1, 2]); %!test %! C = categorical (days ([3, NaN, 3; 2, 1, 2]), days ([3:-1:1]), 'Ordinal', true); %! assert_equal (dispstrings (C), {'3 days', '', '3 days'; '2 days', '1 day', '2 days'}); %! assert_equal (categories (C), {'3 days'; '2 days'; '1 day'}); %! assert_equal (double (C), [1, NaN, 1; 2, 3, 2]); %!test %! C = categorical (days ([3, NaN, 3; 2, 1, 2]), days ([3, 1, 2]), 'Ordinal', true); %! assert_equal (dispstrings (C), {'3 days', '', '3 days'; '2 days', '1 day', '2 days'}); %! assert_equal (categories (C), {'3 days'; '1 day'; '2 days'}); %! assert_equal (double (C), [1, NaN, 1; 3, 2, 3]); %!test %! C = categorical (days ([3, NaN, 3; 2, 1, 2]), days ([2, 3, 1]), 'Ordinal', true); %! assert_equal (dispstrings (C), {'3 days', '', '3 days'; '2 days', '1 day', '2 days'}); %! assert_equal (categories (C), {'2 days'; '3 days'; '1 day'}); %! assert_equal (double (C), [2, NaN, 2; 1, 3, 1]); %!test %! C = categorical ([1:5], [1:5], {'A', 'B', 'C', 'D', 'E'}); %! assert_equal (dispstrings (C), {'A', 'B', 'C', 'D', 'E'}); %! assert_equal (categories (C), {'A'; 'B'; 'C'; 'D'; 'E'}); %! assert_equal (double (C), 1:5); %!test %! C = categorical ([1:5], [5:-1:1], {'A', 'B', 'C', 'D', 'E'}); %! assert_equal (dispstrings (C), {'E', 'D', 'C', 'B', 'A'}); %! assert_equal (categories (C), {'A'; 'B'; 'C'; 'D'; 'E'}); %! assert_equal (double (C), [5, 4, 3, 2, 1]); %!test %! C = categorical ([1:5], [1:5], {'E', 'D', 'C', 'B', 'A'}); %! assert_equal (dispstrings (C), {'E', 'D', 'C', 'B', 'A'}); %! assert_equal (categories (C), {'E'; 'D'; 'C'; 'B'; 'A'}); %! assert_equal (double (C), 1:5); %!test %! C = categorical ([1:5], [5:-1:1], {'E', 'D', 'C', 'B', 'A'}); %! assert_equal (dispstrings (C), {'A', 'B', 'C', 'D', 'E'}); %! assert_equal (categories (C), {'E'; 'D'; 'C'; 'B'; 'A'}); %! assert_equal (double (C), [5, 4, 3, 2, 1]); %!test %! C = categorical (categorical (1:5), categorical (1:5), {'A', 'B', 'C', 'D', 'E'}); %! assert_equal (dispstrings (C), {'A', 'B', 'C', 'D', 'E'}); %! assert_equal (categories (C), {'A'; 'B'; 'C'; 'D'; 'E'}); %! assert_equal (double (C), 1:5); %!test %! C = categorical (categorical (1:5), categorical ([5:-1:1]), {'A', 'B', 'C', 'D', 'E'}); %! assert_equal (dispstrings (C), {'E', 'D', 'C', 'B', 'A'}); %! assert_equal (categories (C), {'A'; 'B'; 'C'; 'D'; 'E'}); %! assert_equal (double (C), [5, 4, 3, 2, 1]); %!test %! C = categorical (categorical (1:5), categorical (1:5), {'E', 'D', 'C', 'B', 'A'}); %! assert_equal (dispstrings (C), {'E', 'D', 'C', 'B', 'A'}); %! assert_equal (categories (C), {'E'; 'D'; 'C'; 'B'; 'A'}); %! assert_equal (double (C), 1:5); %!test %! C = categorical (categorical (1:5), categorical ([5:-1:1]), {'E', 'D', 'C', 'B', 'A'}); %! assert_equal (dispstrings (C), {'A', 'B', 'C', 'D', 'E'}); %! assert_equal (categories (C), {'E'; 'D'; 'C'; 'B'; 'A'}); %! assert_equal (double (C), [5, 4, 3, 2, 1]); %!test %! C = categorical (categorical (1:5), categorical (2:4), {'B', 'C', 'D'}); %! assert_equal (dispstrings (C), {'', 'B', 'C', 'D', ''}); %! assert_equal (categories (C), {'B'; 'C'; 'D'}); %! assert_equal (double (C), [NaN, 1, 2, 3, NaN]); %!test %! C = categorical (categorical ([5:-1:1]), categorical (1:5), {'A', 'B', 'C', 'D', 'E'}); %! assert_equal (dispstrings (C), {'E', 'D', 'C', 'B', 'A'}); %! assert_equal (categories (C), {'A'; 'B'; 'C'; 'D'; 'E'}); %! assert_equal (double (C), [5, 4, 3, 2, 1]); %!test %! C = categorical (categorical ([5:-1:1]), categorical ([5:-1:1]), {'A', 'B', 'C', 'D', 'E'}); %! assert_equal (dispstrings (C), {'A', 'B', 'C', 'D', 'E'}); %! assert_equal (categories (C), {'A'; 'B'; 'C'; 'D'; 'E'}); %! assert_equal (double (C), 1:5); %!test %! C = categorical (categorical ([5:-1:1]), categorical (1:5), {'E', 'D', 'C', 'B', 'A'}); %! assert_equal (dispstrings (C), {'A', 'B', 'C', 'D', 'E'}); %! assert_equal (categories (C), {'E'; 'D'; 'C'; 'B'; 'A'}); %! assert_equal (double (C), [5, 4, 3, 2, 1]); %!test %! C = categorical (categorical ([5:-1:1]), categorical ([5:-1:1]), {'E', 'D', 'C', 'B', 'A'}); %! assert_equal (dispstrings (C), {'E', 'D', 'C', 'B', 'A'}); %! assert_equal (categories (C), {'E'; 'D'; 'C'; 'B'; 'A'}); %! assert_equal (double (C), 1:5); %!test %! C = categorical ([1, 2, 1, NaN, 3, 2, 3]); %! assert_equal (dispstrings (C), ({'1', '2', '1', '', '3', '2', '3'})); %! C = categorical ([1, 2, 1, NaN, 3, 2, 3], [1, 2, 3, NaN], {'a', 's', 'd', 'f'}); %! assert_equal (dispstrings (C), {'a', 's', 'a', 'f', 'd', 's', 'd'}); %! assert_equal (categories (C), {'a'; 's'; 'd'; 'f'}); %! assert_equal (double (C), [1, 2, 1, 4, 3, 2, 3]); %!test %! C = categorical (categorical ([1, 2, 1, NaN, 3, 2, 3])); %! assert_equal (dispstrings (C), ({'1', '2', '1', '', '3', '2', '3'})); %! C = categorical (categorical ([1, 2, 1, NaN, 3, 2, 3]), ... %! categorical ([1, 2, 3, NaN]), {'a', 's', 'd', 'f'}); %! assert_equal (dispstrings (C), {'a', 's', 'a', 'f', 'd', 's', 'd'}); %! assert_equal (categories (C), {'a'; 's'; 'd'; 'f'}); %! assert_equal (double (C), [1, 2, 1, 4, 3, 2, 3]); %!test %! C = categorical (1:5, [1, 2, 3], {'a', 'b', 'b'}); %! assert_equal (dispstrings (C), {'a', 'b', 'b', '', ''}); %! assert_equal (categories (C), {'a'; 'b'}); %! assert_equal (double (C), [1, 2, 2, NaN, NaN]); %!test %! C = categorical (categorical (1:5), categorical ([1, 2, 3]), {'a', 'b', 'b'}); %! assert_equal (dispstrings (C), {'a', 'b', 'b', '', ''}); %! assert_equal (categories (C), {'a'; 'b'}); %! assert_equal (double (C), [1, 2, 2, NaN, NaN]); %!test %! C = categorical ({'1', '2', '1', '2'}, {' ', '1', '2'}, {'a','s','d'}); %! assert_equal (dispstrings (C), {'s', 'd', 's', 'd'}); %! assert_equal (categories (C), {'a'; 's'; 'd'}); %! assert_equal (double (C), [2, 3, 2, 3]); %!test %! C = categorical ({'1', '2', '', '2'}, {' ', '1', '2'}, {'a','s','d'}); %! assert_equal (dispstrings (C), {'s', 'd', 'a', 'd'}); %! assert_equal (categories (C), {'a'; 's'; 'd'}); %! assert_equal (double (C), [2, 3, 1, 3]); %!test %! C = categorical ({'1', '2', ' ', '2'}, {' ', '1', '2'}, {'a','s','d'}); %! assert_equal (dispstrings (C), {'s', 'd', 'a', 'd'}); %! assert_equal (categories (C), {'a'; 's'; 'd'}); %! assert_equal (double (C), [2, 3, 1, 3]); %!test %! C = categorical (string ({'1', '2', '1', '2'}), string ({' ', '1', '2'}), {'a','s','d'}); %! assert_equal (dispstrings (C), {'s', 'd', 's', 'd'}); %! assert_equal (categories (C), {'a'; 's'; 'd'}); %! assert_equal (double (C), [2, 3, 2, 3]); %!test %! C = categorical (string ({'1', '2', '', '2'}), string ({' ', '1', '2'}), {'a','s','d'}); %! assert_equal (dispstrings (C), {'s', 'd', 'a', 'd'}); %! assert_equal (categories (C), {'a'; 's'; 'd'}); %! assert_equal (double (C), [2, 3, 1, 3]); %!test %! C = categorical (string ({'1', '2', ' ', '2'}), string ({' ', '1', '2'}), {'a','s','d'}); %! assert_equal (dispstrings (C), {'s', 'd', 'a', 'd'}); %! assert_equal (categories (C), {'a'; 's'; 'd'}); %! assert_equal (double (C), [2, 3, 1, 3]); %!test %! C = categorical (string ({'1', '2', ' ', '2'}), string ({missing, '1', '2'}), {'a','s','d'}); %! assert_equal (dispstrings (C), {'s', 'd', 'a', 'd'}); %! assert_equal (categories (C), {'a'; 's'; 'd'}); %! assert_equal (double (C), [2, 3, 1, 3]); %!test %! C = categorical (days ([1, 2, 1, 3, NaN])); %! assert_equal (dispstrings (C), {'1 day', '2 days', '1 day', '3 days', ''}); %! assert_equal (categories (C), {'1 day'; '2 days'; '3 days'}); %! assert_equal (double (C), [1, 2, 1, 3, NaN]); %!test %! C = categorical (days ([1, 2, 1, 3, NaN]), days ([1, 2, 3, NaN]), {'a', 'b', 'c', 'd'}); %! assert_equal (dispstrings (C), {'a', 'b', 'a', 'c', 'd'}); %! assert_equal (categories (C), {'a'; 'b'; 'c'; 'd'}); %! assert_equal (double (C), [1, 2, 1, 3, 4]); %!test %! C = categorical (days ([1, 2, 1, 3, NaN]), days ([1, 2, 3, NaN]), {'a', 'c', 'c', 'd'}); %! assert_equal (dispstrings (C), {'a', 'c', 'a', 'c', 'd'}); %! assert_equal (categories (C), {'a'; 'c'; 'd'}); %! assert_equal (double (C), [1, 2, 1, 2, 3]); %!test %! DT = [datetime('yesterday'), datetime('today'), datetime('tomorrow')]; %! C = categorical (DT); %! assert_equal (dispstrings (C), dispstrings (DT)); %! assert_equal (categories (C), dispstrings (DT')); %! assert_equal (double (C), [1, 2, 3]); %!test %! DT = [datetime('yesterday'), datetime('today'), datetime('tomorrow')]; %! C = categorical (DT, DT, {'a', 'b', 'c'}); %! assert_equal (dispstrings (C), {'a', 'b', 'c'}); %! assert_equal (categories (C), {'a'; 'b'; 'c'}); %! assert_equal (double (C), [1, 2, 3]); %!test %! DT = [datetime('yesterday'), datetime('today'), NaT, datetime('tomorrow')]; %! C = categorical (DT, DT, {'a', 'b', 'c', 'd'}); %! assert_equal (dispstrings (C), {'a', 'b', 'c', 'd'}); %! assert_equal (categories (C), {'a'; 'b'; 'c'; 'd'}); %! assert_equal (double (C), [1, 2, 3, 4]); %!test %! DT = [datetime('yesterday'), datetime('today'), NaT, datetime('tomorrow')]; %! C = categorical (DT, DT, {'a', 'b', 'c', 'c'}); %! assert_equal (dispstrings (C), {'a', 'b', 'c', 'c'}); %! assert_equal (categories (C), {'a'; 'b'; 'c'}); %! assert_equal (double (C), [1, 2, 3, 3]); %!test %! C = categorical ([false, true, true, false, true]); %! assert_equal (dispstrings (C), {'false', 'true', 'true', 'false', 'true'}); %! assert_equal (categories (C), {'false'; 'true'}); %! assert_equal (double (C), [1, 2, 2, 1, 2]); %!error ... %! categorical (1, 'Ordinal', 3) %!error ... %! categorical (1, 'Ordinal', 'true') %!error ... %! categorical (1, 'Protected', 3) %!error ... %! categorical (1, 'Protected', 'true') %!error ... %! categorical (categorical (1:5), categorical ([1, NaN, NaN])) %!error ... %! categorical (categorical (1:5), categorical ([1, 1, 1])) %!error ... %! categorical (categorical (1:5), categorical ([1, 2, NaN])) %!error ... %! categorical (categorical (1:5), categorical ([1, 2, NaN]), [1, 2]) %!error ... %! categorical (categorical (1:5), categorical ([1, 2, NaN]), {'1', '2'}) %!error ... %! categorical (categorical (1:5), categorical ([1, 2, NaN]), {'1', '2', ''}) %!error ... %! categorical (categorical (1:5), categorical ([1, 2, 3]), [1, 2]) %!error ... %! categorical (categorical (1:5), categorical ([1, 2, 3]), {'1', '2'}) %!error ... %! categorical (categorical (1:5), categorical ([1, 2, 3]), {'1', '2', ''}) %!error ... %! categorical (categorical (1:5), [1, 2, 3]) %!error categorical ({1}) %!error ... %! categorical ({'1', '2', '3'}, [1, 2, 3]) %!error ... %! categorical (days ([1, 2, 3]), [1, 2, 3]) %!error ... %! categorical (1:5, [1, 2, NaN]) %!error ... %! categorical (1:5, [1, NaN, NaN], {'a', 'b', 'c'}) %!error ... %! categorical (1:5, [1, 2, 2]) %!error ... %! categorical (1:5, [2, 2, NaN], {'a', 'b', 'c'}) %!error ... %! categorical (1:5, [1, 2, NaN], ['a'; 'b'; 'c']) %!error ... %! categorical (1:5, [1, 2, NaN], days ([1, 2, 3])) %!error ... %! categorical (1:5, [1, 2, NaN], {'a', 'b'}) %!error ... %! categorical (1:5, [1, 2, NaN], {'a', 'b', ''}) %!test %! C = categorical ([1, 2, 1, NaN, 3, 2, 3]); %! assert_equal (cellstr (C), dispstrings (C)); %!test %! C = categorical ([1, 2, 1, NaN, 3, 2, 3]); %! assert_equal (char (C), char (cellstr (C))); %! assert_equal (size (char (C)), [7, 11]); %! assert_equal (class (char (C)), 'char'); %!test %! C = categorical ([1, 2, 1, NaN, 3, 2, 3]); %! assert_equal (double (C), [1, 2, 1, NaN, 3, 2, 3]); %! assert_equal (single (C), single ([1, 2, 1, NaN, 3, 2, 3])); %! assert_equal (int64 (C), int64 ([1, 2, 1, 0, 3, 2, 3])); %! assert_equal (int32 (C), int32 ([1, 2, 1, 0, 3, 2, 3])); %! assert_equal (int16 (C), int16 ([1, 2, 1, 0, 3, 2, 3])); %! assert_equal (int8 (C), int8 ([1, 2, 1, 0, 3, 2, 3])); %! assert_equal (uint64 (C), uint64 ([1, 2, 1, 0, 3, 2, 3])); %! assert_equal (uint32 (C), uint32 ([1, 2, 1, 0, 3, 2, 3])); %! assert_equal (uint16 (C), uint16 ([1, 2, 1, 0, 3, 2, 3])); %! assert_equal (uint8 (C), uint8 ([1, 2, 1, 0, 3, 2, 3])); %!test %! C = categorical (1:256); %! assert_equal (int8 (C), int8 ([1:126, 127*ones(1, 130)])); %! assert_equal (uint8 (C), uint8 ([1:255, 255])); ################################################################################ ## ** Summary Information ** ## ################################################################################ ## Available Methods ## ## ## ## 'summary' 'categories' 'countcats' 'length' ## ## 'size' 'ndims' 'numel' 'keyHash' ## ## ## ################################################################################ %!test %! C = categorical (repmat ([1, 3; 4, 2], 3, 3, 2)); %! s = summary (C, 1); %! assert_equal (s.Size, [6, 6, 2]); %! assert_equal (s.Type, 'categorical'); %! assert_equal (s.Categories, {'1'; '2'; '3'; '4'}); %! assert_equal (s.Counts, countcats (C, 1)); %! assert_equal (size (s.Counts), [4, 6, 2]); %! assert_equal (s.NumMissing, sum (isundefined (C), 1)); %! assert_equal (size (s.NumMissing), [1, 6, 2]); %!test %! C = categorical (repmat ([1, 3; 4, 2], 3, 3, 2)); %! s = summary (C, 2); %! assert_equal (s.Size, [6, 6, 2]); %! assert_equal (s.Type, 'categorical'); %! assert_equal (s.Categories, {'1'; '2'; '3'; '4'}); %! assert_equal (s.Counts, countcats (C, 2)); %! assert_equal (size (s.Counts), [6, 4, 2]); %! assert_equal (s.NumMissing, sum (isundefined (C), 2)); %! assert_equal (size (s.NumMissing), [6, 1, 2]); %!test %! C = categorical (repmat ([1, 3; 4, 2], 3, 3, 2)); %! s = summary (C, 3); %! assert_equal (s.Size, [6, 6, 2]); %! assert_equal (s.Type, 'categorical'); %! assert_equal (s.Categories, {'1'; '2'; '3'; '4'}); %! assert_equal (s.Counts, countcats (C, 3)); %! assert_equal (size (s.Counts), [6, 6, 4]); %! assert_equal (s.NumMissing, sum (isundefined (C), 3)); %! assert_equal (size (s.NumMissing), [6, 6]); %!test %! C = categorical (repmat ([1, 3; 4, 2], 3, 3, 2)); %! s = summary (C, 4); %! assert_equal (s.Size, [6, 6, 2]); %! assert_equal (s.Type, 'categorical'); %! assert_equal (s.Categories, {'1'; '2'; '3'; '4'}); %! assert_equal (s.Counts, countcats (C, 4)); %! assert_equal (size (s.Counts), [6, 6, 2, 4]); %! assert_equal (s.NumMissing, sum (isundefined (C), 4)); %! assert_equal (size (s.NumMissing), [6, 6, 2]); %!test %! C = categorical (string ({'1', '2', ' ', '2'}), string ({' ', '1', '2'}), ... %! {'a', 's', 'd'}); %! s = summary (C); %! assert_equal (s.Size, [1, 4]); %! assert_equal (s.Type, 'categorical'); %! assert_equal (s.Categories, {'a'; 's'; 'd'}); %! assert_equal (s.Counts, [1, 1, 2]); %! assert_equal (s.NumMissing, 0); %!test %! C = categorical ({'A'; 'B'; 'C'; ''; 'C'; ''; 'C'; 'A'; ''}); %! C = repmat (C, 1, 4, 3); %! s = summary (C); %! assert_equal (s.Size, [9, 4, 3]); %! assert_equal (s.Type, 'categorical'); %! assert_equal (s.Categories, {'A'; 'B'; 'C'}); %! assert_equal (s.Counts, countcats (C)); %! assert_equal (size (s.Counts), [3, 4, 3]); %! assert_equal (s.NumMissing, sum (isundefined (C))); %! assert_equal (size (s.NumMissing), [1, 4, 3]); %!test %! C = categorical ({'A'; 'B'; 'C'; ''; 'C'; ''; 'C'; 'A'; ''}); %! C = repmat (C, 1, 4, 3); %! s = summary (C, 2); %! assert_equal (s.Size, [9, 4, 3]); %! assert_equal (s.Type, 'categorical'); %! assert_equal (s.Categories, {'A'; 'B'; 'C'}); %! assert_equal (s.Counts, countcats (C, 2)); %! assert_equal (size (s.Counts), [9, 3, 3]); %! assert_equal (s.NumMissing, sum (isundefined (C), 2)); %! assert_equal (size (s.NumMissing), [9, 1, 3]); %!test %! C = categorical ({'A'; 'B'; 'C'; ''; 'C'; ''; 'C'; 'A'; ''}); %! C = repmat (C, 1, 4, 3); %! s = summary (C, 3); %! assert_equal (s.Size, [9, 4, 3]); %! assert_equal (s.Type, 'categorical'); %! assert_equal (s.Categories, {'A'; 'B'; 'C'}); %! assert_equal (s.Counts, countcats (C, 3)); %! assert_equal (size (s.Counts), [9, 4, 3]); %! assert_equal (s.NumMissing, sum (isundefined (C), 3)); %! assert_equal (size (s.NumMissing), [9, 4]); %!test %! C = categorical ({'A'; 'B'; 'C'; ''; 'C'; ''; 'C'; 'A'; ''}); %! C = repmat (C, 1, 4, 3); %! s = summary (C, 4); %! assert_equal (s.Size, [9, 4, 3]); %! assert_equal (s.Type, 'categorical'); %! assert_equal (s.Categories, {'A'; 'B'; 'C'}); %! assert_equal (s.Counts, countcats (C, 4)); %! assert_equal (size (s.Counts), [9, 4, 3, 3]); %! assert_equal (s.NumMissing, sum (isundefined (C), 4)); %! assert_equal (size (s.NumMissing), [9, 4, 3]); %!test %! s = summary (categorical ([1, 2, 1, 3, 2, 3, 2, 1, 3, 2], [1, 2, 3], ... %! {'l','m','h'}, 'Ordinal', true), 1, ... %! 'Statistics', {'min', 'nummissing', 'counts', 'max', 'median'}); %! assert_equal (s.Size, [1, 10]); %! assert_equal (s.Type, 'categorical'); %! assert_equal (s.Categories, {'l'; 'm'; 'h'}); %! assert_equal (size (s.Counts), [3, 10]); %! C = categorical ({'l','m','l','h','m','h','m','l','h','m'}, ... %! {'l','m','h'}, {'l','m','h'}, 'Ordinal', true); %! assert_equal (s.Min, C); %! assert_equal (s.NumMissing, zeros (1, 10)); %! assert_equal (s.Max, C); %! assert_equal (s.Median, C); %!test %! s = summary (categorical ([1, 2, 1, 3, 2, 3, 2, 1, 3, NaN], [1, 2, 3], ... %! {'l','m','h'}, 'Ordinal', true), 1, ... %! 'Statistics', {'min', 'nummissing', 'counts', 'max', 'median'}); %! assert_equal (s.Size, [1, 10]); %! assert_equal (s.Type, 'categorical'); %! assert_equal (s.Categories, {'l'; 'm'; 'h'}); %! assert_equal (size (s.Counts), [3, 10]); %! C = categorical ({'l','m','l','h','m','h','m','l','h',''}, ... %! {'l','m','h'}, {'l','m','h'}, 'Ordinal', true); %! assert_equal (isequaln (s.Min, C), true); %! assert_equal (s.NumMissing, [0, 0, 0, 0, 0, 0, 0, 0, 0, 1]); %! assert_equal (isequaln (s.Max, C), true); %! assert_equal (isequaln (s.Median, C), true); %!test %! s = summary (categorical ([1, 2, 1, 3, 2, 3, 2, 1, 3, NaN], [1, 2, 3], ... %! {'l','m','h'}, 'Ordinal', true), 1, ... %! 'Statistics', {'min', 'nummissing', 'max', 'median', 'mode'}); %! assert_equal (s.Size, [1, 10]); %! assert_equal (s.Type, 'categorical'); %! assert_equal (s.Categories, {'l'; 'm'; 'h'}); %! assert_equal (size (s.Min), [1, 10]); %! assert_equal (s.NumMissing, [0, 0, 0, 0, 0, 0, 0, 0, 0, 1]); %! assert_equal (size (s.Max), [1, 10]); %! assert_equal (size (s.Median), [1, 10]); %! assert_equal (size (s.Mode), [1, 10]); %!test %! s = summary (categorical ([1, 2, 1, 3, 2, 3, 2, 1, 3, NaN], [1, 2, 3], ... %! {'l','m','h'}, 'Ordinal', true), 1, ... %! 'Statistics', {'min', 'counts', 'max', 'median', 'mode'}); %! assert_equal (s.Size, [1, 10]); %! assert_equal (s.Type, 'categorical'); %! assert_equal (s.Categories, {'l'; 'm'; 'h'}); %! assert_equal (size (s.Counts), [3, 10]); %! assert_equal (size (s.Min), [1, 10]); %! assert_equal (size (s.Max), [1, 10]); %! assert_equal (size (s.Median), [1, 10]); %! assert_equal (size (s.Mode), [1, 10]); %!error ... %! summary (categorical ([1:3]), 2, 3) %!error ... %! summary (categorical ([1:3]), 0) %!error ... %! summary (categorical ([1:3]), [1, 2]) %!error ... %! summary (categorical ([1:3]), 'a') %!assert_equal (categories (categorical), {}) %!assert_equal (categories (categorical (NaN (3), [1, 2, 3])), {'1'; '2'; '3'}) %!assert_equal (categories (categorical (1:5)), {'1'; '2'; '3'; '4'; '5'}) %!assert_equal (countcats (categorical), []) %!assert_equal (countcats (categorical ([NaN, 1;NaN,2;NaN, NaN])), [0, 1; 0, 1]) %!assert_equal (countcats (categorical (NaN (3))), zeros (0, 3)) %!assert_equal (countcats (categorical (NaN (3), [1, 2, 3])), zeros (3)) %!assert_equal (countcats (categorical (NaN (2, 0, 3))), zeros (0, 0, 3)) %!assert_equal (countcats (categorical (NaN (2, 0, 3), [1, 2, 3])), zeros (3, 0, 3)) %!assert_equal (countcats (categorical (NaN (2, 0, 3), [1, 2, 3]), 2), zeros (2, 3, 3)) %!assert_equal (countcats (categorical (ones (3, 2, 0, 2))), ones (0, 2, 0, 2)) %!assert_equal (countcats (categorical (ones (3, 2, 0, 2)), 3), ones (3, 2, 0, 2)) %!error ... %! countcats (categorical (ones (3, 2, 0, 2)), 0) %!error ... %! countcats (categorical (ones (3, 2, 0, 2)), [1, 2]) %!error ... %! countcats (categorical (ones (3, 2, 0, 2)), 'a') %!assert_equal (length (categorical), 0) %!assert_equal (length (categorical (ones (3, 2, 0, 2))), 0) %!assert_equal (length (categorical (ones (2, 3, 4))), 4) %!assert_equal (length (categorical (ones (3, 4, 2))), 4) %!assert_equal (length (categorical (ones (3))), 3) %!assert_equal (size (categorical), [0, 0]) %!assert_equal (size (categorical ([1:5])), [1, 5]) %!assert_equal (size (categorical (ones (2, 3, 4))), [2, 3, 4]) %!assert_equal (size (categorical (ones (2, 3, 4)), 1), 2) %!assert_equal (size (categorical (ones (2, 3, 4)), 2), 3) %!assert_equal (size (categorical (ones (2, 3, 4)), 3), 4) %!assert_equal (size (categorical (ones (2, 3, 4)), 4), 1) %!test %! [d1, d2, d3] = size (categorical (ones (2, 3, 4))); %! assert_equal (d1, 2); %! assert_equal (d2, 3); %! assert_equal (d3, 4); %!test %! [d1, d2] = size (categorical (ones (2, 3, 4)), 1, 2); %! assert_equal (d1, 2); %! assert_equal (d2, 3); %!test %! [d2, d3] = size (categorical (ones (2, 3, 4)), 2, 3); %! assert_equal (d2, 3); %! assert_equal (d3, 4); %!test %! [d2, d3] = size (categorical (ones (2, 3, 4)), [2, 3]); %! assert_equal (d2, 3); %! assert_equal (d3, 4); %!error ... %! [d2, d3] = size (categorical (ones (2, 3, 4)), [2, 3, 4]) %!assert_equal (ndims (categorical), 2) %!assert_equal (ndims (categorical (ones (2, 3, 4))), 3) %!assert_equal (ndims (categorical (ones (2, 3, 0, 4))), 4) %!assert_equal (numel (categorical), 0) %!assert_equal (numel (categorical (1:5)), 5) %!assert_equal (numel (categorical (ones (2, 3, 4))), 24) %!assert_equal (keyHash (categorical), uint64 (600793793929482348)) %!assert_equal (keyHash (categorical (1:5)), uint64 (6763637898068551630)) %!assert_equal (keyHash (categorical (ones (3))), uint64 (1656458728321551574)) %!assert_equal (keyHash (categorical (ones (2, 3, 4))), uint64 (16817716959342937566)) %!assert_equal (keyHash (categorical (NaN (3))), uint64 (8652545556995177325)) %!test %! key = keyHash (categorical ([1, 2; 3, 4])); %! assert_equal (key, uint64 (3893744934052072694)); %!test %! key = keyHash (categorical ([1, 2; 3, 4], [1, 2, 3, 4])); %! assert_equal (key, uint64 (3893744934052072694)); %!test %! key = keyHash (categorical ([1, 2; 3, 4], [1, 2, 3, 4], {'a', 'b', 'c', 'd'})); %! assert_equal (key, uint64 (7189439461002363766)); %!test %! key = keyHash (categorical (1:3, [1, 2, 3])); %! assert_equal (key, uint64 (17803270835922091242)); %!test %! base_key = uint64 (2342124352342344234); %! key = keyHash (categorical (1:3, [1, 2, 3]), base_key); %! assert_equal (key, uint64 (14454225355392922673)); %!error ... %! keyHash (categorical (1:5), uint64 ([1, 2])) %!error ... %! keyHash (categorical (1:5), 2231107818551636405) ################################################################################ ## ** Query Operations ** ## ################################################################################ ## Available Methods ## ## ## ## 'iscategory' 'iscolumn' 'isempty' 'isequal' ## ## 'isequaln' 'ismatrix' 'ismember' 'ismissing' ## ## 'isordinal' 'isprotected' 'isrow' 'isscalar' ## ## 'issorted' 'issortedrows' 'isundefined' 'isvector' ## ## ## ################################################################################ %!assert_equal (iscategory (categorical, ''), false) %!assert_equal (iscategory (categorical (1:3), '1'), true) %!assert_equal (iscategory (categorical (1:3), '4'), false) %!assert_equal (iscategory (categorical (1:3), string ('1')), true) %!assert_equal (iscategory (categorical (1:3), string ('4')), false) %!assert_equal (iscategory (categorical (1:3), {'1', '2'}), [true, true]) %!assert_equal (iscategory (categorical (1:3), {'1'; '2'}), [true; true]) %!assert_equal (iscategory (categorical (1:3), {'1', '4', '3'}), logical ([1, 0, 1])) %!test %! C = categorical ([], [1, 2, 3], {'A', 'B', 'C'}); %! assert_equal (iscategory (C, 'A'), true); %! assert_equal (iscategory (C, '1'), false); %! assert_equal (iscategory (C, string ('A')), true); %! assert_equal (iscategory (C, string (1:3)), [false, false, false]); %!error ... %! iscategory (categorical) %!assert_equal (iscolumn (categorical), false) %!assert_equal (iscolumn (categorical (1)), true) %!assert_equal (iscolumn (categorical (1:2)), false) %!assert_equal (iscolumn (categorical ([1:2]')), true) %!assert_equal (iscolumn (categorical (ones (2))), false) %!assert_equal (isempty (categorical), true) %!assert_equal (isempty (categorical (1)), false) %!assert_equal (isempty (categorical (ones (3, 2, 0, 3))), true) %!assert_equal (isempty (categorical ([], [1, 2, 3], {'A', 'B', 'C'})), true) %!assert_equal (isempty (categorical (NaN (2, 0), [1, 2, 3], {'A', 'B', 'C'})), true) %!assert_equal (isequal (categorical ([1, 1, 1]), ... %! categorical ([2, 2, 2], [1, 2], {'2', '1'})), true) %!assert_equal (isequal (categorical ([1, NaN, 1]), ... %! categorical ([2, NaN, 2], [1, 2], {'2', '1'})), false) %!assert_equal (isequal (categorical ([1, 1, NaN]), ... %! categorical ([2, 2, NaN], [1, 2], {'2', '1'})), false) %!assert_equal (isequal (categorical ([1, 1, 1]), ... %! categorical ([1, 1, 1], [1, 2], {'2', '1'})), false) %!assert_equal (isequal (categorical ([1, 1, 1], [1, 2]), ... %! categorical ([2, 2, 2], [1, 2], {'2', '1'})), true) %!assert_equal (isequal (categorical ([NaN, 1, 1], [1, 2]), ... %! categorical ([NaN, 2, 2], [1, 2], {'2', '1'})), false) %!assert_equal (isequal (categorical ([1, NaN, 1], [1, 2]), ... %! categorical ([2, NaN, 2], [1, 2], {'2', '1'})), false) %!assert_equal (isequal (categorical ([1, 1, 1], [1, 2]), ... %! categorical ([1, 1, 1], [1, 2], {'2', '1'})), false) %!assert_equal (isequal ({'1', '1', '1'}, categorical ([2, 2, 2], [1, 2], {'2', '1'})), true) %!assert_equal (isequal (categorical ([1, 1, 1]), {'1', '1', '1'}), true) %!assert_equal (isequal (string ({'1', '1', '1'}), ... %! categorical ([2, 2, 2], [1, 2], {'2', '1'})), true) %!assert_equal (isequal (categorical ([2, 2, 2], [1, 2], {'2', '1'}), ... %! string ({'1', '1', '1'})), true) %!assert_equal (isequal (missing, categorical), false) %!assert_equal (isequal (missing, categorical (NaN)), false) %!assert_equal (isequal (categorical (1), string (1)), true) %!assert_equal (isequal (string (1), categorical (1)), true) %!assert_equal (isequal (categorical (NaN), string (missing)), false) %!test %! C1 = categorical ([1, 1, 1], [1, 2], {'A', 'B'}); %! C2 = categorical ([1, 1, 1], [1, 2], {'A', 'B'}); %! assert_equal (isequal (C1, C2), true); %!test %! C1 = categorical ([1, 1, 1], [1, 2], {'A', 'B'}); %! C2 = categorical ([2, 2, 2], [1, 2], {'B', 'A'}); %! assert_equal (isequal (C1, C2), true); %! assert_equal (isequal (C1, C2, {'A', 'A', 'A'}), true); %! assert_equal (isequal (C1, C2, {'A', '', 'A'}), false); %!test %! C1 = categorical ([1, 1, 1], [1, 2], {'A', 'B'}); %! C2 = categorical ([1, 1, 1], [1, 2], {'A', 'B'}, 'Ordinal', true); %! assert_equal (isequal (C1, C2), false); %!test %! C1 = categorical ([1, 1, 1], [1, 2], {'A', 'B'}, 'Ordinal', true); %! C2 = categorical ([1, 1, 1], [1, 2], {'A', 'B'}); %! assert_equal (isequal (C1, C2), false); %!test %! C1 = categorical ([1, 1, 1], [1, 2], {'A', 'B'}, 'Ordinal', true); %! C2 = categorical ([1, 1, 1], [1, 2], {'A', 'B'}, 'Ordinal', true); %! assert_equal (isequal (C1, C2), true); %!test %! C1 = categorical ([1, 1, 1], [1, 2], {'A', 'B'}, 'Ordinal', true); %! C2 = categorical ([2, 2, 2], [1, 2], {'B', 'A'}, 'Ordinal', true); %! assert_equal (isequal (C1, C2), false); %!assert_equal (isequaln (categorical ([1, 1, 1]), ... %! categorical ([2, 2, 2], [1, 2], {'2', '1'})), true) %!assert_equal (isequaln (categorical ([1, NaN, 1]), ... %! categorical ([2, NaN, 2], [1, 2], {'2', '1'})), true) %!assert_equal (isequaln (categorical ([1, 1, NaN]), ... %! categorical ([2, 2, NaN], [1, 2], {'2', '1'})), true) %!assert_equal (isequaln (categorical ([1, 1, 1], [1, 2]), ... %! categorical ([2, 2, 2], [1, 2], {'2', '1'})), true) %!assert_equal (isequaln (categorical ([NaN, 1, 1], [1, 2]), ... %! categorical ([NaN, 2, 2], [1, 2], {'2', '1'})), true) %!assert_equal (isequaln (categorical ([1, NaN, 1], [1, 2]), ... %! categorical ([2, NaN, 2], [1, 2], {'2', '1'})), true) %!assert_equal (isequaln (missing, categorical), false) %!assert_equal (isequaln (missing, categorical (NaN)), true) %!assert_equal (isequaln (categorical (NaN), string (missing)), true) %!assert_equal (isequaln (repmat (missing, 2), categorical (NaN (2))), true) %!test %! C1 = categorical ([1, 1, NaN], [1, 2], {'A', 'B'}); %! C2 = categorical ([1, 1, NaN], [1, 2], {'A', 'B'}); %! assert_equal (isequaln (C1, C2), true); %!test %! C1 = categorical ([1, NaN, 1], [1, 2], {'A', 'B'}); %! C2 = categorical ([2, NaN, 2], [1, 2], {'B', 'A'}); %! assert_equal (isequaln (C1, C2), true); %! assert_equal (isequaln (C1, C2, {'A', 'A', 'A'}), false); %! assert_equal (isequaln (C1, C2, {'A', '', 'A'}), true); %!test %! C1 = categorical ([1, NaN, 1], [1, 2], {'A', 'B'}); %! C2 = categorical ([1, NaN, 1], [1, 2], {'A', 'B'}, 'Ordinal', true); %! assert_equal (isequaln (C1, C2), false); %!test %! C1 = categorical ([1, NaN, 1], [1, 2], {'A', 'B'}, 'Ordinal', true); %! C2 = categorical ([1, NaN, 1], [1, 2], {'A', 'B'}); %! assert_equal (isequaln (C1, C2), false); %!test %! C1 = categorical ([1, NaN, 1], [1, 2], {'A', 'B'}, 'Ordinal', true); %! C2 = categorical ([1, NaN, 1], [1, 2], {'A', 'B'}, 'Ordinal', true); %! assert_equal (isequaln (C1, C2), true); %!test %! C1 = categorical ([1, 1, 1], [1, 2], {'A', 'B'}, 'Ordinal', true); %! C2 = categorical ([2, 2, 2], [1, 2], {'B', 'A'}, 'Ordinal', true); %! assert_equal (isequaln (C1, C2), false); %!test %! C1 = categorical ([1, NaN, 1], [1, 2], {'A', 'B'}, 'Ordinal', true); %! C2 = categorical ([2, NaN, 2], [1, 2], {'B', 'A'}, 'Ordinal', true); %! assert_equal (isequaln (C1, C2), false); %!assert_equal (ismatrix (categorical), true) %!assert_equal (ismatrix (categorical (NaN)), true) %!assert_equal (ismatrix (categorical (ones (1, 0))), true) %!assert_equal (ismatrix (categorical (ones (2, 1))), true) %!assert_equal (ismatrix (categorical (ones (2, 2, 1))), true) %!assert_equal (ismatrix (categorical (ones (2, 1, 2))), false) %!assert_equal (ismatrix (categorical (ones (2, 0, 2))), false) %!assert_equal (ismember (categorical (1:3), '1'), [true, false, false]) %!assert_equal (ismember (categorical (1:3), string ('1')), [true, false, false]) %!assert_equal (ismember (categorical (1:3), {'1', '2'}), [true, true, false]) %!assert_equal (ismember (categorical (1:3), string ({'1', '2'})), [true, true, false]) %!assert_equal (ismember ('1', categorical (1:3)), true) %!assert_equal (ismember ({'1', '2'}, categorical (1:3)), [true, true]) %!assert_equal (ismember ({'1', '2'}, categorical ([1:3]')), [true, true]) %!assert_equal (ismember ({'1'; '2'}, categorical (1:3)), [true; true]) %!assert_equal (ismember ({'1', '2'}, categorical ([1, 2, 1, 3, 2, 3, 2, 1])), ... %! [true, true]) %!assert_equal (ismember (categorical ([1, 2, 1, 3, 2, 3, 2, 1]), {'1','2'}), ... %! logical ([1, 1, 1, 0, 1, 0, 1, 1])) %!assert_equal (ismember (categorical ([1, 2, 1, 3, 2, 3, 2, 1]), categorical (1:3)), ... %! true (1, 8)) %!assert_equal (ismember (categorical (1:3), categorical([1, 2, 1, 3, 2, 3, 2, 1])), ... %! true (1, 3)) %!assert_equal (ismember (categorical ([1, 2, 1, 3, 2, 3, 2, 1]), categorical (1:2)), ... %! logical ([1, 1, 1, 0, 1, 0, 1, 1])) %!assert_equal (ismember (categorical ([1; 2]), categorical ([1, 2, 1, 3, 2, 3, 2, 1])), ... %! [true; true]) %!test %! C1 = categorical ([1, 2, 1, 3, 2, 3, 2, NaN]); %! C2 = categorical ([1, NaN, 3]); %! assert_equal (ismember (C1, C2), logical ([1, 0, 1, 1, 0, 1, 0, 0])); %! assert_equal (ismember (C2, C1), [true, false, true]); %! assert_equal (ismember (C1', C2), logical ([1; 0; 1; 1; 0; 1; 0; 0])); %! assert_equal (ismember (C2', C1), [true; false; true]); %!test %! C1 = categorical ([1, 2, 1, 3, 2, 3, 2, NaN], [1, 2, 3], 'Ordinal', true); %! C2 = categorical ([1, NaN, 3], [1, 2, 3], 'Ordinal', true); %! assert_equal (ismember (C1, C2), logical ([1, 0, 1, 1, 0, 1, 0, 0])); %! assert_equal (ismember (C2, C1), [true, false, true]); %! assert_equal (ismember (C1', C2), logical ([1; 0; 1; 1; 0; 1; 0; 0])); %! assert_equal (ismember (C2', C1), [true; false; true]); %!test %! C1 = categorical ([1, 2, 1, 3, 2, 3, 2, NaN], [3, 2, 1], 'Ordinal', true); %! C2 = categorical ([1, NaN, 3], [3, 2, 1], 'Ordinal', true); %! assert_equal (ismember (C1, C2), logical ([1, 0, 1, 1, 0, 1, 0, 0])); %! assert_equal (ismember (C2, C1), [true, false, true]); %! assert_equal (ismember (C1', C2), logical ([1; 0; 1; 1; 0; 1; 0; 0])); %! assert_equal (ismember (C2', C1), [true; false; true]); %!test %! C1 = categorical ([1, 2, 1, 3, 2, 3, 2, NaN], [1, 2, 3], {'a', 'b', 'c'}, 'Ordinal', true); %! C2 = categorical ([1, NaN, 3], [3, 2, 1], {'a', 'b', 'c'}, 'Ordinal', true); %! assert_equal (ismember (C1, C2), logical ([1, 0, 1, 1, 0, 1, 0, 0])); %! assert_equal (ismember (C2, C1), [true, false, true]); %! assert_equal (ismember (C1', C2), logical ([1; 0; 1; 1; 0; 1; 0; 0])); %! assert_equal (ismember (C2', C1), [true; false; true]); %!assert_equal (ismember ({'1', '2', '2'}, categorical (1:3), 'rows'), false) %!assert_equal (ismember ({'1', '2', '2'; '1', '2', '3'}, ... %! categorical (1:3), 'rows'), [false; true]) %!assert_equal (ismember ({'1', '2', '3'}, categorical (1:3), 'rows'), true) %!assert_equal (ismember ({'1', '2', '3'}, ... %! categorical ([1, 2, 3; 2, 2, 3]), 'rows'), true) %!assert_equal (ismember (string ({'1', '2', '2'}), categorical (1:3), 'rows'), ... %! false) %!assert_equal (ismember (string ({'1', '2', '2'; '1', '2', '3'}), ... %! categorical (1:3), 'rows'), [false; true]) %!assert_equal (ismember (string ({'1', '2', '3'}), ... %! categorical (1:3), 'rows'), true) %!assert_equal (ismember (string ({'1', '2', '3'}), ... %! categorical ([1, 2, 3; 2, 2, 3]), 'rows'), true) %!assert_equal (ismember (categorical (1:3), {'1', '2', '2'}, 'rows'), false) %!assert_equal (ismember (categorical (1:3), {'1', '2', '2'; '1', '2', '3'}, 'rows'), true) %!assert_equal (ismember (categorical (1:3), {'1', '2', '3'}, 'rows'), true) %!assert_equal (ismember (categorical ([1, 2, 3; 2, 2, 3]), ... %! {'1', '2', '3'}, 'rows'), [true; false]) %!error ... %! ismember (1, categorical) %!error ... %! ismember (categorical, {1}) %!error ... %! ismember (categorical, categorical, 1) %!error ... %! ismember (categorical, categorical, 'option') %!error ... %! ismember (categorical (ones (2, 3, 4)), categorical, 'rows') %!error ... %! ismember ({'1', '2'}, categorical (1:3), 'rows') %!error ... %! ismember (categorical (1:3), {'1', '2'}, 'rows') %!error ... %! ismember (categorical (1, 'Ordinal', true), categorical (2, 'Ordinal', true)) %!error ... %! ismember (categorical (1, 'Ordinal', true), categorical (1)) %!assert_equal (ismissing (categorical ([1, 2, NaN, 4]), '2'), logical ([0, 1, 0, 0])) %!assert_equal (ismissing (categorical ([1, 2, NaN, 4]), {'2'}), logical ([0, 1, 0, 0])) %!assert_equal (ismissing (categorical ([1, 2, NaN, 4]), string ('2')), ... %! logical ([0, 1, 0, 0])) %!assert_equal (ismissing (categorical ([1, 2, NaN, 4]), {'2', '4'}), logical ([0, 1, 0, 1])) %!assert_equal (ismissing (categorical ([1, 2, NaN, 4]), string ({'2', '4'})), ... %! logical ([0, 1, 0, 1])) %!assert_equal (ismissing (categorical ([1; 2; NaN; 4]), {'2', '4'}), logical ([0; 1; 0; 1])) %!assert_equal (ismissing (categorical ([1, 2; NaN, 4]), string ({'2'; '4'})), ... %! logical ([0, 1; 0, 1])) %!assert_equal (ismissing (categorical ([1, 2; NaN, 3], [1:3], {'A', 'B', 'C'}), {'A', 'B'}), ... %! logical ([1, 1; 0, 0])) %!assert_equal (ismissing (categorical ([1, 2, NaN, 3], [1:3], {'A', 'B', 'C'}), {'A', 'C'}), ... %! logical ([1, 0, 0, 1])) %!assert_equal (ismissing (categorical ([1, 2, NaN, 3], [1:3], {'A', 'B', 'C'}), {'A', ''}), ... %! logical ([1, 0, 1, 0])) %!assert_equal (ismissing (categorical ([1, 2, NaN, 4]), categorical (NaN)), ... %! logical ([0, 0, 1, 0])) %!assert_equal (ismissing (categorical ([1, 2, NaN, 4]), categorical ([NaN, 5, 2])), ... %! logical ([0, 1, 1, 0])) %!assert_equal (ismissing (categorical ([1, 2, 5, 4]), categorical ([NaN, 5, 2])), ... %! logical ([0, 1, 1, 0])) %!assert_equal (ismissing (categorical ([1, 2; 5, 4]), categorical ([NaN, 5, 2])), ... %! logical ([0, 1; 1, 0])) %!assert_equal (ismissing (categorical ([1, 2, 5, 4]), categorical ([NaN; 5; 2])), ... %! logical ([0, 1, 1, 0])) %!assert_equal (ismissing (categorical ([1, 2; 5, 4]), categorical ([NaN; 5; 2])), ... %! logical ([0, 1; 1, 0])) %!error ... %! ismissing (categorical ([1, 2, 5, 4]), 1, 2) %!error ... %! ismissing (categorical ([1, 2, 5, 4]), ['1';'2']) %!error ... %! ismissing (categorical ([1, 2, 5, 4]), 1) %!error ... %! ismissing (categorical ([1, 2, 5, 4]), {'a', 'f'; 'e', 'r'}) %!assert_equal (isordinal (categorical (1)), false) %!assert_equal (isordinal (categorical (1, 'Ordinal', true)), true) %!assert_equal (isordinal (categorical (1, 'Protected', true)), false) %!assert_equal (isordinal (categorical (1:3, 1:3, {'a', 'b', 'c'})), false) %!assert_equal (isordinal (categorical (1:3, 1:3, {'a', 'b', 'c'}, 'Ordinal', true)), true) %!assert_equal (isordinal (categorical (1:3, 1:3, {'a', 'b', 'c'}, 'Protected', true)), false) %!assert_equal (isprotected (categorical (1)), false) %!assert_equal (isprotected (categorical (1, 'Ordinal', true)), true) %!assert_equal (isprotected (categorical (1, 'Protected', true)), true) %!assert_equal (isprotected (categorical (1:3, 1:3, {'a', 'b', 'c'})), false) %!assert_equal (isprotected (categorical (1:3, 1:3, {'a', 'b', 'c'}, 'Ordinal', true)), true) %!assert_equal (isprotected (categorical (1:3, 1:3, {'a', 'b', 'c'}, 'Protected', true)), true) %!assert_equal (isrow (categorical), false) %!assert_equal (isrow (categorical (NaN)), true) %!assert_equal (isrow (categorical (1)), true) %!assert_equal (isrow (categorical (1:4)), true) %!assert_equal (isrow (categorical (ones (2))), false) %!assert_equal (isrow (categorical (ones (4, 1))), false) %!assert_equal (isrow (categorical (ones (0, 1))), false) %!assert_equal (isrow (categorical (ones (1, 0))), true) %!assert_equal (isscalar (categorical), false) %!assert_equal (isscalar (categorical (NaN)), true) %!assert_equal (isscalar (categorical (1)), true) %!assert_equal (isscalar (categorical (1:4)), false) %!assert_equal (isscalar (categorical (ones (2))), false) %!assert_equal (isscalar (categorical (ones (4, 1))), false) %!assert_equal (isscalar (categorical (ones (0, 1))), false) %!assert_equal (isscalar (categorical (ones (1, 0))), false) %!test %! C = categorical ([1, 2; 1, 3; 2, -4; 2, 1]); %! assert_equal (issorted (sort (C)), true); %! assert_equal (issorted (sort (C), 'ascend'), true); %! assert_equal (issorted (sort (C), 'descend'), false); %! assert_equal (issorted (sort (C), 'monotonic'), true); %! assert_equal (issorted (sort (C), 'strictascend'), false); %! assert_equal (issorted (sort (C), 'strictdescend'), false); %! assert_equal (issorted (sort (C), 'strictmonotonic'), false); %!test %! C = categorical ([1, 2; 1, 3; 2, -4; 2, 1]); %! assert_equal (issorted (sort (C, 2), 2), true); %! assert_equal (issorted (sort (C, 2), 2, 'ascend'), true); %! assert_equal (issorted (sort (C, 2), 2, 'descend'), false); %! assert_equal (issorted (sort (C, 2), 2, 'monotonic'), true); %! assert_equal (issorted (sort (C, 2), 2, 'strictascend'), true); %! assert_equal (issorted (sort (C, 2), 2, 'strictdescend'), false); %! assert_equal (issorted (sort (C, 2), 2, 'strictmonotonic'), true); %!test %! C = categorical ([1, 2; 1, 3; 2, -4; 2, 1]); %! assert_equal (issorted (sort (C, 2), 'ascend'), false); %! assert_equal (issorted (sort (C, 2), 'descend'), false); %! assert_equal (issorted (sort (C, 2), 'monotonic'), false); %! assert_equal (issorted (sort (C, 2), 'strictascend'), false); %! assert_equal (issorted (sort (C, 2), 'strictdescend'), false); %! assert_equal (issorted (sort (C, 2), 'strictmonotonic'), false); %!test %! C = categorical ([1, 2; 1, 3; 2, -4; 2, 1]); %! assert_equal (issorted (sort (C, 'descend'), 'ascend'), false); %! assert_equal (issorted (sort (C, 'descend'), 'descend'), true); %! assert_equal (issorted (sort (C, 'descend'), 'monotonic'), true); %! assert_equal (issorted (sort (C, 'descend'), 'strictascend'), false); %! assert_equal (issorted (sort (C, 'descend'), 'strictdescend'), false); %! assert_equal (issorted (sort (C, 'descend'), 'strictmonotonic'), false); %!test %! C = categorical ([1, 2; 1, 3; 2, -4; 2, 1]); %! assert_equal (issorted (sort (C, 2, 'descend'), 2, 'ascend'), false); %! assert_equal (issorted (sort (C, 2, 'descend'), 2, 'descend'), true); %! assert_equal (issorted (sort (C, 2, 'descend'), 2, 'monotonic'), true); %! assert_equal (issorted (sort (C, 2, 'descend'), 2, 'strictascend'), false); %! assert_equal (issorted (sort (C, 2, 'descend'), 2, 'strictdescend'), true); %! assert_equal (issorted (sort (C, 2, 'descend'), 2, 'strictmonotonic'), true); %!test %! C = categorical ([1, 2; 1, 3; 2, -4; 2, 1]); %! assert_equal (issorted (sort (C, 2, 'descend'), 'ascend'), false); %! assert_equal (issorted (sort (C, 2, 'descend'), 'descend'), false); %! assert_equal (issorted (sort (C, 2, 'descend'), 'monotonic'), false); %! assert_equal (issorted (sort (C, 2, 'descend'), 'strictascend'), false); %! assert_equal (issorted (sort (C, 2, 'descend'), 'strictdescend'), false); %! assert_equal (issorted (sort (C, 2, 'descend'), 'strictmonotonic'), false); %!test %! C = categorical ([1, 2; 2, 3; 3, 4; 4, 5]); %! assert_equal (issorted (sort (C), 'ascend'), true); %! assert_equal (issorted (sort (C), 'descend'), false); %! assert_equal (issorted (sort (C), 'monotonic'), true); %! assert_equal (issorted (sort (C), 'strictascend'), true); %! assert_equal (issorted (sort (C), 'strictdescend'), false); %! assert_equal (issorted (sort (C), 'strictmonotonic'), true); %!test %! C = categorical ([1, 2; 2, 3; 3, 4; 4, 5]); %! assert_equal (issorted (sort (C, 'descend'), 'ascend'), false); %! assert_equal (issorted (sort (C, 'descend'), 'descend'), true); %! assert_equal (issorted (sort (C, 'descend'), 'monotonic'), true); %! assert_equal (issorted (sort (C, 'descend'), 'strictascend'), false); %! assert_equal (issorted (sort (C, 'descend'), 'strictdescend'), true); %! assert_equal (issorted (sort (C, 'descend'), 'strictmonotonic'), true); %!test %! C = categorical ([1, 2; 2, 3; 3, 4; 4, NaN]); %! assert_equal (issorted (sort (C)), true); %! assert_equal (issorted (sort (C), 'ascend'), true); %! assert_equal (issorted (sort (C), 'descend'), false); %! assert_equal (issorted (sort (C), 'monotonic'), true); %! assert_equal (issorted (sort (C), 'strictascend'), false); %! assert_equal (issorted (sort (C), 'strictdescend'), false); %! assert_equal (issorted (sort (C), 'strictmonotonic'), false); %!test %! C = categorical ([1, 2; 2, 3; 3, 4; 4, NaN]); %! assert_equal (issorted (sort (C, 'descend'), 'ascend'), false); %! assert_equal (issorted (sort (C, 'descend'), 'descend'), true); %! assert_equal (issorted (sort (C, 'descend'), 'monotonic'), true); %! assert_equal (issorted (sort (C, 'descend'), 'strictascend'), false); %! assert_equal (issorted (sort (C, 'descend'), 'strictdescend'), false); %! assert_equal (issorted (sort (C, 'descend'), 'strictmonotonic'), false); %!test %! C = categorical ([1, 2; 2, 3; 3, 4; 4, NaN]); %! assert_equal (issorted (sort (C, 'MissingPlacement', 'auto')), true); %! assert_equal (issorted (sort (C, 'MissingPlacement', 'first')), false); %! assert_equal (issorted (sort (C, 'MissingPlacement', 'first'), ... %! 'MissingPlacement', 'first'), true); %!test %! C = categorical ([1, 2; 2, 3; 3, 4; 4, NaN]); %! assert_equal (issorted (sort (C, 'descend', 'MissingPlacement', 'auto'), 'descend'), true); %! assert_equal (issorted (sort (C, 'descend', 'MissingPlacement', 'last'), 'descend'), false); %! assert_equal (issorted (sort (C, 'descend', 'MissingPlacement', 'first'), ... %! 'descend', 'MissingPlacement', 'first'), true); %!test %! C = categorical ([1, 2; 2, 3; 3, 4; 4, NaN]); %! assert_equal (issorted (sort (C, 2, 'MissingPlacement', 'auto'), 2), true); %! assert_equal (issorted (sort (C, 2, 'MissingPlacement', 'first'), 2), false); %! assert_equal (issorted (sort (C, 2, 'MissingPlacement', 'first'), 2, ... %! 'MissingPlacement', 'first'), true); %!test %! C = categorical ([1, 2; 2, 3; 3, 4; 4, NaN]); %! assert_equal (issorted (sort (C, 2, 'descend', 'MissingPlacement', 'auto'), 2, 'descend'), true); %! assert_equal (issorted (sort (C, 2, 'descend', 'MissingPlacement', 'last'), 2, 'descend'), false); %! assert_equal (issorted (sort (C, 2, 'descend', 'MissingPlacement', 'first'), 2, ... %! 'descend', 'MissingPlacement', 'first'), true); %!test %! C = categorical ([1, 2; 2, 3; 3, 4; 4, NaN]); %! assert_equal (issorted (sort (C, 2, string ('ascend'), 'MissingPlacement', 'auto'), 2), true); %! assert_equal (issorted (sort (C, 2, string ('ascend'), 'MissingPlacement', 'first'), 2), false); %! assert_equal (issorted (sort (C, 2, string ('ascend'), 'MissingPlacement', 'first'), 2, ... %! 'MissingPlacement', 'first'), true); %!test %! C = categorical ([1, 2; 2, 3; 3, 4; 4, NaN]); %! assert_equal (issorted (sort (C, 2, string ('descend'), 'MissingPlacement', 'auto'), 2, 'descend'), true); %! assert_equal (issorted (sort (C, 2, string ('descend'), 'MissingPlacement', 'last'), 2, 'descend'), false); %! assert_equal (issorted (sort (C, 2, string ('descend'), 'MissingPlacement', 'first'), 2, ... %! 'descend', 'MissingPlacement', 'first'), true); %!test ## strictmonotonic honours an explicit MissingPlacement argument %! assert_equal (issorted (categorical ([3, 2, 1]), 'strictmonotonic', ... %! 'MissingPlacement', 'last'), true); %! assert_equal (issorted (categorical ([3, 2, 1]), 'strictmonotonic', ... %! 'MissingPlacement', 'first'), true); %! assert_equal (issorted (categorical ([1, 1, 2]), 'strictmonotonic', ... %! 'MissingPlacement', 'last'), false); %!error ... %! issorted (sort (categorical), 'MissingPlacement', 'some') %!error ... %! issorted (sort (categorical), 'asd') %!test %! C = categorical ([1, 2; 1, 3; 2, -4; 2, 1]); %! assert_equal (issortedrows (sortrows (C)), true); %! assert_equal (issortedrows (sortrows (C, 1)), true); %! assert_equal (issortedrows (sortrows (C, 1), -1), false); %! assert_equal (issortedrows (sortrows (C, 1), 1), true); %! assert_equal (issortedrows (sortrows (C, 2)), false); %! assert_equal (issortedrows (sortrows (C, 2), 2), true); %! assert_equal (issortedrows (sortrows (C, 2), -2), false); %! assert_equal (issortedrows (sortrows (C, -2), 2), false); %! assert_equal (issortedrows (sortrows (C, -2), -2), true); %!test %! C = categorical ([1, 2, 2; 1, 3, 1; 2, -4, 2; 2, 1, NaN; -2, NaN, 3; 1, 3, 2]); %! assert_equal (issortedrows (sortrows (C)), true); %!test %! C = categorical ([1, 2, 2; 1, 3, 1; 2, -4, 2; 2, 1, NaN; -2, NaN, 3; 1, 3, 2]); %! assert_equal (issortedrows (sortrows (C, -2, 'MissingPlacement', 'first')), false); %! assert_equal (issortedrows (sortrows (C, -2, 'MissingPlacement', 'first'), -2), true); %! assert_equal (issortedrows (sortrows (C, -2, 'MissingPlacement', 'last'), -2), false); %! assert_equal (issortedrows (sortrows (C, -2, 'MissingPlacement', 'last'), -2, ... %! 'MissingPlacement', 'last'), true); %!test %! C = categorical ([1, 2, 2; 1, 3, 1; 2, -4, 2; 2, 1, NaN; -2, NaN, 3; 1, 3, 2]); %! assert_equal (issortedrows (sortrows (C, 2, 'MissingPlacement', 'last')), false); %! assert_equal (issortedrows (sortrows (C, 2, 'MissingPlacement', 'last'), 2), true); %! assert_equal (issortedrows (sortrows (C, 2, 'MissingPlacement', 'first'), 2), false); %! assert_equal (issortedrows (sortrows (C, 2, 'MissingPlacement', 'first'), 2, ... %! 'MissingPlacement', 'first'), true); %!test %! C = categorical ([1, 2, 2; 1, 3, 1; 2, -4, 2; 2, 1, NaN; -2, NaN, 3; 1, 3, 2]); %! assert_equal (issortedrows (sortrows (C, [1, -2, 3]), [1, -2, 3]), true); %! assert_equal (issortedrows (sortrows (C, [1, 2, -3]), [1, -2, 3]), false); %!test %! C = categorical ([1, 2, 2; 1, 3, 1; 2, -4, 2; 2, 1, NaN; -2, NaN, 3; 1, 3, 2]); %! assert_equal (issortedrows (sortrows (C, string ({'ascend', 'descend', 'ascend'})), ... %! string ({'ascend', 'descend', 'ascend'})), true); %! C = categorical ([1, 2, 2; 1, 3, 1; 2, -4, 2; 2, 1, NaN; -2, NaN, 3; 1, 3, 2]); %! assert_equal (issortedrows (sortrows (C, string ({'ascend', 'descend', 'ascend'})), ... %! string ({'descend', 'ascend', 'ascend'})), false); %!test %! C = categorical ([1, 2, 2; 1, 3, 1; 2, -4, 2; 2, 1, NaN; -2, NaN, 3; 1, 3, 2]); %! assert_equal (issortedrows (sortrows (C, [2, 3], string ({'descend', 'ascend'})), ... %! [2, 3], string ({'descend', 'ascend'})), true); %! C = categorical ([1, 2, 2; 1, 3, 1; 2, -4, 2; 2, 1, NaN; -2, NaN, 3; 1, 3, 2]); %! assert_equal (issortedrows (sortrows (C, [2, 3], string ({'ascend', 'descend'})), ... %! [2, -3]), true); %! assert_equal (issortedrows (sortrows (C, [2, 3], string ({'descend', 'descend'})), ... %! [2, -3], string ({'descend', 'ascend'})), false); %!test %! C = categorical ([1, 2, 2; 1, 3, 1; 2, -4, 2; 2, 1, NaN; -2, NaN, 3; 1, 3, 2]); %! assert_equal (issortedrows (sortrows (C, [1, -2, 3], 'MissingPlacement', 'first'), ... %! [1, -2, 3], 'MissingPlacement', 'first'), true); %! assert_equal (issortedrows (sortrows (C, [-1, -2, -3], 'MissingPlacement', 'first'), ... %! [-1, -2, -3]), true); %! assert_equal (issortedrows (sortrows (C, [-1, -2, -3], 'MissingPlacement', 'first'), ... %! [1, -2,-3], string ({'descend', 'descend', 'descend'})), true); %!test ## strict issortedrows semantics, verified against MATLAB R2026a %! assert_equal (issortedrows (categorical ([1, 3; 1, 4]), 'strictascend'), false); %! assert_equal (issortedrows (categorical ([1, 3; 1, 3]), 'strictascend'), false); %! assert_equal (issortedrows (categorical ([1, 3; 1, 4]), ... %! {'strictascend', 'ascend'}), false); %! assert_equal (issortedrows (categorical ([1, 3; 2, 3]), ... %! {'ascend', 'strictascend'}), true); %! assert_equal (issortedrows (categorical ([2, 4; 1, 3]), 'monotonic'), true); %! assert_equal (issortedrows (categorical ([2, 4; 1, 3]), 'descend'), true); %! assert_equal (issortedrows (categorical ([1, 4; 2, 3]), 2, 'strictascend'), false); %! assert_equal (issortedrows (categorical ([2, 3; 1, 4]), 2, 'strictascend'), true); %!test ## strict issortedrows with missing values (vs MATLAB R2026a) %! assert_equal (issortedrows (categorical ([1, 3; NaN, 4]), ... %! {'strictascend', 'ascend'}), false); %! assert_equal (issortedrows (categorical ([1, 3; NaN, 4]), ... %! {'ascend', 'strictascend'}), true); %! assert_equal (issortedrows (categorical ([NaN, 3; 2, 4]), 2, 'strictascend'), true); %! assert_equal (issortedrows (categorical ([1, NaN; 2, 4]), 'strictascend'), true); %!assert_equal (isundefined (categorical ([1:10])), false (1, 10)) %!assert_equal (isundefined (categorical ([1, 2, NaN, 4, NaN])), logical ([0, 0, 1, 0, 1])) %!assert_equal (isundefined (categorical (NaN (3, 4, 5))), true (3, 4, 5)) %!assert_equal (isundefined (categorical (zeros (3, 4, 5))), false (3, 4, 5)) %!assert_equal (isvector (categorical ([1, 2, 3, 4])), true) %!assert_equal (isvector (categorical ([1; 2; 3; 4])), true) %!assert_equal (isvector (categorical ([1, 2; 3, 4])), false) %!assert_equal (isvector (categorical ([])), false) %!assert_equal (isvector (categorical (ones (1, 0))), true) %!assert_equal (isvector (categorical (ones (0, 1))), true) %!assert_equal (isvector (categorical (1)), true) %!assert_equal (isvector (categorical (ones (2, 3, 4))), false) ################################################################################ ## ** Category Operations ** ## ################################################################################ ## Available Methods ## ## ## ## 'addcats' 'mergecats' 'removecats' 'renamecats' ## ## 'reordercats' 'setcats' 'times' ## ## ## ################################################################################ %!test %! C = categorical ({'red', 'blue', 'red', 'blue', 'red', 'blue'}); %! B = addcats (C, {'green', 'black'}); %! assert_equal (C, B); %! assert_equal (categories (B), {'blue'; 'red'; 'green'; 'black'}); %! assert_equal (categories (C), {'blue'; 'red'}); %!test %! C = categorical ({'red', 'blue', 'red', 'blue', 'red', 'blue'}); %! B = addcats (C, string ({'green', 'black'})); %! assert_equal (C, B); %! assert_equal (categories (B), {'blue'; 'red'; 'green'; 'black'}); %! assert_equal (categories (C), {'blue'; 'red'}); %!test %! S = {'medium', 'large', 'small', 'xlarge', 'large', 'medium'}; %! C = categorical (S, {'small', 'medium', 'large', 'xlarge'}, 'Ordinal', true); %! B = addcats (C, 'xsmall', 'Before', 'small'); %! cats = categories (B); %! assert_equal (cats, {'xsmall'; 'small'; 'medium'; 'large'; 'xlarge'}); %! assert_equal (double (B), [3, 4, 2, 5, 4, 3]); %! assert_equal (cats (double (B)), S(:)); %!test %! S = {'medium', 'large', 'small', 'xlarge', 'large', 'medium'}; %! C = categorical (S, {'small', 'medium', 'large', 'xlarge'}, 'Ordinal', true); %! B = addcats (C, 'xsmall', 'Before', 'medium'); %! cats = categories (B); %! assert_equal (cats, {'small'; 'xsmall'; 'medium'; 'large'; 'xlarge'}); %! assert_equal (double (B), [3, 4, 1, 5, 4, 3]); %! assert_equal (cats (double (B)), S(:)); %!test %! S = {'medium', 'large', 'small', 'xlarge', 'large', 'medium'}; %! C = categorical (S, {'small', 'medium', 'large', 'xlarge'}, 'Ordinal', true); %! B = addcats (C, 'xsmall', 'Before', 'large'); %! cats = categories (B); %! assert_equal (cats, {'small'; 'medium'; 'xsmall'; 'large'; 'xlarge'}); %! assert_equal (double (B), [2, 4, 1, 5, 4, 2]); %! assert_equal (cats (double (B)), S(:)); %!test %! S = {'medium', 'large', 'small', 'xlarge', 'large', 'medium'}; %! C = categorical (S, {'small', 'medium', 'large', 'xlarge'}, 'Ordinal', true); %! B = addcats (C, 'xsmall', 'Before', 'xlarge'); %! cats = categories (B); %! assert_equal (cats, {'small'; 'medium'; 'large'; 'xsmall'; 'xlarge'}); %! assert_equal (double (B), [2, 3, 1, 5, 3, 2]); %! assert_equal (cats (double (B)), S(:)); %!test %! S = {'medium', 'large', 'small', 'xlarge', 'large', 'medium'}; %! C = categorical (S, {'small', 'medium', 'large', 'xlarge'}, 'Ordinal', true); %! B = addcats (C, {'xxsmall', 'xsmall'}, 'Before', 'small'); %! cats = categories (B); %! assert_equal (cats, {'xxsmall'; 'xsmall'; 'small'; 'medium'; 'large'; 'xlarge'}); %! assert_equal (double (B), [4, 5, 3, 6, 5, 4]); %! assert_equal (cats (double (B)), S(:)); %!test %! S = {'medium', 'large', 'small', 'xlarge', 'large', 'medium'}; %! C = categorical (S, {'small', 'medium', 'large', 'xlarge'}, 'Ordinal', true); %! B = addcats (C, {'xxsmall', 'xsmall'}, 'Before', 'xlarge'); %! cats = categories (B); %! assert_equal (cats, {'small'; 'medium'; 'large'; 'xxsmall'; 'xsmall'; 'xlarge'}); %! assert_equal (double (B), [2, 3, 1, 6, 3, 2]); %! assert_equal (cats (double (B)), S(:)); %!test %! S = {'medium', 'large', 'small', 'xsmall', 'large', 'medium'}; %! C = categorical (S, {'xsmall', 'small', 'medium', 'large'}, 'Ordinal', true); %! B = addcats (C, 'xlarge', 'After', 'large'); %! cats = categories (B); %! assert_equal (cats, {'xsmall'; 'small'; 'medium'; 'large'; 'xlarge'}); %! assert_equal (double (B), [3, 4, 2, 1, 4, 3]); %! assert_equal (cats (double (B)), S(:)); %!test %! S = {'medium', 'large', 'small', 'xsmall', 'large', 'medium'}; %! C = categorical (S, {'xsmall', 'small', 'medium', 'large'}, 'Ordinal', true); %! B = addcats (C, 'xlarge', 'After', 'medium'); %! cats = categories (B); %! assert_equal (cats, {'xsmall'; 'small'; 'medium'; 'xlarge'; 'large'}); %! assert_equal (double (B), [3, 5, 2, 1, 5, 3]); %! assert_equal (cats (double (B)), S(:)); %!test %! S = {'medium', 'large', 'small', 'xsmall', 'large', 'medium'}; %! C = categorical (S, {'xsmall', 'small', 'medium', 'large'}, 'Ordinal', true); %! B = addcats (C, 'xlarge', 'After', 'small'); %! cats = categories (B); %! assert_equal (cats, {'xsmall'; 'small'; 'xlarge'; 'medium'; 'large'}); %! assert_equal (double (B), [4, 5, 2, 1, 5, 4]); %! assert_equal (cats (double (B)), S(:)); %!test %! S = {'medium', 'large', 'small', 'xsmall', 'large', 'medium'}; %! C = categorical (S, {'xsmall', 'small', 'medium', 'large'}, 'Ordinal', true); %! B = addcats (C, 'xlarge', 'After', 'xsmall'); %! cats = categories (B); %! assert_equal (cats, {'xsmall'; 'xlarge'; 'small'; 'medium'; 'large'}); %! assert_equal (double (B), [4, 5, 3, 1, 5, 4]); %! assert_equal (cats (double (B)), S(:)); %!test %! S = {'medium', 'large', 'small', 'xsmall', 'large', 'medium'}; %! C = categorical (S, {'xsmall', 'small', 'medium', 'large'}, 'Ordinal', true); %! B = addcats (C, {'xlarge', 'xxlarge'}, 'After', 'large'); %! cats = categories (B); %! assert_equal (cats, {'xsmall'; 'small'; 'medium'; 'large'; 'xlarge'; 'xxlarge'}); %! assert_equal (double (B), [3, 4, 2, 1, 4, 3]); %! assert_equal (cats (double (B)), S(:)); %!test %! S = {'medium', 'large', 'small', 'xsmall', 'large', 'medium'}; %! C = categorical (S, {'xsmall', 'small', 'medium', 'large'}, 'Ordinal', true); %! B = addcats (C, {'xlarge', 'xxlarge'}, 'After', 'xsmall'); %! cats = categories (B); %! assert_equal (cats, {'xsmall'; 'xlarge'; 'xxlarge'; 'small'; 'medium'; 'large'}); %! assert_equal (double (B), [5, 6, 4, 1, 6, 5]); %! assert_equal (cats (double (B)), S(:)); %!error addcats (categorical) %!error addcats (categorical, '') %!error addcats (categorical, []) %!error addcats (categorical, {}) %!error ... %! addcats (categorical, 1) %!error ... %! addcats (categorical, false) %!error ... %! addcats (categorical, struct ('a', 2)) %!error ... %! addcats (categorical, {'a', 'a'}) %!error ... %! addcats (categorical ({'a', 'b', 'a'}), {'a', 'c'}) %!error ... %! addcats (categorical ({'a', 'b', 'a'}), 'c', 'After', 'a', 'Before', 'b') %!error ... %! addcats (categorical ({'a', 'b', 'a'}), 'c', 'After', 'd') %!error ... %! addcats (categorical ({'a', 'b', 'a'}), 'c', 'Before', 'c') %!error ... %! mergecats (categorical, {'a'}, 1) %!error ... %! mergecats (categorical, {'a'}, []) %!error ... %! mergecats (categorical, {'a'}, {'w', 'e'}) %!error ... %! mergecats (categorical, {'a'}, ['w'; 'e']) %!error ... %! mergecats (categorical, {'a'}, string ({'w', 'e'})) %!error ... %! mergecats (categorical, {'a'}, missing) %!error ... %! mergecats (categorical, {'a'}, '') %!error ... %! mergecats (categorical, {'a'}, {''}) %!error ... %! mergecats (categorical, {'a'}, string ({''})) %!error ... %! mergecats (categorical, {'a'}, string (missing)) %!test %! A = categorical ({'pink', 'blue', 'pink', 'red', 'blue', 'red'}); %! B = mergecats (A, {'red', 'pink'}); %! assert_equal (B, categorical ({'red', 'blue', 'red', 'red', 'blue', 'red'})); %! assert_equal (categories (B), {'blue'; 'red'}); %! assert_equal (double (B), [2, 1, 2, 2, 1, 2]); %!test %! A = categorical ({'pink', 'blue', 'pink', 'red', 'blue', 'red'}); %! B = mergecats (A, {'pink', 'red'}); %! assert_equal (B, categorical ({'pink', 'blue', 'pink', 'pink', 'blue', 'pink'})); %! assert_equal (categories (B), {'blue'; 'pink'}); %! assert_equal (double (B), [2, 1, 2, 2, 1, 2]); %!test %! A = categorical ({'pink', 'blue', 'pink', 'red', 'blue', 'red'}); %! B = mergecats (A, {'pink', 'blue'}); %! assert_equal (B, categorical ({'pink', 'pink', 'pink', 'red', 'pink', 'red'})); %! assert_equal (categories (B), {'pink'; 'red'}); %! assert_equal (double (B), [1, 1, 1, 2, 1, 2]); %!test %! A = categorical ({'pink', 'blue', 'pink', 'red', 'blue', 'red'}); %! B = mergecats (A, {'red', 'pink'}, 'gold'); %! assert_equal (B, categorical ({'gold', 'blue', 'gold', 'gold', 'blue', 'gold'})); %! assert_equal (categories (B), {'blue'; 'gold'}); %! assert_equal (double (B), [2, 1, 2, 2, 1, 2]); %!test %! A = categorical ({'pink', 'blue', 'pink', 'red', 'blue', 'red'}); %! B = mergecats (A, string ({'pink', 'red'}), 'gold'); %! assert_equal (B, categorical ({'gold', 'blue', 'gold', 'gold', 'blue', 'gold'})); %! assert_equal (categories (B), {'blue'; 'gold'}); %! assert_equal (double (B), [2, 1, 2, 2, 1, 2]); %!test %! A = categorical ({'pink', 'blue', 'pink', 'red', 'blue', 'red'}); %! B = mergecats (A, {'pink', 'blue'}, string ('gold')); %! assert_equal (B, categorical ({'gold', 'gold', 'gold', 'red', 'gold', 'red'})); %! assert_equal (categories (B), {'gold'; 'red'}); %! assert_equal (double (B), [1, 1, 1, 2, 1, 2]); %!test %! A = categorical ({'shirt', 'pants', 'shoes', 'shirt', 'dress', 'belt'}); %! assert_equal (categories (A), {'belt'; 'dress'; 'pants'; 'shirt'; 'shoes'}); %! B = mergecats (A, {'belt', 'shoes'}, 'hat'); %! assert_equal (categories (B), {'hat'; 'dress'; 'pants'; 'shirt'}); %! assert_equal (double (B), [4, 3, 1, 4, 2, 1]); %!test %! A = categorical ({'shirt', 'pants', 'shoes', 'shirt', 'dress', 'belt'}); %! assert_equal (categories (A), {'belt'; 'dress'; 'pants'; 'shirt'; 'shoes'}); %! B = mergecats (A, {'dress', 'shoes'}, 'hat'); %! assert_equal (categories (B), {'belt'; 'hat'; 'pants'; 'shirt'}); %! assert_equal (double (B), [4, 3, 2, 4, 2, 1]); %!test %! A = categorical ({'shirt', 'pants', 'shoes', 'shirt', 'dress', 'belt'}); %! assert_equal (categories (A), {'belt'; 'dress'; 'pants'; 'shirt'; 'shoes'}); %! B = mergecats (A, {'dress', 'shirt'}, 'hat'); %! assert_equal (categories (B), {'belt'; 'hat'; 'pants'; 'shoes'}); %! assert_equal (double (B), [2, 3, 4, 2, 2, 1]); %!test %! A = categorical ({'shirt', 'pants', 'shoes', 'shirt', 'dress', 'belt'}); %! assert_equal (categories (A), {'belt'; 'dress'; 'pants'; 'shirt'; 'shoes'}); %! B = mergecats (A, {'belt', 'shirt', 'shoes'}, 'hat'); %! assert_equal (categories (B), {'hat'; 'dress'; 'pants'}); %! assert_equal (double (B), [1, 3, 1, 1, 2, 1]); %!test %! A = categorical ({'shirt', 'pants', 'shoes', 'shirt', 'dress', 'belt'}); %! B = mergecats (A, {'dresss', 'shoes'}, 'hat'); %! assert_equal (categories (B), {'belt'; 'dress'; 'pants'; 'shirt'; 'hat'}); %! assert_equal (double (B), [4, 3, 5, 4, 2, 1]); %!test %! A = categorical ({'shirt', 'pants', 'shoes', 'shirt', 'dress', 'belt'}); %! B = mergecats (A, {'dresss', 'shirt'}, 'hat'); %! assert_equal (categories (B), {'belt'; 'dress'; 'pants'; 'hat'; 'shoes'}); %! assert_equal (double (B), [4, 3, 5, 4, 2, 1]); %!test %! A = categorical ({'shirt', 'pants', 'shoes', 'shirt', 'dress', 'belt', ''}); %! B = mergecats (A, {'belt', 'shoes'}, 'other'); %! cats = categories (B); %! assert_equal (cats, {'other'; 'dress'; 'pants'; 'shirt'}); %! codes = double (B); %! assert_equal (codes, [4, 3, 1, 4, 2, 1, NaN]); %! S = {'shirt'; 'pants'; 'other'; 'shirt'; 'dress'; 'other'}; %! assert_equal (cats(codes(! isnan (codes))), S); %!test %! A = categorical ([1, 2, 3, 2, 1], 1:3, {'poor', 'fair', 'good'}, 'Ordinal', true); %! B = mergecats (A, {'fair', 'poor'}, "bad"); %! assert_equal (categories (B), {'bad'; 'good'}); %! assert_equal (double (B), [1, 1, 2, 1, 1]); %!error mergecats (categorical) %!error mergecats (categorical, '') %!error mergecats (categorical, []) %!error mergecats (categorical, {}) %!error ... %! mergecats (categorical, 1) %!error ... %! mergecats (categorical, false) %!error ... %! mergecats (categorical ({'a'}), 'a') %!error ... %! mergecats (categorical, struct ('a', 2)) %!test %! A = categorical ({'red', 'blue', 'blue', 'blue', 'red', 'blue'}, ... %! {'black', 'blue', 'red', 'green'}); %! assert_equal (categories (A), {'black'; 'blue'; 'red'; 'green'}); %! B = removecats (A); %! assert_equal (categories (B), {'blue'; 'red'}); %! assert_equal (A, B); %!test %! A = categorical ({'red', 'blue', 'blue', 'blue', 'red', 'blue'}, ... %! {'black', 'blue', 'red', 'green'}); %! B = removecats (A, cell (0, 1)); %! assert_equal (categories (B), {'black'; 'blue'; 'red'; 'green'}); %! assert_equal (A, B); %!test %! A = categorical ({'red', 'blue', 'blue', 'blue', 'red', 'blue'}, ... %! {'black', 'blue', 'red', 'green'}); %! B = removecats (A, {}); %! assert_equal (categories (B), {'black'; 'blue'; 'red'; 'green'}); %! assert_equal (A, B); %!test %! A = categorical ({'plane', 'car', 'train', 'car', 'plane', 'car'}); %! B = removecats (A, 'car'); %! assert_equal (categories (B), {'plane'; 'train'}); %! assert_equal (double (B), [1, NaN, 2, NaN, 1, NaN]); %!test %! A = categorical ({'plane', 'car', 'train', 'car', 'plane', 'car'}); %! B = removecats (A, string ('plane')); %! assert_equal (categories (B), {'car'; 'train'}); %! assert_equal (double (B), [NaN, 1, 2, 1, NaN, 1]); %!test %! A = categorical ({'plane', 'car', 'train', 'car', 'plane', 'car'}); %! B = removecats (A, {'car', 'train'}); %! assert_equal (categories (B), {'plane'}); %! assert_equal (double (B), [1, NaN, NaN, NaN, 1, NaN]); %!error ... %! removecats (categorical, []) %!error ... %! removecats (categorical, '') %!error ... %! removecats (categorical, 1) %!error ... %! removecats (categorical, true) %!error ... %! removecats (categorical, struct ('a', 1)) %!test %! A = categorical ({'purple', 'blue', 'purple', 'red', 'red', 'blue'}); %! B = renamecats (A, {'b', 'p', 'r'}); %! cats = categories (B); %! assert_equal (cats, {'b'; 'p'; 'r'}); %! assert_equal (cats(double (B)), {'p'; 'b'; 'p'; 'r'; 'r'; 'b'}); %!test %! A = categorical ({'purple', 'blue', 'purple', 'red', 'red', 'blue'}); %! B = renamecats (A, string ({'b', 'p', 'r'})); %! cats = categories (B); %! assert_equal (cats, {'b'; 'p'; 'r'}); %! assert_equal (cats(double (B)), {'p'; 'b'; 'p'; 'r'; 'r'; 'b'}); %!test %! A = categorical ({'purple', 'blue', 'purple', 'red', 'red', 'blue'}); %! B = renamecats (A, 'purple', 'gold'); %! cats = categories (B); %! assert_equal (cats, {'blue'; 'gold'; 'red'}); %! assert_equal (cats(double (B)), {'gold'; 'blue'; 'gold'; 'red'; 'red'; 'blue'}); %!test %! A = categorical (); %! B = renamecats (A, {}); %! assert_equal (A, B); %!test %! A = categorical ({'purple', 'blue', 'purple', 'red', 'red', 'blue'}); %! B = renamecats (A, {}, {}); %! assert_equal (A, B); %!error ... %! renamecats (categorical) %!error ... %! renamecats (categorical, '') %!error ... %! renamecats (categorical, 1) %!error ... %! renamecats (categorical, true) %!error ... %! renamecats (categorical, struct ('a', 'b')) %!error ... %! renamecats (categorical, string (missing)) %!error ... %! renamecats (categorical ({'a'}), '', 'a') %!error ... %! renamecats (categorical ({'a'}), 1, 'a') %!error ... %! renamecats (categorical ({'a'}), true, 'a') %!error ... %! renamecats (categorical ({'a'}), struct ('a', 'a'), 'a') %!error ... %! renamecats (categorical ({'a'}), 'a', '') %!error ... %! renamecats (categorical ({'a'}), 'a', 1) %!error ... %! renamecats (categorical ({'a'}), 'a', true) %!error ... %! renamecats (categorical ({'a'}), 'a', struct ('a', 'a')) %!error ... %! renamecats (categorical ({'a'}), 'a', {}) %!error ... %! renamecats (categorical ({'a'}), {}, 'a') %!error ... %! renamecats (categorical ({'a'}), 'b', 'a') %!test %! X = categorical ({'Frog', 'Cat', 'Cat', 'Ant', 'Frog'}); %! Xcats = categories (X); %! assert_equal (Xcats, {'Ant'; 'Cat'; 'Frog'}); %! Y = categorical ({'Deer', 'Bear', 'Eagle', 'Deer'}); %! Ycats = categories (Y); %! assert_equal (Ycats, {'Bear'; 'Deer'; 'Eagle'}); %! A = [X, Y]; %! Acats = categories (A); %! S = {'Frog'; 'Cat'; 'Cat'; 'Ant'; 'Frog'; 'Deer'; 'Bear'; 'Eagle'; 'Deer'}; %! assert_equal (Acats, {'Ant'; 'Cat'; 'Frog'; 'Bear'; 'Deer'; 'Eagle'}); %! assert_equal (Acats(double (A)), S); %! B = reordercats (A); %! Bcats = categories (B); %! assert_equal (Bcats, {'Ant'; 'Bear'; 'Cat'; 'Deer'; 'Eagle'; 'Frog'}); %! assert_equal (Bcats(double (B)), S); %!test %! A = categorical ({'red', 'green', 'blue', 'red', 'green', 'red', 'blue', 'blue'}); %! assert_equal (categories (A), {'blue'; 'green'; 'red'}); %! B = reordercats (A, string ({'red', 'green', 'blue'})); %! assert_equal (categories (B), {'red'; 'green'; 'blue'}); %!test %! A = categorical ({'plane', 'car', 'train', 'car', 'plane', 'car'}, ... %! {'car', 'train', 'plane'}, 'Ordinal', true); %! assert_equal (categories (A), {'car'; 'train'; 'plane'}); %! A_out = categorical ({'plane', 'train', 'plane'}, ... %! {'car', 'train', 'plane'}, 'Ordinal', true); %! assert_equal (A(A > "car"), A_out); %! B = reordercats (A, {'train', 'car', 'plane'}); %! assert_equal (categories (B), {'train'; 'car'; 'plane'}); %! B_out = categorical ({'plane', 'plane'}, ... %! {'train', 'car', 'plane'}, 'Ordinal', true); %! assert_equal (B(B > "car"), B_out); %!test %! A = categorical ({'plane', 'car', 'train', 'car', 'car', 'plane', 'car'}); %! assert_equal (categories (A), {'car'; 'plane'; 'train'}); %! assert_equal (countcats (A), [4, 2, 1]); %! [~, neworder] = sort (countcats (A)); %! B = reordercats (A, neworder); %! assert_equal (categories (B), {'train'; 'plane'; 'car'}); %! assert_equal (A, B); %!error ... %! reordercats (categorical, 1.5) %!error ... %! reordercats (categorical, [0, 1, 2]) %!error ... %! reordercats (categorical, []) %!error ... %! reordercats (categorical ({'a', 'b', 'c'}), [1, 2, 2]) %!error ... %! reordercats (categorical ({'a', 'b', 'c'}), [1, 2, 4]) %!error ... %! reordercats (categorical, '') %!error ... %! reordercats (categorical, true) %!error ... %! reordercats (categorical, 'a') %!error ... %! reordercats (categorical, struct ('a', 1)) %!error ... %! reordercats (categorical ({'a', 'b', 'c'}), {'a', 'd', 'c'}) %!test %! A = categorical ({'blue', 'black', 'red', 'red', 'blue', 'black', 'black', 'red', 'blue'}); %! B = setcats (A, string ({'red', 'black'})); %! assert_equal (categories (B), {'red'; 'black'}); %! Bout = categorical ({'', 'black', 'red', 'red', '', 'black', 'black', 'red', ''}, ... %! {'red', 'black'}); %! assert_equal (isequaln (B, Bout), true); %!test %! A = categorical ({'blue', 'black', 'red', 'red', 'blue', 'black', 'black', 'red', 'blue'}); %! B = setcats (A, {'red', 'pink', 'blue'}); %! assert_equal (categories (B), {'red'; 'pink'; 'blue'}); %! Bout = categorical ({'blue', '', 'red', 'red', 'blue', '', '', 'red', 'blue'}, ... %! {'red', 'pink', 'blue'}); %! assert_equal (isequaln (B, Bout), true); %!test %! A = setcats (categorical, {}); %! assert_equal (categorical, categorical); %! assert_equal (isempty (A), true); %! assert_equal (categories (A), {}); %!test %! A = setcats (categorical ({'a', 'b', 'b'}), {}); %! assert_equal (double (A), [NaN, NaN, NaN]); %! assert_equal (categories (A), {}); %!test %! A = setcats (categorical ({'a','b'}, 'Protected', true), {'e','a'}); %! assert_equal (categories (A), {'e'; 'a'}); %! assert_equal (double (A), [2, NaN]); %! assert_equal (isprotected (A), true); %!error setcats (categorical) %!error setcats (categorical, []) %!error setcats (categorical, '') %!error ... %! setcats (categorical, 1) %!error ... %! setcats (categorical, true) %!error ... %! setcats (categorical, struct ('a', 1)) %!test %! colors = categorical ({'blue', 'red', 'green', 'black'}); %! signs = categorical ({'+', '-', '-', '+'}); %! C = colors .* signs; %! cats = categories (C); %! assert_equal (cats, {'black +'; 'black -'; 'blue +' ; 'blue -'; ... %! 'green +'; 'green -'; 'red +'; 'red -'}); %! C_out = categorical ({'blue +', 'red -', 'green -', 'black +'}, cats); %! assert_equal (C, C_out); %!error times (categorical) %!error ... %! categorical .* 1 %!error ... %! categorical .* string ('a') %!error ... %! 'cat' .* categorical %!error ... %! {'cat'} .* categorical ################################################################################ ## ** Relational Operations ** ## ################################################################################ ## Available Methods ## ## ## ## 'eq' 'ge' 'gt' 'le' ## ## 'lt' 'ne' ## ## ## ################################################################################ %!assert_equal (categorical ({'a'}) == 'a', true) %!assert_equal (string ('1') == categorical ({'a'}), false) %!assert_equal (categorical ({'a', ''}) == {'a'}, [true, false]) %!test %! A = categorical ({'a', 'b', 'c'}); %! B = categorical ({'a'; 'b'; 'c'}); %! assert_equal (A == B, logical (eye (3))); %!test %! A = categorical ({'a', 'b', 'c'}); %! B = categorical ({'a'; 'b'; 'c'}); %! B = repmat (B, 1, 3); %! assert_equal (A == B, logical (eye (3))); %!test %! X = categorical ([1, 2, NaN], [1, 2, 3], 'Ordinal', true); %! assert_equal (X == X, [true, true, false]); %! assert_equal (X == '1', [true, false, false]); %!error ... %! categorical == {'a', 'b'} %!error ... %! categorical ({'e', 'r'}) == string ({'e', 'r'}) %!error ... %! string ({'e', 'r'}) == categorical ({'e', 'r'}) %!error ... %! {'a', 'b'} == categorical %!error ... %! categorical ({'e', 'r'}, {'e', 'r'}, 'Ordinal', true) == categorical ({'e', 'r'}, {'e'}, 'Ordinal', true) %!error ... %! categorical ({'e', 'r'}, {'e', 'r'}, 'Ordinal', true) == categorical ({'e', 'r'}) %!error ... %! categorical ([1, 2, 3]) == categorical ([1, 2, 3, 4]) %!error ... %! categorical ([1, 2, 3]) == 1 %!error ... %! 1 == categorical ([1, 2, 3]) %!test %! X = categorical ([1, 2, NaN], [1, 2, 3], 'Ordinal', true); %! assert_equal (X >= '2', [false, true, false]); %! assert_equal (X >= '1', [true, true, false]); %!test %! X = categorical ([1, 2, 3], [1, 2, 3], 'Ordinal', true); %! assert_equal (X >= {'2'}, [false, true, true]); %! assert_equal (X >= string ('1'), [true, true, true]); %!test %! X = categorical ([1, 2, NaN], [1, 2, 3], 'Ordinal', true); %! assert_equal ('2' >= X, [true, true, false]); %! assert_equal ('1' >= X, [true, false, false]); %!test %! X = categorical ([1, 2, 3], [1, 2, 3], 'Ordinal', true); %! assert_equal ({'2'} >= X, [true, true, false]); %! assert_equal (string ('1') >= X, [true, false, false]); %!test %! X = categorical ([1, 2, 4], [1, 2, 3], 'Ordinal', true); %! assert_equal (X >= X', logical ([1, 1, 0; 0, 1, 0; 0, 0, 0])); %!test %! X = categorical ([1, 2, 3], [1, 2, 3], 'Ordinal', true); %! assert_equal (X >= X', logical ([1, 1, 1; 0, 1, 1; 0, 0, 1])); %!error categorical >= '1' %!error {'1'} >= categorical %!error ... %! categorical ([1, 2, 3], [1, 2, 3], 'Ordinal', true) >= {'1', '2'} %!error ... %! string ({'1', '2'}) >= categorical ([1, 2, 3], [1, 2, 3], 'Ordinal', true) %!error ... %! categorical ([1, 2, 3], [1, 2, 3], 'Ordinal', true) >= {'4'} %!error ... %! '4' >= categorical ([1, 2, 3], [1, 2, 3], 'Ordinal', true) %!error ... %! categorical >= 1 %!error ... %! 1 >= categorical %!error ... %! categorical >= categorical %!test %! X = categorical ([1, 2, NaN], [1, 2, 3], 'Ordinal', true); %! assert_equal (X > '2', [false, false, false]); %! assert_equal (X > '1', [false, true, false]); %!test %! X = categorical ([1, 2, 3], [1, 2, 3], 'Ordinal', true); %! assert_equal (X > {'2'}, [false, false, true]); %! assert_equal (X > string ('1'), [false, true, true]); %!test %! X = categorical ([1, 2, NaN], [1, 2, 3], 'Ordinal', true); %! assert_equal ('2' > X, [true, false, false]); %! assert_equal ('1' > X, [false, false, false]); %!test %! X = categorical ([1, 2, 3], [1, 2, 3], 'Ordinal', true); %! assert_equal ({'2'} > X, [true, false, false]); %! assert_equal (string ('1') > X, [false, false, false]); %!test %! X = categorical ([1, 2, 4], [1, 2, 3], 'Ordinal', true); %! assert_equal (X > X', logical ([0, 1, 0; 0, 0, 0; 0, 0, 0])); %!test %! X = categorical ([1, 2, 3], [1, 2, 3], 'Ordinal', true); %! assert_equal (X > X', logical ([0, 1, 1; 0, 0, 1; 0, 0, 0])); %!error categorical > '1' %!error {'1'} > categorical %!error ... %! categorical ([1, 2, 3], [1, 2, 3], 'Ordinal', true) > {'1', '2'} %!error ... %! string ({'1', '2'}) > categorical ([1, 2, 3], [1, 2, 3], 'Ordinal', true) %!error ... %! categorical ([1, 2, 3], [1, 2, 3], 'Ordinal', true) > {'4'} %!error ... %! '4' > categorical ([1, 2, 3], [1, 2, 3], 'Ordinal', true) %!error ... %! categorical > 1 %!error ... %! 1 > categorical %!error ... %! categorical > categorical %!test %! X = categorical ([1, 2, NaN], [1, 2, 3], 'Ordinal', true); %! assert_equal (X <= '2', [true, true, false]); %! assert_equal (X <= '1', [true, false, false]); %!test %! X = categorical ([1, 2, 3], [1, 2, 3], 'Ordinal', true); %! assert_equal (X <= {'2'}, [true, true, false]); %! assert_equal (X <= string ('1'), [true, false, false]); %!test %! X = categorical ([1, 2, NaN], [1, 2, 3], 'Ordinal', true); %! assert_equal ('2' <= X, [false, true, false]); %! assert_equal ('1' <= X, [true, true, false]); %!test %! X = categorical ([1, 2, 3], [1, 2, 3], 'Ordinal', true); %! assert_equal ({'2'} <= X, [false, true, true]); %! assert_equal (string ('1') <= X, [true, true, true]); %!test %! X = categorical ([1, 2, 4], [1, 2, 3], 'Ordinal', true); %! assert_equal (X <= X', logical ([1, 0, 0; 1, 1, 0; 0, 0, 0])); %!test %! X = categorical ([1, 2, 3], [1, 2, 3], 'Ordinal', true); %! assert_equal (X <= X', logical ([1, 0, 0; 1, 1, 0; 1, 1, 1])); %!error categorical <= '1' %!error {'1'} <= categorical %!error ... %! categorical ([1, 2, 3], [1, 2, 3], 'Ordinal', true) <= {'1', '2'} %!error ... %! string ({'1', '2'}) <= categorical ([1, 2, 3], [1, 2, 3], 'Ordinal', true) %!error ... %! categorical ([1, 2, 3], [1, 2, 3], 'Ordinal', true) <= {'4'} %!error ... %! '4' <= categorical ([1, 2, 3], [1, 2, 3], 'Ordinal', true) %!error ... %! categorical <= 1 %!error ... %! 1 <= categorical %!error ... %! categorical <= categorical %!test %! X = categorical ([1, 2, NaN], [1, 2, 3], 'Ordinal', true); %! assert_equal (X < '2', [true, false, false]); %! assert_equal (X < '1', [false, false, false]); %!test %! X = categorical ([1, 2, 3], [1, 2, 3], 'Ordinal', true); %! assert_equal (X < {'2'}, [true, false, false]); %! assert_equal (X < string ('1'), [false, false, false]); %!test %! X = categorical ([1, 2, NaN], [1, 2, 3], 'Ordinal', true); %! assert_equal ('2' < X, [false, false, false]); %! assert_equal ('1' < X, [false, true, false]); %!test %! X = categorical ([1, 2, 3], [1, 2, 3], 'Ordinal', true); %! assert_equal ({'2'} < X, [false, false, true]); %! assert_equal (string ('1') < X, [false, true, true]); %!test %! X = categorical ([1, 2, 4], [1, 2, 3], 'Ordinal', true); %! assert_equal (X < X', logical ([0, 0, 0; 1, 0, 0; 0, 0, 0])); %!test %! X = categorical ([1, 2, 3], [1, 2, 3], 'Ordinal', true); %! assert_equal (X < X', logical ([0, 0, 0; 1, 0, 0; 1, 1, 0])); %!error categorical < '1' %!error {'1'} < categorical %!error ... %! categorical ([1, 2, 3], [1, 2, 3], 'Ordinal', true) < {'1', '2'} %!error ... %! string ({'1', '2'}) < categorical ([1, 2, 3], [1, 2, 3], 'Ordinal', true) %!error ... %! categorical ([1, 2, 3], [1, 2, 3], 'Ordinal', true) < {'4'} %!error ... %! '4' < categorical ([1, 2, 3], [1, 2, 3], 'Ordinal', true) %!error ... %! categorical < 1 %!error ... %! 1 < categorical %!error ... %! categorical < categorical %!assert_equal (categorical ({'a'}) != 'a', false) %!assert_equal (string ('1') != categorical ({'a'}), true) %!assert_equal (categorical ({'a', ''}) != {'a'}, [false, true]) %!test %! A = categorical ({'a', 'b', 'c'}); %! B = categorical ({'a'; 'b'; 'c'}); %! assert_equal (A != B, ! logical (eye (3))); %!test %! A = categorical ({'a', 'b', 'c'}); %! B = categorical ({'a'; 'b'; 'c'}); %! B = repmat (B, 1, 3); %! assert_equal (A != B, ! logical (eye (3))); %!test %! X = categorical ([1, 2, NaN], [1, 2, 3], 'Ordinal', true); %! assert_equal (X != X, [false, false, true]); %! assert_equal (X != '1', [false, true, true]); %!error ... %! categorical != {'a', 'b'} %!error ... %! categorical ({'e', 'r'}) != string ({'e', 'r'}) %!error ... %! string ({'e', 'r'}) != categorical ({'e', 'r'}) %!error ... %! {'a', 'b'} != categorical %!error ... %! categorical ({'e', 'r'}, {'e', 'r'}, 'Ordinal', true) != categorical ({'e', 'r'}, {'e'}, 'Ordinal', true) %!error ... %! categorical ({'e', 'r'}, {'e', 'r'}, 'Ordinal', true) != categorical ({'e', 'r'}) %!error ... %! categorical ([1, 2, 3]) != categorical ([1, 2, 3, 4]) %!error ... %! categorical ([1, 2, 3]) != 1 %!error ... %! 1 != categorical ([1, 2, 3]) ################################################################################ ## ** Arithmetic Operations ** ## ################################################################################ ## Available Methods ## ## ## ## 'min' 'mink' 'max' 'maxk' ## ## 'median' 'mode' 'histcounts' ## ## ## ################################################################################ %!test %! X = categorical ([1,2,4], [1,2,3], 'Ordinal', true); %! M = min (X); %! M_out = categorical (1, [1,2,3], 'Ordinal', true); %! assert_equal (M, M_out); %!test %! X = categorical ([1,2,4], [1,2,3], 'Ordinal', true); %! M = min (X, [], 1); %! M_out = categorical ([1, 2, NaN], [1,2,3], 'Ordinal', true); %! assert_equal (isequaln (M, X), true); %! assert_equal (isequaln (M, M_out), true); %!test %! X = categorical ([1,2,4], [1,2,3], 'Ordinal', true); %! M = min (X, [], 'includenan'); %! M_out = categorical (NaN, [1,2,3], 'Ordinal', true); %! assert_equal (isequaln (M, M_out), true); %!test %! X = categorical ([1,2,4], [1,2,3], 'Ordinal', true); %! M = min (X, [], 1, 'includenan'); %! M_out = categorical ([1, 2, NaN], [1,2,3], 'Ordinal', true); %! assert_equal (isequaln (M, X), true); %! assert_equal (isequaln (M, M_out), true); %!test %! X = categorical ([1,2,4], [1,2,3], 'Ordinal', true); %! M = min (X, [], 2, 'omitnan'); %! M_out = categorical (1, [1,2,3], 'Ordinal', true); %! assert_equal (M, M_out); %!test %! X = categorical ([1,2,4], [1,2,3], 'Ordinal', true); %! M = min (X, [], 1, 'omitnan'); %! M_out = categorical ([1, 2, NaN], [1,2,3], 'Ordinal', true); %! assert_equal (isequaln (M, X), true); %! assert_equal (isequaln (M, M_out), true); %!test %! X = categorical ([1,2,4], [1,2,3], 'Ordinal', true); %! M = min (X, [], 'includeundefined'); %! M_out = categorical (NaN, [1,2,3], 'Ordinal', true); %! assert_equal (isequaln (M, M_out), true); %!test %! X = categorical ([1,2,4], [1,2,3], 'Ordinal', true); %! M = min (X, [], 1, 'includeundefined'); %! M_out = categorical ([1, 2, NaN], [1,2,3], 'Ordinal', true); %! assert_equal (isequaln (M, X), true); %! assert_equal (isequaln (M, M_out), true); %!test %! X = categorical ([1,2,4], [1,2,3], 'Ordinal', true); %! M = min (X, [], 2, 'omitundefined'); %! M_out = categorical (1, [1,2,3], 'Ordinal', true); %! assert_equal (M, M_out); %!test %! X = categorical ([1,2,4], [1,2,3], 'Ordinal', true); %! M = min (X, [], 1, 'omitundefined'); %! M_out = categorical ([1, 2, NaN], [1,2,3], 'Ordinal', true); %! assert_equal (isequaln (M, X), true); %! assert_equal (isequaln (M, M_out), true); %!test %! X = categorical (magic (3), [1:9], 'Ordinal', true); %! M = min (X); %! M_out = categorical ([3, 1, 2], [1:9], 'Ordinal', true); %! assert_equal (M, M_out); %!test %! X = categorical (magic (3), [1:9], 'Ordinal', true); %! M = min (X, [], 2); %! M_out = categorical ([1; 3; 2], [1:9], 'Ordinal', true); %! assert_equal (M, M_out); %!test %! x = categorical (reshape (1:8, [2, 2, 2]), 1:8, 'Ordinal', true); %! out = reshape ([1, 3, 5, 7], [1, 2, 2]); %! assert_equal (min (x, [], 1), categorical (out, 1:8, 'Ordinal', true)); %! out = reshape ([1, 2, 5, 6], [2, 1, 2]); %! assert_equal (min (x, [], 2), categorical (out, 1:8, 'Ordinal', true)); %! [y, i] = min (x, [], 3); %! assert_equal (ndims (y), 2); %! assert_equal (y, categorical ([1, 3; 2, 4], 1:8, 'Ordinal', true)); %! assert_equal (ndims (i), 2); %! assert_equal (i, ones (2)); %! [~, i] = min (x, [], 3, 'linear'); %! assert_equal (i, [1, 3; 2, 4]); %!test %! x = categorical (reshape (1:8, [2, 2, 2]), 1:8, 'Ordinal', true); %! assert_equal (min (x, [], [1, 3]), categorical ([1, 3], 1:8, 'Ordinal', true)); %! assert_equal (min (x, [], [2, 3]), categorical ([1; 2], 1:8, 'Ordinal', true)); %! out = reshape ([1, 5], [1, 1, 2]); %! assert_equal (min (x, [], [1, 2]), categorical (out, 1:8, 'Ordinal', true)); %! assert_equal (min (x, [], [1, 2, 3]), min (x, [], "all")); %!test %! X = categorical ([1, 2, 3, 4], 1:4, 'Ordinal', true); %! Y = categorical ([4, 3, 2, 1], 1:4, 'Ordinal', true); %! assert_equal (min (X, Y), categorical ([1, 2, 2, 1], 1:4, 'Ordinal', true)); %!test %! X = categorical ([1, 2, 3, 4], 1:4, 'Ordinal', true); %! Y = categorical (3, 1:4, 'Ordinal', true); %! assert_equal (min (X, Y), categorical ([1, 2, 3, 3], 1:4, 'Ordinal', true)); %!test %! X = categorical ([1, 2, 3, 4], 1:4, 'Ordinal', true); %! Y = categorical (2, 1:4, 'Ordinal', true); %! assert_equal (min (Y, X), categorical ([1, 2, 2, 2], 1:4, 'Ordinal', true)); %!test %! X = categorical ([1, 2, 3, 4], 1:4, 'Ordinal', true); %! Y = categorical ([4, 3, 2, 1], 1:4, 'Ordinal', true); %! out = min ([1, 2, 3, 4], [4; 3; 2; 1]); %! assert_equal (min (X, Y'), categorical (out, 1:4, 'Ordinal', true)); %!test %! X = categorical ([1, 2, 3, 4], 1:4, 'Ordinal', true); %! Y = categorical ([4, 3, 2, 1], 1:4, 'Ordinal', true); %! out = min ([1; 2; 3; 4], [4, 3, 2, 1]); %! assert_equal (min (X', Y), categorical (out, 1:4, 'Ordinal', true)); %!test %! x = repmat ([4, 3, 2, 4, 1, 5, 3, 2], 3, 2, 5, 2); %! [m, i] = min (categorical (x, 1:5, 'Ordinal', true), [], [2, 3], 'linear'); %! assert_equal (m, categorical (ones (3, 1, 1, 2), 1:5, 'Ordinal', true)); %! assert_equal (i(:,:,1,1), [13; 14; 15]); %! assert_equal (i(:,:,1,2), [253; 254; 255]); %!test %! x = repmat ([4, 3, 2, 4, 1, 5, 3, 2], 3, 2, 5, 2); %! [m, i] = min (categorical (x, 1:5, 'Ordinal', true), [], [1, 3], 'linear'); %! assert_equal (m, categorical (x(1,:,1,:), 1:5, 'Ordinal', true)); %! assert_equal (i(:,:,1,1), [1:3:46]); %! assert_equal (i(:,:,1,2), [241:3:286]); %!test %! x = repmat ([4, 3, 2, 4, 1, 5, 3, 2], 3, 2, 5, 2); %! [m, i] = min (categorical (x, 1:5, 'Ordinal', true), [], [2, 4], 'linear'); %! assert_equal (m, categorical (ones (3, 1, 5), 1:5, 'Ordinal', true)); %! assert_equal (i(:,:,1), [13; 14; 15]); %! assert_equal (i(:,:,2), [61; 62; 63]); %! assert_equal (i(:,:,3), [109; 110; 111]); %! assert_equal (i(:,:,4), [157; 158; 159]); %! assert_equal (i(:,:,5), [205; 206; 207]); %!test %! x = repmat ([4, 3, 2, 4, 1, 5, 3, 2], 3, 2, 5, 2); %! [m, i] = min (categorical (x, 1:5, 'Ordinal', true), [], [1, 4], 'linear'); %! assert_equal (m, categorical (x(1,:,:,1), 1:5, 'Ordinal', true)); %! assert_equal (i(:,:,1), [1:3:46]); %! assert_equal (i(:,:,2), [49:3:94]); %! assert_equal (i(:,:,3), [97:3:142]); %! assert_equal (i(:,:,4), [145:3:190]); %! assert_equal (i(:,:,5), [193:3:238]); %!test %! x = repmat ([4, 3, 2, 4, 1, 5, 3, 2], 3, 2, 5, 2); %! [m, i] = min (categorical (x, 1:5, 'Ordinal', true), [], [1, 2, 3], 'linear'); %! assert_equal (m, categorical (ones (1, 1, 1, 2), 1:5, 'Ordinal', true)); %! assert_equal (i(:,:,1,1), 13); %! assert_equal (i(:,:,1,2), 253); %!test %! x = repmat ([4, 3, 2, 4, 1, 5, 3, 2], 3, 2, 5, 2); %! [m, i] = min (categorical (x, 1:5, 'Ordinal', true), [], [2, 3, 4], 'linear'); %! assert_equal (m, categorical ([1; 1; 1], 1:5, 'Ordinal', true)); %! assert_equal (i, [13; 14; 15]); %!error ... %! min (categorical) %!error ... %! min (categorical ([], 1, 'Ordinal', true), [], 1, 2, 3, 4) %!error ... %! [m, i] = min (categorical ([], 1, 'Ordinal', true), categorical) %!error ... %! min (categorical ([], 1, 'Ordinal', true), categorical) %!error ... %! min (categorical ([], 1, 'Ordinal', true), categorical (1, 2, 'Ordinal', true)) %!test %! X = categorical ([1,2,4], [1,2,3], 'Ordinal', true); %! M = max (X); %! M_out = categorical (2, [1,2,3], 'Ordinal', true); %! assert_equal (M, M_out); %!test %! X = categorical ([1,2,4], [1,2,3], 'Ordinal', true); %! M = max (X, [], 1); %! M_out = categorical ([1, 2, NaN], [1,2,3], 'Ordinal', true); %! assert_equal (isequaln (M, X), true); %! assert_equal (isequaln (M, M_out), true); %!test %! X = categorical ([1,2,4], [1,2,3], 'Ordinal', true); %! M = max (X, [], 'includenan'); %! M_out = categorical (NaN, [1,2,3], 'Ordinal', true); %! assert_equal (isequaln (M, M_out), true); %!test %! X = categorical ([1,2,4], [1,2,3], 'Ordinal', true); %! M = max (X, [], 1, 'includenan'); %! M_out = categorical ([1, 2, NaN], [1,2,3], 'Ordinal', true); %! assert_equal (isequaln (M, X), true); %! assert_equal (isequaln (M, M_out), true); %!test %! X = categorical ([1,2,4], [1,2,3], 'Ordinal', true); %! M = max (X, [], 2, 'omitnan'); %! M_out = categorical (2, [1,2,3], 'Ordinal', true); %! assert_equal (M, M_out); %!test %! X = categorical ([1,2,4], [1,2,3], 'Ordinal', true); %! M = max (X, [], 1, 'omitnan'); %! M_out = categorical ([1, 2, NaN], [1,2,3], 'Ordinal', true); %! assert_equal (isequaln (M, X), true); %! assert_equal (isequaln (M, M_out), true); %!test %! X = categorical ([1,2,4], [1,2,3], 'Ordinal', true); %! M = max (X, [], 'includeundefined'); %! M_out = categorical (NaN, [1,2,3], 'Ordinal', true); %! assert_equal (isequaln (M, M_out), true); %!test %! X = categorical ([1,2,4], [1,2,3], 'Ordinal', true); %! M = max (X, [], 1, 'includeundefined'); %! M_out = categorical ([1, 2, NaN], [1,2,3], 'Ordinal', true); %! assert_equal (isequaln (M, X), true); %! assert_equal (isequaln (M, M_out), true); %!test %! X = categorical ([1,2,4], [1,2,3], 'Ordinal', true); %! M = max (X, [], 2, 'omitundefined'); %! M_out = categorical (2, [1,2,3], 'Ordinal', true); %! assert_equal (M, M_out); %!test %! X = categorical ([1,2,3], [1,2,3], 'Ordinal', true); %! M = max (X, [], 2, 'omitundefined'); %! M_out = categorical (3, [1,2,3], 'Ordinal', true); %! assert_equal (M, M_out); %!test %! X = categorical ([1,2,4], [1,2,3], 'Ordinal', true); %! M = max (X, [], 1, 'omitundefined'); %! M_out = categorical ([1, 2, NaN], [1,2,3], 'Ordinal', true); %! assert_equal (isequaln (M, X), true); %! assert_equal (isequaln (M, M_out), true); %!test %! X = categorical (magic (3), [1:9], 'Ordinal', true); %! M = max (X); %! M_out = categorical ([8, 9, 7], [1:9], 'Ordinal', true); %! assert_equal (M, M_out); %!test %! X = categorical (magic (3), [1:9], 'Ordinal', true); %! M = max (X, [], 2); %! M_out = categorical ([8; 7; 9], [1:9], 'Ordinal', true); %! assert_equal (M, M_out); %!test %! x = categorical (reshape (1:8, [2, 2, 2]), 1:8, 'Ordinal', true); %! out = reshape ([2, 4, 6, 8], [1, 2, 2]); %! assert_equal (max (x, [], 1), categorical (out, 1:8, 'Ordinal', true)); %! out = reshape ([3, 4, 7, 8], [2, 1, 2]); %! assert_equal (max (x, [], 2), categorical (out, 1:8, 'Ordinal', true)); %! [y, i] = max (x, [], 3); %! assert_equal (ndims (y), 2); %! assert_equal (y, categorical ([5, 7; 6, 8], 1:8, 'Ordinal', true)); %! assert_equal (ndims (i), 2); %! assert_equal (i, 2 * ones (2)); %! [~, i] = max (x, [], 3, 'linear'); %! assert_equal (i, [5, 7; 6, 8]); %!test %! x = categorical (reshape (1:8, [2, 2, 2]), 1:8, 'Ordinal', true); %! assert_equal (max (x, [], [1, 3]), categorical ([6, 8], 1:8, 'Ordinal', true)); %! assert_equal (max (x, [], [2, 3]), categorical ([7; 8], 1:8, 'Ordinal', true)); %! out = reshape ([4, 8], [1, 1, 2]); %! assert_equal (max (x, [], [1, 2]), categorical (out, 1:8, 'Ordinal', true)); %! assert_equal (max (x, [], [1, 2, 3]), max (x, [], "all")); %!test %! X = categorical ([1, 2, 3, 4], 1:4, 'Ordinal', true); %! Y = categorical ([4, 3, 2, 1], 1:4, 'Ordinal', true); %! assert_equal (max (X, Y), categorical ([4, 3, 3, 4], 1:4, 'Ordinal', true)); %!test %! X = categorical ([1, 2, 3, 4], 1:4, 'Ordinal', true); %! Y = categorical (3, 1:4, 'Ordinal', true); %! assert_equal (max (X, Y), categorical ([3, 3, 3, 4], 1:4, 'Ordinal', true)); %!test %! X = categorical ([1, 2, 3, 4], 1:4, 'Ordinal', true); %! Y = categorical (2, 1:4, 'Ordinal', true); %! assert_equal (max (Y, X), categorical ([2, 2, 3, 4], 1:4, 'Ordinal', true)); %!test %! X = categorical ([1, 2, 3, 4], 1:4, 'Ordinal', true); %! Y = categorical ([4, 3, 2, 1], 1:4, 'Ordinal', true); %! out = max ([1, 2, 3, 4], [4; 3; 2; 1]); %! assert_equal (max (X, Y'), categorical (out, 1:4, 'Ordinal', true)); %!test %! X = categorical ([1, 2, 3, 4], 1:4, 'Ordinal', true); %! Y = categorical ([4, 3, 2, 1], 1:4, 'Ordinal', true); %! out = max ([1; 2; 3; 4], [4, 3, 2, 1]); %! assert_equal (max (X', Y), categorical (out, 1:4, 'Ordinal', true)); %!test %! x = repmat ([4, 3, 2, 4, 1, 5, 3, 2], 3, 2, 5, 2); %! [m, i] = max (categorical (x, 1:5, 'Ordinal', true), [], [2, 3], 'linear'); %! assert_equal (m, categorical (5 * ones (3, 1, 1, 2), 1:5, 'Ordinal', true)); %! assert_equal (i(:,:,1,1), [16; 17; 18]); %! assert_equal (i(:,:,1,2), [256; 257; 258]); %!test %! x = repmat ([4, 3, 2, 4, 1, 5, 3, 2], 3, 2, 5, 2); %! [m, i] = max (categorical (x, 1:5, 'Ordinal', true), [], [1, 3], 'linear'); %! assert_equal (m, categorical (x(1,:,1,:), 1:5, 'Ordinal', true)); %! assert_equal (i(:,:,1,1), [1:3:46]); %! assert_equal (i(:,:,1,2), [241:3:286]); %!test %! x = repmat ([4, 3, 2, 4, 1, 5, 3, 2], 3, 2, 5, 2); %! [m, i] = max (categorical (x, 1:5, 'Ordinal', true), [], [2, 4], 'linear'); %! assert_equal (m, categorical (5 * ones (3, 1, 5), 1:5, 'Ordinal', true)); %! assert_equal (i(:,:,1), [16; 17; 18]); %! assert_equal (i(:,:,2), [64; 65; 66]); %! assert_equal (i(:,:,3), [112; 113; 114]); %! assert_equal (i(:,:,4), [160; 161; 162]); %! assert_equal (i(:,:,5), [208; 209; 210]); %!test %! x = repmat ([4, 3, 2, 4, 1, 5, 3, 2], 3, 2, 5, 2); %! [m, i] = max (categorical (x, 1:5, 'Ordinal', true), [], [1, 4], 'linear'); %! assert_equal (m, categorical (x(1,:,:,1), 1:5, 'Ordinal', true)); %! assert_equal (i(:,:,1), [1:3:46]); %! assert_equal (i(:,:,2), [49:3:94]); %! assert_equal (i(:,:,3), [97:3:142]); %! assert_equal (i(:,:,4), [145:3:190]); %! assert_equal (i(:,:,5), [193:3:238]); %!test %! x = repmat ([4, 3, 2, 4, 1, 5, 3, 2], 3, 2, 5, 2); %! [m, i] = max (categorical (x, 1:5, 'Ordinal', true), [], [1, 2, 3], 'linear'); %! assert_equal (m, categorical (5 * ones (1, 1, 1, 2), 1:5, 'Ordinal', true)); %! assert_equal (i(:,:,1,1), 16); %! assert_equal (i(:,:,1,2), 256); %!test %! x = repmat ([4, 3, 2, 4, 1, 5, 3, 2], 3, 2, 5, 2); %! [m, i] = max (categorical (x, 1:5, 'Ordinal', true), [], [2, 3, 4], 'linear'); %! assert_equal (m, categorical ([5; 5; 5], 1:5, 'Ordinal', true)); %! assert_equal (i, [16; 17; 18]); %!error ... %! max (categorical) %!error ... %! max (categorical ([], 1, 'Ordinal', true), [], 1, 2, 3, 4) %!error ... %! [m, i] = max (categorical ([], 1, 'Ordinal', true), categorical) %!error ... %! max (categorical ([], 1, 'Ordinal', true), categorical) %!error ... %! max (categorical ([], 1, 'Ordinal', true), categorical (1, 2, 'Ordinal', true)) %!test %! M = median (categorical ([2, 4, 6, 8], 1:8, 'Ordinal', true)); %! assert_equal (M, categorical (5, 1:8, 'Ordinal', true)); %!test %! M = median (categorical ([8, 2, 6, 4], 1:8, 'Ordinal', true)); %! assert_equal (M, categorical (5, 1:8, 'Ordinal', true)); %!test %! M = median (categorical ([1, 2, 3, 4], 1:4, 'Ordinal', true)); %! assert_equal (M, categorical (3, 1:4, 'Ordinal', true)); %!test %! M = median (categorical ([1, 2, 3, 4], 1:3, 'Ordinal', true)); %! assert_equal (isequaln (M, categorical (NaN, 1:3, 'Ordinal', true)), true); %!test %! M = median (categorical ([1, 2, 3, 4], 1:3, 'Ordinal', true), 'omitundefined'); %! assert_equal (M, categorical (2, 1:3, 'Ordinal', true)); %!test %! M = median (categorical (magic (3), 1:9, 'Ordinal', true)); %! assert_equal (M, categorical ([4, 5, 6], 1:9, 'Ordinal', true)); %!test %! M = median (categorical (magic (3), 1:9, 'Ordinal', true), 2); %! assert_equal (M, categorical ([6; 5; 4], 1:9, 'Ordinal', true)); %!test %! M = median (categorical (magic (3), 1:9, 'Ordinal', true), 3); %! assert_equal (M, categorical (magic (3), 1:9, 'Ordinal', true)); %!test %! M = median (categorical (magic (3), 1:9, 'Ordinal', true), [1, 2]); %! assert_equal (M, categorical (5, 1:9, 'Ordinal', true)); %!test %! M = median (categorical (magic (3), 1:9, 'Ordinal', true), 'all'); %! assert_equal (M, categorical (5, 1:9, 'Ordinal', true)); %!test %! M = median (categorical (magic (4), 1:16, 'Ordinal', true)); %! assert_equal (M, categorical ([7, 9, 8, 10], 1:16, 'Ordinal', true)); %!test %! M = median (categorical (magic (4), 1:16, 'Ordinal', true), 2); %! assert_equal (M, categorical ([8; 9; 8; 9], 1:16, 'Ordinal', true)); %!test %! M = median (categorical (magic (4), 1:16, 'Ordinal', true), 3); %! assert_equal (M, categorical (magic (4), 1:16, 'Ordinal', true)); %!test %! M = median (categorical (magic (4), 1:16, 'Ordinal', true), [1, 2]); %! assert_equal (M, categorical (9, 1:16, 'Ordinal', true)); %!test %! M = median (categorical (magic (4), 1:16, 'Ordinal', true), 'all'); %! assert_equal (M, categorical (9, 1:16, 'Ordinal', true)); %!test %! M = median (categorical (magic (4), 1:14, 'Ordinal', true)); %! assert_equal (isequaln (M, categorical ([NaN, 9, NaN, 10], 1:14, 'Ordinal', true)), true); %!test %! M = median (categorical (magic (4), 1:14, 'Ordinal', true), 2); %! assert_equal (isequaln (M, categorical ([NaN; 9; 8; NaN], 1:14, 'Ordinal', true)), true); %!test %! M = median (categorical (magic (4), 1:14, 'Ordinal', true), 3); %! assert_equal (isequaln (M, categorical (magic (4), 1:14, 'Ordinal', true)), true); %!test %! M = median (categorical (magic (4), 1:14, 'Ordinal', true), [1, 2]); %! assert_equal (isequaln (M, categorical (NaN, 1:14, 'Ordinal', true)), true); %!test %! M = median (categorical (magic (4), 1:14, 'Ordinal', true), 'all'); %! assert_equal (isequaln (M, categorical (NaN, 1:14, 'Ordinal', true)), true); %!test %! M = median (categorical (magic (4), 1:14, 'Ordinal', true), 'omitundefined'); %! assert_equal (M, categorical ([5, 9, 6, 10], 1:14, 'Ordinal', true)); %!test %! M = median (categorical (magic (4), 1:14, 'Ordinal', true), 2, 'omitundefined'); %! assert_equal (M, categorical ([3; 9; 8; 4], 1:14, 'Ordinal', true)); %!test %! M = median (categorical (magic (4), 1:14, 'Ordinal', true), 3, 'omitundefined'); %! assert_equal (isequaln (M, categorical (magic (4), 1:14, 'Ordinal', true)), true); %!test %! M = median (categorical (magic (4), 1:14, 'Ordinal', true), [1, 2], 'omitundefined'); %! assert_equal (M, categorical (8, 1:14, 'Ordinal', true)); %!test %! M = median (categorical (magic (4), 1:14, 'Ordinal', true), 'all', 'omitundefined'); %! assert_equal (M, categorical (8, 1:14, 'Ordinal', true)); %!error ... %! median (categorical) %!error ... %! median (categorical (1, 1, 'Ordinal', true), 1, 2, 3) %!test %! [m, f, c] = mode (categorical (toeplitz (1:5))); %! assert_equal (m, categorical ([1, 2, 2, 2, 1], 1:5)); %! assert_equal (f, [1, 2, 2, 2, 1]); %! assert_equal (size (c), [1, 5]); %! assert_equal (c{1}, categorical ([1:5]', 1:5)); %! assert_equal (c{2}, categorical (2, 1:5)); %! assert_equal (c{3}, categorical ([2; 3], 1:5)); %! assert_equal (c{4}, categorical (2, 1:5)); %! assert_equal (c{5}, categorical ([1:5]', 1:5)); %!test %! [m, f, c] = mode (categorical (toeplitz (1:5)), 2); %! assert_equal (m, categorical ([1; 2; 2; 2; 1], 1:5)); %! assert_equal (f, [1; 2; 2; 2; 1]); %! assert_equal (size (c), [5, 1]); %! assert_equal (c{1}, categorical ([1:5]', 1:5)); %! assert_equal (c{2}, categorical (2, 1:5)); %! assert_equal (c{3}, categorical ([2; 3], 1:5)); %! assert_equal (c{4}, categorical (2, 1:5)); %! assert_equal (c{5}, categorical ([1:5]', 1:5)); %!test %! [m, f, c] = mode (categorical (toeplitz (1:5)), 3); %! assert_equal (m, categorical (toeplitz (1:5), 1:5)); %! assert_equal (f, ones (5)); %! assert_equal (size (c), [5, 5]); %! C = arrayfun (@(x) categorical (x, 1:5), toeplitz (1:5), 'UniformOutput', false); %! assert_equal (c, C); %!test %! [m, f, c] = mode (categorical (toeplitz (1:5)), [1, 2]); %! assert_equal (m, categorical (2, 1:5)); %! assert_equal (f, 8); %! assert_equal (isscalar (c), true); %! assert_equal (c{1}, categorical (2, 1:5)); %!test %! [m, f, c] = mode (categorical (toeplitz (1:5)), 'all'); %! assert_equal (m, categorical (2, 1:5)); %! assert_equal (f, 8); %! assert_equal (isscalar (c), true); %! assert_equal (c{1}, categorical (2, 1:5)); %!test %! A = [0 0 1 1 1 0 0 0 0 NaN NaN 1 0 0 0 1 0 1 0 1 0 0 0 1 1 1 1]; %! C = categorical (A, [1, 0, NaN], {'yes', 'no', 'undecided'}); %! [N, Cats] = histcounts (C); %! assert_equal (N, [11, 14, 2]); %! assert_equal (Cats, {'yes', 'no', 'undecided'}); %! N = histcounts (C, 'Normalization', 'countdensity'); %! assert_equal (N, [11, 14, 2]); %! N = histcounts (C, 'Normalization', 'probability'); %! assert_equal (N, [0.4074, 0.5185, 0.0741], 1e-4); %! N = histcounts (C, 'Normalization', 'pdf'); %! assert_equal (N, [0.4074, 0.5185, 0.0741], 1e-4); %! N = histcounts (C, 'Normalization', 'cumcount'); %! assert_equal (N, [11, 25, 27]); %! N = histcounts (C, 'Normalization', 'cdf'); %! assert_equal (N, [0.4074, 0.9259, 1.0000], 1e-4); %!test %! A = [0 0 1 1 1 0 0 0 0 NaN NaN 1 0 0 0 1 0 1 0 1 0 0 0 1 1 1 1]; %! C = categorical (A, [1, 0, NaN], {'yes', 'no', 'undecided'}); %! [N, Cats] = histcounts (C, {'yes', 'no'}); %! assert_equal (N, [11, 14]); %! assert_equal (Cats, {'yes', 'no'}); %! N = histcounts (C, {'yes', 'no'}, 'Normalization', 'countdensity'); %! assert_equal (N, [11, 14]); %! N = histcounts (C, {'yes', 'no'}, 'Normalization', 'probability'); %! assert_equal (N, [0.4074, 0.5185], 1e-4); %! N = histcounts (C, {'yes', 'no'}, 'Normalization', 'pdf'); %! assert_equal (N, [0.4074, 0.5185], 1e-4); %! N = histcounts (C, {'yes', 'no'}, 'Normalization', 'cumcount'); %! assert_equal (N, [11, 25]); %! N = histcounts (C, {'yes', 'no'}, 'Normalization', 'cdf'); %! assert_equal (N, [0.4074, 0.9259], 1e-4); %!test %! A = [0 0 1 1 1 0 0 0 0 NaN NaN 1 0 0 0 1 0 1 0 1 0 0 0 1 1 1 1]; %! C = categorical (A); %! [N, Cats] = histcounts (C); %! assert_equal (N, [14, 11]); %! assert_equal (Cats, {'0', '1'}); %! N = histcounts (C, 'Normalization', 'countdensity'); %! assert_equal (N, [14, 11]); %! N = histcounts (C, 'Normalization', 'probability'); %! assert_equal (N, [0.5185, 0.4074], 1e-4); %! N = histcounts (C, 'Normalization', 'pdf'); %! assert_equal (N, [0.5185, 0.4074], 1e-4); %! N = histcounts (C, 'Normalization', 'cumcount'); %! assert_equal (N, [14, 25]); %! N = histcounts (C, 'Normalization', 'cdf'); %! assert_equal (N, [0.5185, 0.9259], 1e-4); %!test %! [N, Cats] = histcounts (categorical, {}); %! assert_equal (N, ones (1, 0)); %! assert_equal (Cats, cell (1, 0)); %!error ... %! histcounts (categorical, 'Normalization', 'some') %!error ... %! histcounts (categorical, 1) %!error ... %! histcounts (categorical, '1') %!error ... %! histcounts (categorical, {1}) %!error ... %! histcounts (categorical, {'1'}) %!error ... %! histcounts (categorical, categorical (1)) %!error ... %! histcounts (categorical, string ('a')) ################################################################################ ## ** Sort, Filter, and Set Operations ** ## ################################################################################ ## Available Methods ## ## ## ## 'sort' 'sortrows' 'topkrows' 'unique' ## ## 'intersect' 'setdiff' 'setxor' 'union' ## ## ## ################################################################################ %!test %! C = categorical ([1, 2; 1, 3; 2, -4; 2, 1]); %! [B, I] = sort (C); %! assert_equal (B, categorical ([1, -4; 1, 1; 2, 2; 2, 3])); %! assert_equal (I, [1, 3; 2, 4; 3, 1; 4, 2]); %!test %! C = categorical ([1, 2; 1, 3; 2, -4; 2, 1]); %! [B, I] = sort (C, 1); %! assert_equal (B, categorical ([1, -4; 1, 1; 2, 2; 2, 3])); %! assert_equal (I, [1, 3; 2, 4; 3, 1; 4, 2]); %!test %! C = categorical ([1, 2; 1, 3; 2, -4; 2, 1]); %! [B, I] = sort (C, 2); %! assert_equal (B, categorical ([1, 2; 1, 3; -4, 2; 1, 2])); %! assert_equal (I, [1, 2; 1, 2; 2, 1; 2, 1]); %!test %! C = categorical ([1, 2; 1, 3; 2, -4; 2, 1]); %! [B, I] = sort (C, 'descend'); %! assert_equal (B, categorical ([2, 3; 2, 2; 1, 1; 1, -4])); %! assert_equal (I, [3, 2; 4, 1; 1, 4; 2, 3]); %!test %! C = categorical ([1, 2; 1, 3; 2, -4; 2, 1]); %! [B, I] = sort (C, 1, 'descend'); %! assert_equal (B, categorical ([2, 3; 2, 2; 1, 1; 1, -4])); %! assert_equal (I, [3, 2; 4, 1; 1, 4; 2, 3]); %!test %! C = categorical ([1, 2; 1, 3; 2, -4; 2, 1]); %! [B, I] = sort (C, 2, 'descend'); %! assert_equal (B, categorical ([2, 1; 3, 1; 2, -4; 2, 1])); %! assert_equal (I, [2, 1; 2, 1; 1, 2; 1, 2]); %!test %! C = categorical ([1, 2, 2; 1, 3, 1; 2, -4, 2; 2, 1, NaN; -2, NaN, 3; 1, 3, 2]); %! [B, I] = sort (C); %! X = [-2, -4, 1; 1, 1, 2; 1, 2, 2; 1, 3, 2; 2, 3, 3; 2, NaN, NaN]; %! assert_equal (isequaln (B, categorical (X)), true); %! assert_equal (I, [5, 3, 2; 1, 4, 1; 2, 1, 3; 6, 2, 6; 3, 6, 5; 4, 5, 4]); %!test %! C = categorical ([1, 2, 2; 1, 3, 1; 2, -4, 2; 2, 1, NaN; -2, NaN, 3; 1, 3, 2]); %! [B, I] = sort (C, 1); %! X = [-2, -4, 1; 1, 1, 2; 1, 2, 2; 1, 3, 2; 2, 3, 3; 2, NaN, NaN]; %! assert_equal (isequaln (B, categorical (X)), true); %! assert_equal (I, [5, 3, 2; 1, 4, 1; 2, 1, 3; 6, 2, 6; 3, 6, 5; 4, 5, 4]); %!test %! C = categorical ([1, 2, 2; 1, 3, 1; 2, -4, 2; 2, 1, NaN; -2, NaN, 3; 1, 3, 2]); %! [B, I] = sort (C, 2); %! X = [1, 2, 2; 1, 1, 3; -4, 2, 2; 1, 2, NaN; -2, 3, NaN; 1, 2, 3]; %! assert_equal (isequaln (B, categorical (X)), true); %! assert_equal (I, [1, 2, 3; 1, 3, 2; 2, 1, 3; 2, 1, 3; 1, 3, 2; 1, 3, 2]); %!test %! C = categorical ([1, 2, 2; 1, 3, 1; 2, -4, 2; 2, 1, NaN; -2, NaN, 3; 1, 3, 2]); %! [B, I] = sort (C, 1, 'MissingPlacement', 'first'); %! X = [-2, NaN, NaN; 1, -4, 1; 1, 1, 2; 1, 2, 2; 2, 3, 2; 2, 3, 3]; %! assert_equal (isequaln (B, categorical (X)), true); %! assert_equal (I, [5, 5, 4; 1, 3, 2; 2, 4, 1; 6, 1, 3; 3, 2, 6; 4, 6, 5]); %!test %! C = categorical ([1, 2, 2; 1, 3, 1; 2, -4, 2; 2, 1, NaN; -2, NaN, 3; 1, 3, 2]); %! [B, I] = sort (C, 2, 'MissingPlacement', 'first'); %! X = [1, 2, 2; 1, 1, 3; -4, 2, 2; NaN, 1, 2; NaN, -2, 3; 1, 2, 3]; %! assert_equal (isequaln (B, categorical (X)), true); %! assert_equal (I, [1, 2, 3; 1, 3, 2; 2, 1, 3; 3, 2, 1; 2, 1, 3; 1, 3, 2]); %!test %! C = categorical ([1, 2, 2; 1, 3, 1; 2, -4, 2; 2, 1, NaN; -2, NaN, 3; 1, 3, 2]); %! [B1, I1] = sort (C, 1); %! [B2, I2] = sort (C, 1, 'MissingPlacement', 'last'); %! assert_equal (isequaln (B1, B2), true); %! assert_equal (I1, I2); %!test %! C = categorical ([1, 2, 2; 1, 3, 1; 2, -4, 2; 2, 1, NaN; -2, NaN, 3; 1, 3, 2]); %! [B1, I1] = sort (C, 2); %! [B2, I2] = sort (C, 2, 'MissingPlacement', 'last'); %! assert_equal (isequaln (B1, B2), true); %! assert_equal (I1, I2); %!test %! C = categorical ([1, 2, 2; 1, 3, 1; 2, -4, 2; 2, 1, NaN; -2, NaN, 3; 1, 3, 2]); %! [B, I] = sort (C, 'descend'); %! X = [2, NaN, NaN; 2, 3, 3; 1, 3, 2; 1, 2, 2; 1, 1, 2; -2, -4, 1]; %! assert_equal (isequaln (B, categorical (X)), true); %! assert_equal (I, [3, 5, 4; 4, 2, 5; 1, 6, 1; 2, 1, 3; 6, 4, 6; 5, 3, 2]); %!test %! C = categorical ([1, 2, 2; 1, 3, 1; 2, -4, 2; 2, 1, NaN; -2, NaN, 3; 1, 3, 2]); %! [B, I] = sort (C, 1, 'descend'); %! X = [2, NaN, NaN; 2, 3, 3; 1, 3, 2; 1, 2, 2; 1, 1, 2; -2, -4, 1]; %! assert_equal (isequaln (B, categorical (X)), true); %! assert_equal (I, [3, 5, 4; 4, 2, 5; 1, 6, 1; 2, 1, 3; 6, 4, 6; 5, 3, 2]); %!test %! C = categorical ([1, 2, 2; 1, 3, 1; 2, -4, 2; 2, 1, NaN; -2, NaN, 3; 1, 3, 2]); %! [B, I] = sort (C, 2, 'descend'); %! X = [2, 2, 1; 3, 1, 1; 2, 2, -4; NaN, 2, 1; NaN, 3, -2; 3, 2, 1]; %! assert_equal (isequaln (B, categorical (X)), true); %! assert_equal (I, [2, 3, 1; 2, 1, 3; 1, 3, 2; 3, 1, 2; 2, 3, 1; 2, 3, 1]); %!test %! C = categorical ([1, 2, 2; 1, 3, 1; 2, -4, 2; 2, 1, NaN; -2, NaN, 3; 1, 3, 2]); %! [B1, I1] = sort (C, 1, 'descend'); %! [B2, I2] = sort (C, 1, 'descend', 'MissingPlacement', 'first'); %! assert_equal (isequaln (B1, B2), true); %! assert_equal (I1, I2); %!test %! C = categorical ([1, 2, 2; 1, 3, 1; 2, -4, 2; 2, 1, NaN; -2, NaN, 3; 1, 3, 2]); %! [B1, I1] = sort (C, 2, 'descend'); %! [B2, I2] = sort (C, 2, 'descend', 'MissingPlacement', 'first'); %! assert_equal (isequaln (B1, B2), true); %! assert_equal (I1, I2); %!test %! C = categorical ([1, 2, 2; 1, 3, 1; 2, -4, 2; 2, 1, NaN; -2, NaN, 3; 1, 3, 2]); %! [B, I] = sort (C, 1, 'descend', 'MissingPlacement', 'last'); %! X = [2, 3, 3; 2, 3, 2; 1, 2, 2; 1, 1, 2; 1, -4, 1; -2, NaN, NaN]; %! assert_equal (isequaln (B, categorical (X)), true); %! assert_equal (I, [3, 2, 5; 4, 6, 1; 1, 1, 3; 2, 4, 6; 6, 3, 2; 5, 5, 4]); %!test %! C = categorical ([1, 2, 2; 1, 3, 1; 2, -4, 2; 2, 1, NaN; -2, NaN, 3; 1, 3, 2]); %! [B, I] = sort (C, 2, 'descend', 'MissingPlacement', 'last'); %! X = [2, 2, 1; 3, 1, 1; 2, 2, -4; 2, 1, NaN; 3, -2, NaN; 3, 2, 1]; %! assert_equal (isequaln (B, categorical (X)), true); %! assert_equal (I, [2, 3, 1; 2, 1, 3; 1, 3, 2; 1, 2, 3; 3, 1, 2; 2, 3, 1]); %!test %! C = categorical ([1, 2; 1, 3; 2, 1; 2, 1], [3, 2, 1], {'a', 'b', 'c'}, 'Ordinal', true); %! [B, I] = sort (C); %! X = categorical ([2, 3; 2, 2; 1, 1; 1, 1], [3, 2, 1], {'a', 'b', 'c'}, 'Ordinal', true); %! assert_equal (isequaln (B, X), true); %! assert_equal (I, [3, 2; 4, 1; 1, 3; 2, 4]); %!test %! C = categorical ([1, 2; 1, 3; 2, 1; 2, 1], [3, 2, 1], {'a', 'b', 'c'}, 'Ordinal', true); %! [B, I] = sort (C, 1); %! X = categorical ([2, 3; 2, 2; 1, 1; 1, 1], [3, 2, 1], {'a', 'b', 'c'}, 'Ordinal', true); %! assert_equal (isequaln (B, X), true); %! assert_equal (I, [3, 2; 4, 1; 1, 3; 2, 4]); %!test %! C = categorical ([1, 2; 1, 3; 2, 1; 2, 1], [3, 2, 1], {'a', 'b', 'c'}, 'Ordinal', true); %! [B, I] = sort (C, 2); %! X = categorical ([2, 1; 3, 1; 2, 1; 2, 1], [3, 2, 1], {'a', 'b', 'c'}, 'Ordinal', true); %! assert_equal (isequaln (B, X), true); %! assert_equal (I, [2, 1; 2, 1; 1, 2; 1, 2]); %!test %! C = categorical ([1, 2; 1, 3; 2, NaN; 2, 1], [3, 2, 1], {'a', 'b', 'c'}, 'Ordinal', true); %! [B, I] = sort (C, 'descend'); %! X = categorical ([1, NaN; 1, 1; 2, 2; 2, 3], [3, 2, 1], {'a', 'b', 'c'}, 'Ordinal', true); %! assert_equal (isequaln (B, X), true); %! assert_equal (I, [1, 3; 2, 4; 3, 1; 4, 2]); %!test %! C = categorical ([1, 2; 1, 3; 2, NaN; 2, 1], [3, 2, 1], {'a', 'b', 'c'}, 'Ordinal', true); %! [B, I] = sort (C, 1, 'descend'); %! X = categorical ([1, NaN; 1, 1; 2, 2; 2, 3], [3, 2, 1], {'a', 'b', 'c'}, 'Ordinal', true); %! assert_equal (isequaln (B, X), true); %! assert_equal (I, [1, 3; 2, 4; 3, 1; 4, 2]); %!test %! C = categorical ([1, 2; 1, 3; 2, NaN; 2, 1], [3, 2, 1], {'a', 'b', 'c'}, 'Ordinal', true); %! [B, I] = sort (C, 2, 'descend'); %! X = categorical ([1, 2; 1, 3; NaN, 2; 1, 2], [3, 2, 1], {'a', 'b', 'c'}, 'Ordinal', true); %! assert_equal (isequaln (B, X), true); %! assert_equal (I, [1, 2; 1, 2; 2, 1; 2, 1]); %!test %! X = [1, 2, 2; 1, 3, 1; 2, 4, 2; 2, 1, NaN; 2, NaN, 3; 1, 3, 2]; %! C = categorical (X, [3, 2, 1, 4], {'a', 'b', 'c', 'd'}, 'Ordinal', true); %! [B, I] = sort (C); %! Xs = [2, 3, 3; 2, 3, 2; 2, 2, 2; 1, 1, 2; 1, 4, 1; 1, NaN, NaN]; %! Cs = categorical (Xs, [3, 2, 1, 4], {'a', 'b', 'c', 'd'}, 'Ordinal', true); %! assert_equal (isequaln (B, Cs), true); %! assert_equal (I, [3, 2, 5; 4, 6, 1; 5, 1, 3; 1, 4, 6; 2, 3, 2; 6, 5, 4]); %!test %! X = [1, 2, 2; 1, 3, 1; 2, 4, 2; 2, 1, NaN; 2, NaN, 3; 1, 3, 2]; %! C = categorical (X, [3, 2, 1, 4], {'a', 'b', 'c', 'd'}, 'Ordinal', true); %! [B, I] = sort (C, 1); %! Xs = [2, 3, 3; 2, 3, 2; 2, 2, 2; 1, 1, 2; 1, 4, 1; 1, NaN, NaN]; %! Cs = categorical (Xs, [3, 2, 1, 4], {'a', 'b', 'c', 'd'}, 'Ordinal', true); %! assert_equal (isequaln (B, Cs), true); %! assert_equal (I, [3, 2, 5; 4, 6, 1; 5, 1, 3; 1, 4, 6; 2, 3, 2; 6, 5, 4]); %!test %! X = [1, 2, 2; 1, 3, 1; 2, 4, 2; 2, 1, NaN; 2, NaN, 3; 1, 3, 2]; %! C = categorical (X, [3, 2, 1, 4], {'a', 'b', 'c', 'd'}, 'Ordinal', true); %! [B, I] = sort (C, 2); %! Xs = [2, 2, 1; 3, 1, 1; 2, 2, 4; 2, 1, NaN; 3, 2, NaN; 3, 2, 1]; %! Cs = categorical (Xs, [3, 2, 1, 4], {'a', 'b', 'c', 'd'}, 'Ordinal', true); %! assert_equal (isequaln (B, Cs), true); %! assert_equal (I, [2, 3, 1; 2, 1, 3; 1, 3, 2; 1, 2, 3; 3, 1, 2; 2, 3, 1]); %!test %! X = [1, 2, 2; 1, 3, 1; 2, 4, 2; 2, 1, NaN; 2, NaN, 3; 1, 3, 2]; %! C = categorical (X, [3, 2, 1, 4], {'a', 'b', 'c', 'd'}, 'Ordinal', true); %! [B, I] = sort (C, 1, 'MissingPlacement', 'first'); %! Xs = [2, NaN, NaN; 2, 3, 3; 2, 3, 2; 1, 2, 2; 1, 1, 2; 1, 4, 1]; %! Cs = categorical (Xs, [3, 2, 1, 4], {'a', 'b', 'c', 'd'}, 'Ordinal', true); %! assert_equal (isequaln (B, Cs), true); %! assert_equal (I, [3, 5, 4; 4, 2, 5; 5, 6, 1; 1, 1, 3; 2, 4, 6; 6, 3, 2]); %!test %! X = [1, 2, 2; 1, 3, 1; 2, 4, 2; 2, 1, NaN; 2, NaN, 3; 1, 3, 2]; %! C = categorical (X, [3, 2, 1, 4], {'a', 'b', 'c', 'd'}, 'Ordinal', true); %! [B, I] = sort (C, 2, 'MissingPlacement', 'first'); %! Xs = [2, 2, 1; 3, 1, 1; 2, 2, 4; NaN, 2, 1; NaN, 3, 2; 3, 2, 1]; %! Cs = categorical (Xs, [3, 2, 1, 4], {'a', 'b', 'c', 'd'}, 'Ordinal', true); %! assert_equal (isequaln (B, Cs), true); %! assert_equal (I, [2, 3, 1; 2, 1, 3; 1, 3, 2; 3, 1, 2; 2, 3, 1; 2, 3, 1]); %!test %! X = [1, 2, 2; 1, 3, 1; 2, 4, 2; 2, 1, NaN; 2, NaN, 3; 1, 3, 2]; %! C = categorical (X, [3, 2, 1, 4], {'a', 'b', 'c', 'd'}, 'Ordinal', true); %! [B1, I1] = sort (C, 1); %! [B2, I2] = sort (C, 1, 'MissingPlacement', 'last'); %! assert_equal (isequaln (B1, B2), true); %! assert_equal (I1, I2); %!test %! X = [1, 2, 2; 1, 3, 1; 2, 4, 2; 2, 1, NaN; 2, NaN, 3; 1, 3, 2]; %! C = categorical (X, [3, 2, 1, 4], {'a', 'b', 'c', 'd'}, 'Ordinal', true); %! [B1, I1] = sort (C, 2); %! [B2, I2] = sort (C, 2, 'MissingPlacement', 'last'); %! assert_equal (isequaln (B1, B2), true); %! assert_equal (I1, I2); %!test %! X = [1, 2, 2; 1, 3, 1; 2, 4, 2; 2, 1, NaN; 2, NaN, 3; 1, 3, 2]; %! C = categorical (X, [3, 2, 1, 4], {'a', 'b', 'c', 'd'}, 'Ordinal', true); %! [B, I] = sort (C, 'descend'); %! Xs = [1, NaN, NaN; 1, 4, 1; 1, 1, 2; 2, 2, 2; 2, 3, 2; 2, 3, 3]; %! Cs = categorical (Xs, [3, 2, 1, 4], {'a', 'b', 'c', 'd'}, 'Ordinal', true); %! assert_equal (isequaln (B, Cs), true); %! assert_equal (I, [1, 5, 4; 2, 3, 2; 6, 4, 1; 3, 1, 3; 4, 2, 6; 5, 6, 5]); %!test %! X = [1, 2, 2; 1, 3, 1; 2, 4, 2; 2, 1, NaN; 2, NaN, 3; 1, 3, 2]; %! C = categorical (X, [3, 2, 1, 4], {'a', 'b', 'c', 'd'}, 'Ordinal', true); %! [B, I] = sort (C, 1, 'descend'); %! Xs = [1, NaN, NaN; 1, 4, 1; 1, 1, 2; 2, 2, 2; 2, 3, 2; 2, 3, 3]; %! Cs = categorical (Xs, [3, 2, 1, 4], {'a', 'b', 'c', 'd'}, 'Ordinal', true); %! assert_equal (isequaln (B, Cs), true); %! assert_equal (I, [1, 5, 4; 2, 3, 2; 6, 4, 1; 3, 1, 3; 4, 2, 6; 5, 6, 5]); %!test %! X = [1, 2, 2; 1, 3, 1; 2, 4, 2; 2, 1, NaN; 2, NaN, 3; 1, 3, 2]; %! C = categorical (X, [3, 2, 1, 4], {'a', 'b', 'c', 'd'}, 'Ordinal', true); %! [B, I] = sort (C, 2, 'descend'); %! Xs = [1, 2, 2; 1, 1, 3; 4, 2, 2; NaN, 1, 2; NaN, 2, 3; 1, 2, 3]; %! Cs = categorical (Xs, [3, 2, 1, 4], {'a', 'b', 'c', 'd'}, 'Ordinal', true); %! assert_equal (isequaln (B, Cs), true); %! assert_equal (I, [1, 2, 3; 1, 3, 2; 2, 1, 3; 3, 2, 1; 2, 1, 3; 1, 3, 2]); %!test %! X = [1, 2, 2; 1, 3, 1; 2, 4, 2; 2, 1, NaN; 2, NaN, 3; 1, 3, 2]; %! C = categorical (X, [3, 2, 1, 4], {'a', 'b', 'c', 'd'}, 'Ordinal', true); %! [B1, I1] = sort (C, 1, 'descend'); %! [B2, I2] = sort (C, 1, 'descend', 'MissingPlacement', 'first'); %! assert_equal (isequaln (B1, B2), true); %! assert_equal (I1, I2); %!test %! X = [1, 2, 2; 1, 3, 1; 2, 4, 2; 2, 1, NaN; 2, NaN, 3; 1, 3, 2]; %! C = categorical (X, [3, 2, 1, 4], {'a', 'b', 'c', 'd'}, 'Ordinal', true); %! [B1, I1] = sort (C, 2, 'descend'); %! [B2, I2] = sort (C, 2, 'descend', 'MissingPlacement', 'first'); %! assert_equal (isequaln (B1, B2), true); %! assert_equal (I1, I2); %!test %! X = [1, 2, 2; 1, 3, 1; 2, 4, 2; 2, 1, NaN; 2, NaN, 3; 1, 3, 2]; %! C = categorical (X, [3, 2, 1, 4], {'a', 'b', 'c', 'd'}, 'Ordinal', true); %! [B, I] = sort (C, 1, 'descend', 'MissingPlacement', 'last'); %! Xs = [1, 4, 1; 1, 1, 2; 1, 2, 2; 2, 3, 2; 2, 3, 3; 2, NaN, NaN]; %! Cs = categorical (Xs, [3, 2, 1, 4], {'a', 'b', 'c', 'd'}, 'Ordinal', true); %! assert_equal (isequaln (B, Cs), true); %! assert_equal (I, [1, 3, 2; 2, 4, 1; 6, 1, 3; 3, 2, 6; 4, 6, 5; 5, 5, 4]); %!test %! X = [1, 2, 2; 1, 3, 1; 2, 4, 2; 2, 1, NaN; 2, NaN, 3; 1, 3, 2]; %! C = categorical (X, [3, 2, 1, 4], {'a', 'b', 'c', 'd'}, 'Ordinal', true); %! [B, I] = sort (C, 2, 'descend', 'MissingPlacement', 'last'); %! Xs = [1, 2, 2; 1, 1, 3; 4, 2, 2; 1, 2, NaN; 2, 3, NaN; 1, 2, 3]; %! Cs = categorical (Xs, [3, 2, 1, 4], {'a', 'b', 'c', 'd'}, 'Ordinal', true); %! assert_equal (isequaln (B, Cs), true); %! assert_equal (I, [1, 2, 3; 1, 3, 2; 2, 1, 3; 2, 1, 3; 1, 3, 2; 1, 3, 2]); %!error ... %! sort (categorical, 'MissingPlacement', 'on') %!test %! C = categorical ([1, 2; 1, 3; 2, -4; 2, 1]); %! [B, I] = sortrows (C); %! assert_equal (B, categorical ([1, 2; 1, 3; 2, -4; 2, 1])); %! assert_equal (I, [1; 2; 3; 4]); %!test %! C = categorical ([1, 2; 1, 3; 2, -4; 2, 1]); %! [B, I] = sortrows (C, 1); %! assert_equal (B, categorical ([1, 2; 1, 3; 2, -4; 2, 1])); %! assert_equal (I, [1; 2; 3; 4]); %!test %! C = categorical ([1, 2; 1, 3; 2, -4; 2, 1]); %! [B, I] = sortrows (C, -1); %! assert_equal (B, categorical ([2, -4; 2, 1; 1, 2; 1, 3])); %! assert_equal (I, [3; 4; 1; 2]); %!test %! C = categorical ([1, 2; 1, 3; 2, -4; 2, 1]); %! [B, I] = sortrows (C, 2); %! assert_equal (B, categorical ([2, -4; 2, 1; 1, 2; 1, 3])); %! assert_equal (I, [3; 4; 1; 2]); %!test %! C = categorical ([1, 2; 1, 3; 2, -4; 2, 1]); %! [B, I] = sortrows (C, -2); %! assert_equal (B, categorical ([1, 3; 1, 2; 2, 1; 2, -4])); %! assert_equal (I, [2; 1; 4; 3]); %!test %! C = categorical ([1, 2; 1, 3; 2, -4; 2, 1]); %! [B, I] = sortrows (C, [1, -2]); %! assert_equal (B, categorical ([1, 3; 1, 2; 2, 1; 2, -4])); %! assert_equal (I, [2; 1; 4; 3]); %!test %! C = categorical ([1, 2; 1, 3; 2, -4; 2, 1]); %! [B, I] = sortrows (C, [-1, -2]); %! assert_equal (B, categorical ([2, 1; 2, -4; 1, 3; 1, 2])); %! assert_equal (I, [4; 3; 2; 1]); %!test %! C = categorical ([1, 2, 2; 1, 3, 1; 2, 4, 2; 2, 1, NaN; 2, NaN, 3; 1, 3, 2]); %! [B, I] = sortrows (C); %! X = [1, 2, 2; 1, 3, 1; 1, 3, 2; 2, 1, NaN; 2, 4, 2; 2, NaN, 3]; %! assert_equal (isequaln (B, categorical (X)), true); %! assert_equal (I, [1; 2; 6; 4; 3; 5]); %!test %! C = categorical ([1, 2, 2; 1, 3, 1; 2, 4, 2; 2, 1, NaN; 2, NaN, 3; 1, 3, 2]); %! [B, I] = sortrows (C, 'MissingPlacement', 'first'); %! X = [1, 2, 2; 1, 3, 1; 1, 3, 2; 2, NaN, 3; 2, 1, NaN; 2, 4, 2]; %! assert_equal (isequaln (B, categorical (X)), true); %! assert_equal (I, [1; 2; 6; 5; 4; 3]); %!test %! C = categorical ([1, 2, 2; 1, 3, 1; 2, 4, 2; 2, 1, NaN; 2, NaN, 3; 1, 3, 2]); %! [B, I] = sortrows (C, 'MissingPlacement', 'last'); %! X = [1, 2, 2; 1, 3, 1; 1, 3, 2; 2, 1, NaN; 2, 4, 2; 2, NaN, 3]; %! assert_equal (isequaln (B, categorical (X)), true); %! assert_equal (I, [1; 2; 6; 4; 3; 5]); %!test %! C = categorical ([1, 2, 2; 1, 3, 1; 2, 4, 2; 2, 1, NaN; 2, NaN, 3; 1, 3, 2]); %! [B, I] = sortrows (C, [-1, -2], 'MissingPlacement', 'first'); %! X = [2, NaN, 3; 2, 4, 2; 2, 1, NaN; 1, 3, 1; 1, 3, 2; 1, 2, 2]; %! assert_equal (isequaln (B, categorical (X)), true); %! assert_equal (I, [5; 3; 4; 2; 6; 1]); %!test %! C = categorical ([1, 2, 2; 1, 3, 1; 2, 4, 2; 2, 1, NaN; 2, NaN, 3; 1, 3, 2]); %! [B, I] = sortrows (C, [-1, -2], 'MissingPlacement', 'last'); %! X = [2, 4, 2; 2, 1, NaN; 2, NaN, 3; 1, 3, 1; 1, 3, 2; 1, 2, 2]; %! assert_equal (isequaln (B, categorical (X)), true); %! assert_equal (I, [3; 4; 5; 2; 6; 1]); %!test %! C = categorical ([1, 2, 2; 1, 3, 1; 2, 4, 2; 2, 1, NaN; 2, NaN, 3; 1, 3, 2]); %! [B, I] = sortrows (C, 2, 'MissingPlacement', 'first'); %! X = [2, NaN, 3; 2, 1, NaN; 1, 2, 2; 1, 3, 1; 1, 3, 2; 2, 4, 2]; %! assert_equal (isequaln (B, categorical (X)), true); %! assert_equal (I, [5; 4; 1; 2; 6; 3]); %!test %! C = categorical ([1, 2, 2; 1, 3, 1; 2, 4, 2; 2, 1, NaN; 2, NaN, 3; 1, 3, 2]); %! [B, I] = sortrows (C, 2, 'MissingPlacement', 'last'); %! X = [2, 1, NaN; 1, 2, 2; 1, 3, 1; 1, 3, 2; 2, 4, 2; 2, NaN, 3]; %! assert_equal (isequaln (B, categorical (X)), true); %! assert_equal (I, [4; 1; 2; 6; 3; 5]); %!test %! C = categorical ([1, 2, 2; 1, 3, 1; 2, 4, 2; 2, 1, NaN; 2, NaN, 3; 1, 3, 2]); %! [B, I] = sortrows (C, -2, 'MissingPlacement', 'first'); %! X = [2, NaN, 3; 2, 4, 2; 1, 3, 1; 1, 3, 2; 1, 2, 2; 2, 1, NaN]; %! assert_equal (isequaln (B, categorical (X)), true); %! assert_equal (I, [5; 3; 2; 6; 1; 4]); %!test %! C = categorical ([1, 2, 2; 1, 3, 1; 2, 4, 2; 2, 1, NaN; 2, NaN, 3; 1, 3, 2]); %! [B, I] = sortrows (C, -2, 'MissingPlacement', 'last'); %! X = [2, 4, 2; 1, 3, 1; 1, 3, 2; 1, 2, 2; 2, 1, NaN; 2, NaN, 3]; %! assert_equal (isequaln (B, categorical (X)), true); %! assert_equal (I, [3; 2; 6; 1; 4; 5]); %!test %! C = categorical ([1, 2, 2; 1, 3, 1; 2, 4, 2; 2, 1, NaN; 2, NaN, 3; 1, 3, 2]); %! [B, I] = sortrows (C, [1, -2, 3]); %! X = [1, 3, 1; 1, 3, 2; 1, 2, 2; 2, NaN, 3; 2, 4, 2; 2, 1, NaN]; %! assert_equal (isequaln (B, categorical (X)), true); %! assert_equal (I, [2; 6; 1; 5; 3; 4]); %!test %! C = categorical ([1, 2, 2; 1, 3, 1; 2, 4, 2; 2, 1, NaN; 2, NaN, 3; 1, 3, 2]); %! [B, I] = sortrows (C, [-1, -2, 3]); %! X = [2, NaN, 3; 2, 4, 2; 2, 1, NaN; 1, 3, 1; 1, 3, 2; 1, 2, 2]; %! assert_equal (isequaln (B, categorical (X)), true); %! assert_equal (I, [5; 3; 4; 2; 6; 1]); %!test %! C = categorical ([1, 2, 2; 1, 3, 1; 2, 4, 2; 2, 1, NaN; 2, NaN, 3; 1, 3, 2]); %! [B, I] = sortrows (C, [1, -2], {'ascend', 'descend'}); %! X = [1, 3, 1; 1, 3, 2; 1, 2, 2; 2, NaN, 3; 2, 4, 2; 2, 1, NaN]; %! assert_equal (isequaln (B, categorical (X)), true); %! assert_equal (I, [2; 6; 1; 5; 3; 4]); %!test %! C = categorical ([1, 2, 2; 1, 3, 1; 2, 4, 2; 2, 1, NaN; 2, NaN, 3; 1, 3, 2]); %! [B, I] = sortrows (C, [1, -2], {'descend', 'ascend'}); %! X = [2, 1, NaN; 2, 4, 2; 2, NaN, 3; 1, 2, 2; 1, 3, 1; 1, 3, 2]; %! assert_equal (isequaln (B, categorical (X)), true); %! assert_equal (I, [4; 3; 5; 1; 2; 6]); %!test %! C = categorical ([1, 2, 2; 1, 3, 1; 2, 4, 2; 2, 1, NaN; 2, NaN, 3; 1, 3, 2]); %! [B, I] = sortrows (C, [-1, -2, 3], {'ascend', 'ascend', 'descend'}); %! X = [1, 2, 2; 1, 3, 2; 1, 3, 1; 2, 1, NaN; 2, 4, 2; 2, NaN, 3]; %! assert_equal (isequaln (B, categorical (X)), true); %! assert_equal (I, [1; 6; 2; 4; 3; 5]); %!test %! C = categorical ([1, 2, 2; 1, 3, 1; 2, 4, 2; 2, 1, NaN; 2, NaN, 3; 1, 3, 2]); %! [B, I] = sortrows (C, [1, -2, 3], {'ascend', 'ascend', 'descend'}); %! X = [1, 2, 2; 1, 3, 2; 1, 3, 1; 2, 1, NaN; 2, 4, 2; 2, NaN, 3]; %! assert_equal (isequaln (B, categorical (X)), true); %! assert_equal (I, [1; 6; 2; 4; 3; 5]); %!test %! C = categorical ([1, 2, 2; 1, 3, 1; 2, 4, 2; 2, 1, NaN; 2, NaN, 3; 1, 3, 2]); %! [B, I] = sortrows (C, [1, -2, 3], 'MissingPlacement', 'first'); %! X = [1, 3, 1; 1, 3, 2; 1, 2, 2; 2, NaN, 3; 2, 4, 2; 2, 1, NaN]; %! assert_equal (isequaln (B, categorical (X)), true); %! assert_equal (I, [2; 6; 1; 5; 3; 4]); %!test %! C = categorical ([1, 2, 2; 1, 3, 1; 2, 4, 2; 2, 1, NaN; 2, NaN, 3; 1, 3, 2]); %! [B, I] = sortrows (C, [-1, -2, 3], 'MissingPlacement', 'first'); %! X = [2, NaN, 3; 2, 4, 2; 2, 1, NaN; 1, 3, 1; 1, 3, 2; 1, 2, 2]; %! assert_equal (isequaln (B, categorical (X)), true); %! assert_equal (I, [5; 3; 4; 2; 6; 1]); %!test %! C = categorical ([1, 2, 2; 1, 3, 1; 2, 4, 2; 2, 1, NaN; 2, NaN, 3; 1, 3, 2]); %! [B, I] = sortrows (C, [1, -2, -3], 'MissingPlacement', 'first'); %! X = [1, 3, 2; 1, 3, 1; 1, 2, 2; 2, NaN, 3; 2, 4, 2; 2, 1, NaN]; %! assert_equal (isequaln (B, categorical (X)), true); %! assert_equal (I, [6; 2; 1; 5; 3; 4]); %!test %! C = categorical ([1, 2, 2; 1, 3, 1; 2, 4, 2; 2, 1, NaN; 2, NaN, 3; 1, 3, 2]); %! [B, I] = sortrows (C, [1, -2, 3], 'MissingPlacement', 'last'); %! X = [1, 3, 1; 1, 3, 2; 1, 2, 2; 2, 4, 2; 2, 1, NaN; 2, NaN, 3]; %! assert_equal (isequaln (B, categorical (X)), true); %! assert_equal (I, [2; 6; 1; 3; 4; 5]); %!test %! C = categorical ([1, 2, 2; 1, 3, 1; 2, 4, 2; 2, 1, NaN; 2, NaN, 3; 1, 3, 2]); %! [B, I] = sortrows (C, [-1, -2, 3], 'MissingPlacement', 'last'); %! X = [2, 4, 2; 2, 1, NaN; 2, NaN, 3; 1, 3, 1; 1, 3, 2; 1, 2, 2]; %! assert_equal (isequaln (B, categorical (X)), true); %! assert_equal (I, [3; 4; 5; 2; 6; 1]); %!test %! C = categorical ([1, 2, 2; 1, 3, 1; 2, 4, 2; 2, 1, NaN; 2, NaN, 3; 1, 3, 2]); %! [B, I] = sortrows (C, [1, -2, -3], 'MissingPlacement', 'last'); %! X = [1, 3, 2; 1, 3, 1; 1, 2, 2; 2, 4, 2; 2, 1, NaN; 2, NaN, 3]; %! assert_equal (isequaln (B, categorical (X)), true); %! assert_equal (I, [6; 2; 1; 3; 4; 5]); %!test %! C = categorical ([1, 2, 3; 2, 3, 4], [4, 3, 2, 1], 'Ordinal', true); %! [B, I] = sortrows (C); %! out = categorical ([2, 3, 4; 1, 2, 3], [4, 3, 2, 1], 'Ordinal', true); %! assert_equal (B, out); %! assert_equal (I, [2; 1]); %!test %! C = categorical ([1, 2, 3; 2, 3, 4], [1, 2, 3, 4], 'Ordinal', true); %! [B, I] = sortrows (C); %! out = categorical ([1, 2, 3; 2, 3, 4], [1, 2, 3, 4], 'Ordinal', true); %! assert_equal (B, out); %! assert_equal (I, [1; 2]); %!test %! C = categorical ([1, 2, 3; 2, 3, 4; 1, 2, NaN], [1:4], 'Ordinal', true); %! [B, I] = sortrows (C); %! assert_equal (double (B), [1, 2, 3; 1, 2, NaN; 2, 3, 4]); %! assert_equal (I, [1; 3; 2]); %!test %! C = categorical ([1, 2, 3; 2, 3, 4; 1, 2, NaN], [1:4], 'Ordinal', true); %! [B, I] = sortrows (C, 'MissingPlacement', 'first'); %! assert_equal (double (B), [1, 2, NaN; 1, 2, 3; 2, 3, 4]); %! assert_equal (I, [3; 1; 2]); %!test %! C = categorical ([1, 2, 3; 2, 3, 4; 1, 2, NaN], [1:4], 'Ordinal', true); %! [B, I] = sortrows (C, 'descend'); %! assert_equal (double (B), [2, 3, 4; 1, 2, NaN; 1, 2, 3]); %! assert_equal (I, [2; 3; 1]); %!test %! C = categorical ([1, 2, 3; 2, 3, 4; 1, 2, NaN], [1:4], 'Ordinal', true); %! [B, I] = sortrows (C, 'descend', 'MissingPlacement', 'last'); %! assert_equal (double (B), [2, 3, 4; 1, 2, 3; 1, 2, NaN]); %! assert_equal (I, [2; 1; 3]); %!error ... %! sortrows (categorical (ones (2, 3, 4))) %!error ... %! sortrows (categorical (ones (2)), 'MissingPlacement', 2) %!error ... %! sortrows (categorical (1), 1, 'ascend', 2) %!error ... %! sortrows (categorical (1), 1.5) %!error ... %! sortrows (categorical (1), ones (2)) %!error ... %! sortrows (categorical (1), 2) %!error ... %! sortrows (categorical (1), {'a'}) %!error ... %! sortrows (categorical (1), 1, {'ascend', 'descend'}) %!error ... %! sortrows (categorical (1), {1}) %!error ... %! sortrows (categorical (1), {'ascend'}, 'ascend') %!error ... %! sortrows (categorical (1), {'ascend'}, 1) %!error ... %! sortrows (categorical (1), 1, 1) %!error ... %! sortrows (categorical (1), 1, {'a'}) %!error ... %! sortrows (categorical (1), 1, {'ascend', 'descend'}) %!test %! C = categorical ([1, 2; 1, 3; 2, -4; 2, 1]); %! [B, I] = topkrows (C, 2); %! assert_equal (B, categorical ([2, 1; 2, -4])); %! assert_equal (I, [4; 3]); %!test %! C = categorical ([1, 2; 1, 3; 2, -4; 2, 1]); %! [B, I] = topkrows (C, 5); %! assert_equal (B, sortrows (C, 'descend')); %!test %! C = categorical ([1, 2; 1, 3; 2, -4; 2, 1]); %! [B, I] = topkrows (C, 2, 'ascend'); %! assert_equal (B, categorical ([1, 2; 1, 3])); %! assert_equal (I, [1; 2]); %!test %! C = categorical ([1, 2; 1, 3; 2, -4; 2, 1]); %! [B, I] = topkrows (C, 2, -1); %! assert_equal (B, categorical ([2, -4; 2, 1])); %! assert_equal (I, [3; 4]); %!test %! C = categorical ([1, 2; 1, 3; 2, -4; 2, 1]); %! [B, I] = topkrows (C, 2, 2); %! assert_equal (B, categorical ([2, -4; 2, 1])); %! assert_equal (I, [3; 4]); %!test %! C = categorical ([1, 2; 1, 3; 2, -4; 2, 1]); %! [B, I] = topkrows (C, 2, -2); %! assert_equal (B, categorical ([1, 3; 1, 2])); %! assert_equal (I, [2; 1]); %!test %! C = categorical ([1, 2; 1, 3; 2, -4; 2, 1]); %! [B, I] = topkrows (C, 2, [1, -2]); %! assert_equal (B, categorical ([1, 3; 1, 2])); %! assert_equal (I, [2; 1]); %!test %! C = categorical ([1, 2; 1, 3; 2, -4; 2, 1]); %! [B, I] = topkrows (C, 2, [-1, -2]); %! assert_equal (B, categorical ([2, 1; 2, -4])); %! assert_equal (I, [4; 3]); %!test %! C = categorical ([1, 2, 2; 1, 3, 1; 2, 4, 2; 2, 1, NaN; 2, NaN, 3; 1, 3, 2]); %! [B, I] = topkrows (C, 3); %! assert_equal (double (B), [2, NaN, 3; 2, 4, 2; 2, 1, NaN]); %! assert_equal (I, [5; 3; 4]); %!test %! C = categorical ([1, 2, 2; 1, 3, 1; 2, 4, 2; 2, 1, NaN; 2, NaN, 3; 1, 3, 2]); %! [B, I] = topkrows (C, 3, 'ascend'); %! assert_equal (B, categorical ([1, 2, 2; 1, 3, 1; 1, 3, 2])); %! assert_equal (I, [1; 2; 6]); %!error ... %! topkrows (categorical (1)) %!error ... %! topkrows (categorical (1), 0) %!assert_equal (unique (categorical ([1, 2, 2, 3, 4, 4])), categorical ([1:4])) %!assert_equal (unique (categorical ([1, 2, 2, 3, 4, 4]')), categorical ([1:4]')) %!test %! C = categorical ([1, 2, 2, 3, 4, 4]); %! [c, i, j] = unique (C); %! assert_equal (c, C(i)); %! assert_equal (c(j), C); %!assert_equal (unique (categorical ([1, 4, 2, 2, 3]), 'sorted'), categorical (1:4)) %!test %! C = categorical ([1, 4, 2, 2, 3]); %! [c, i, j] = unique (C, 'sorted'); %! assert_equal (c, categorical (1:4)); %! assert_equal (c, C(i)); %! assert_equal (c(j), C); %!test %! C = categorical ([1, 4, 2, 2, 3]); %! [c, i, j] = unique (C, 'stable'); %! assert_equal (c, categorical ([1, 4, 2, 3])); %! assert_equal (c, C(i)); %! assert_equal (c(j), C); %!test %! C = categorical ([1, 4, 2, 2, 3]); %! [c, i, j] = unique (C, 'sorted', 'last'); %! assert_equal (i, [1; 4; 5; 2]); %! assert_equal (c, C(i)); %! assert_equal (c(j), C); %!test %! C = categorical ([1, 4, 2, 2, 3]); %! [c, i, j] = unique (C, 'stable', 'last'); %! assert_equal (i, [1; 2; 4; 5]); %! assert_equal (c, C(i)); %! assert_equal (c(j), C); %!test %! C = categorical ([3, 1, 3; 3, 3, 1; 1, 3, 3; 3, 2, 3; 2, 3, 3; ... %! 1, 1, 3; 1, 2, 3; 2, 3, 2; 3, 3, 2; 3, 3, 1]); %! [c, i, j] = unique (C(:,[1:2]), 'rows'); %! assert_equal (c, categorical ([1, 1; 1, 2; 1, 3; 2, 3; 3, 1; 3, 2; 3, 3])); %! assert_equal (i, [6; 7; 3; 5; 1; 4; 2]); %! assert_equal (j, [5; 7; 3; 6; 4; 1; 2; 4; 7; 7]); %! assert_equal (C(i,[1:2]), categorical ([1, 1; 1, 2; 1, 3; 2, 3; 3, 1; 3, 2; 3, 3])); %!test %! [B, ia, ib] = unique (categorical ([1, 2; 2, 3; 1, 2]), 'rows', 'sorted', 'first'); %! assert_equal (B, categorical ([1, 2; 2, 3])); %! assert_equal (ia, [1; 2]); %! assert_equal (ib, [1; 2; 1]); %!test %! [B, ia, ib] = unique (categorical ([1, 2; 2, 3; 1, 2]), 'rows', 'sorted', 'last'); %! assert_equal (B, categorical ([1, 2; 2, 3])); %! assert_equal (ia, [3; 2]); %! assert_equal (ib, [1; 2; 1]); %!test %! [B, ia, ib] = unique (categorical ([1, 2; 2, 3; 1, 2]), 'rows', 'stable', 'first'); %! assert_equal (B, categorical ([1, 2; 2, 3])); %! assert_equal (ia, [1; 2]); %! assert_equal (ib, [1; 2; 1]); %!test %! [B, ia, ib] = unique (categorical ([1, 2; 2, 3; 1, 2]), 'rows', 'stable', 'last'); %! assert_equal (B, categorical ([2, 3; 1, 2])); %! assert_equal (ia, [2; 3]); %! assert_equal (ib, [2; 1; 2]); %!error ... %! unique (categorical ([1:3]), 'legacy') %!test %! A = categorical ([7 1 7 7 4]); %! B = categorical ([7 0 4 4 0]); %! [C, ixA, ixB] = intersect (A, B); %! assert_equal (C, categorical ([4, 7])); %! assert_equal (ixA, [5; 1]); %! assert_equal (ixB, [3; 1]); %!test %! A = categorical ([7 1 7 7 4]); %! B = categorical ([7 0 4 4 0]); %! [C, ixA, ixB] = intersect (A, B, 'sorted'); %! assert_equal (C, categorical ([4, 7])); %! assert_equal (ixA, [5; 1]); %! assert_equal (ixB, [3; 1]); %!test %! A = categorical ([7 1 7 7 4]); %! B = categorical ([7 0 4 4 0]); %! [C, ixA, ixB] = intersect (A, B, 'stable'); %! assert_equal (C, categorical ([7, 4])); %! assert_equal (ixA, [1; 5]); %! assert_equal (ixB, [1; 3]); %!test %! A = categorical ([2, 2, 2; 0, 0, 1; 1, 2, 3; 1, 1, 1]); %! B = categorical ([1, 2, 3; 2, 2, 2; 2, 2, 0]); %! [C, ixA, ixB] = intersect (A, B, 'rows'); %! assert_equal (C, categorical ([1, 2, 3; 2, 2, 2])); %! assert_equal (ixA, [3; 1]); %! assert_equal (ixB, [1; 2]); %!test %! A = categorical ([2, 2, 2; 0, 0, 1; 1, 2, 3; 1, 1, 1]); %! B = categorical ([1, 2, 3; 2, 2, 2; 2, 2, 0]); %! [C, ixA, ixB] = intersect (A, B, 'sorted', 'rows'); %! assert_equal (C, categorical ([1, 2, 3; 2, 2, 2])); %! assert_equal (ixA, [3; 1]); %! assert_equal (ixB, [1; 2]); %!test %! A = categorical ([2, 2, 2; 0, 0, 1; 1, 2, 3; 1, 1, 1]); %! B = categorical ([1, 2, 3; 2, 2, 2; 2, 2, 0]); %! [C, ixA, ixB] = intersect (A, B, 'stable', 'rows'); %! assert_equal (C, categorical ([2, 2, 2; 1, 2, 3])); %! assert_equal (ixA, [1; 3]); %! assert_equal (ixB, [2; 1]); %!assert_equal (intersect (categorical ([1, 2]), '2'), categorical (2, [1, 2])) %!assert_equal (intersect (categorical ([1, 2]), {'1'}), categorical (1, [1, 2])) %!assert_equal (intersect (categorical ([1, 2]), string ('2')), categorical (2, [1, 2])) %!assert_equal (intersect ('2', categorical ([1, 2])), categorical (2, [1, 2])) %!assert_equal (intersect ({'1'}, categorical ([1, 2])), categorical (1, [1, 2])) %!assert_equal (intersect (string ('2'), categorical ([1, 2])), categorical (2, [1, 2])) %!error ... %! intersect (categorical, categorical, 'legacy') %!error ... %! intersect (categorical, 1) %!error ... %! intersect (categorical, categorical (1, 1, 'Ordinal', true)) %!error ... %! intersect (categorical (1, [1, 2], 'Ordinal', true), categorical (1, 1, 'Ordinal', true)) %!test %! A = categorical ([3, 6, 2, 1, 5, 1, 1]); %! B = categorical ([2, 4, 6]); %! [C, index] = setdiff (A, B); %! assert_equal (C, categorical ([1, 3, 5])); %! assert_equal (index, [4; 1; 5]); %!test %! A = categorical ([3, 6, 2, 1, 5, 1, 1]); %! B = categorical ([2, 4, 6]); %! [C, index] = setdiff (A, B, 'sorted'); %! assert_equal (C, categorical ([1, 3, 5])); %! assert_equal (index, [4; 1; 5]); %!test %! A = categorical ([3, 6, 2, 1, 5, 1, 1]); %! B = categorical ([2, 4, 6]); %! [C, index] = setdiff (A, B, 'stable'); %! assert_equal (C, categorical ([3, 1, 5])); %! assert_equal (index, [1; 4; 5]); %!test %! A = categorical ([7, 9, 7; 0, 0, 0; 7, 9, 7; 5, 5, 5; 1, 4, 5]); %! B = categorical ([0, 0, 0; 5, 5, 5]); %! [C, index] = setdiff (A, B, 'rows'); %! assert_equal (C, categorical ([1, 4, 5; 7, 9, 7])); %! assert_equal (index, [5; 1]); %!test %! A = categorical ([7, 9, 7; 0, 0, 0; 7, 9, 7; 5, 5, 5; 1, 4, 5]); %! B = categorical ([0, 0, 0; 5, 5, 5]); %! [C, index] = setdiff (A, B, 'sorted', 'rows'); %! assert_equal (C, categorical ([1, 4, 5; 7, 9, 7])); %! assert_equal (index, [5; 1]); %!test %! A = categorical ([7, 9, 7; 0, 0, 0; 7, 9, 7; 5, 5, 5; 1, 4, 5]); %! B = categorical ([0, 0, 0; 5, 5, 5]); %! [C, index] = setdiff (A, B, 'stable', 'rows'); %! assert_equal (C, categorical ([7, 9, 7; 1, 4, 5])); %! assert_equal (index, [1; 5]); %!assert_equal (setdiff (categorical ([1, 2]), '2'), categorical (1, [1, 2])) %!assert_equal (setdiff (categorical ([1, 2]), {'1'}), categorical (2, [1, 2])) %!assert_equal (setdiff (categorical ([1, 2]), string ('2')), categorical (1, [1, 2])) %!assert_equal (size (setdiff (categorical ([1, 2]), string ({'2', 1}))), [1, 0]) %!assert_equal (class (setdiff (categorical ([1, 2]), string ({'2', 1}))), 'categorical') %!assert_equal (size (setdiff ('2', categorical ([1, 2]))), [1, 0]) %!assert_equal (class (setdiff ('2', categorical ([1, 2]))), 'categorical') %!assert_equal (size (setdiff ({'1'}, categorical ([1, 2]))), [1, 0]) %!assert_equal (class (setdiff ({'1'}, categorical ([1, 2]))), 'categorical') %!assert_equal (size (setdiff (string ({'1', '2'}), categorical ([1, 2]))), [1, 0]) %!assert_equal (class (setdiff (string ({'1', '2'}), categorical ([1, 2]))), 'categorical') %!error ... %! setdiff (categorical, categorical, 'legacy') %!error ... %! setdiff (categorical, 1) %!error ... %! setdiff (categorical, categorical (1, 1, 'Ordinal', true)) %!error ... %! setdiff (categorical (1, [1, 2], 'Ordinal', true), categorical (1, 1, 'Ordinal', true)) %!test %! A = categorical ([5, 1, 3, 3, 3]); %! B = categorical ([4, 1, 2]); %! [C, ixA, ixB] = setxor (A, B); %! assert_equal (C, categorical ([2, 3, 4, 5])); %! assert_equal (ixA, [3; 1]); %! assert_equal (ixB, [3; 1]); %!test %! A = categorical ([5, 1, 3, 3, 3]); %! B = categorical ([4, 1, 2]); %! [C, ixA, ixB] = setxor (A, B, 'sorted'); %! assert_equal (C, categorical ([2, 3, 4, 5])); %! assert_equal (ixA, [3; 1]); %! assert_equal (ixB, [3; 1]); %!test %! A = categorical ([5, 1, 3, 3, 3]); %! B = categorical ([4, 1, 2]); %! [C, ixA, ixB] = setxor (A, B, 'stable'); %! assert_equal (C, categorical ([5, 3, 4, 2])); %! assert_equal (ixA, [1; 3]); %! assert_equal (ixB, [1; 3]); %!test %! A = categorical ([7, 8, 9; 7, 7, 1; 7, 7, 1; 1, 2, 3; 4, 5, 6]); %! B = categorical ([1, 2, 3; 4, 5, 6; 7, 7, 2]); %! [C, ixA, ixB] = setxor (A, B, 'rows'); %! assert_equal (C, categorical ([7, 7, 1; 7, 7, 2; 7, 8, 9])); %! assert_equal (ixA, [2; 1]); %! assert_equal (ixB, [3]); %!test %! A = categorical ([7, 8, 9; 7, 7, 1; 7, 7, 1; 1, 2, 3; 4, 5, 6]); %! B = categorical ([1, 2, 3; 4, 5, 6; 7, 7, 2]); %! [C, ixA, ixB] = setxor (A, B, 'sorted', 'rows'); %! assert_equal (C, categorical ([7, 7, 1; 7, 7, 2; 7, 8, 9])); %! assert_equal (ixA, [2; 1]); %! assert_equal (ixB, [3]); %!test %! A = categorical ([7, 8, 9; 7, 7, 1; 7, 7, 1; 1, 2, 3; 4, 5, 6]); %! B = categorical ([1, 2, 3; 4, 5, 6; 7, 7, 2]); %! [C, ixA, ixB] = setxor (A, B, 'stable', 'rows'); %! assert_equal (C, categorical ([7, 8 ,9; 7, 7, 1; 7, 7, 2])); %! assert_equal (ixA, [1; 2]); %! assert_equal (ixB, [3]); %!assert_equal (setxor (categorical ([1, 2]), '2'), categorical (1, [1, 2])) %!assert_equal (setxor (categorical ([1, 2]), {'1'}), categorical (2, [1, 2])) %!assert_equal (setxor (categorical ([1, 2]), string ('2')), categorical (1, [1, 2])) %!assert_equal (size (setxor (categorical ([1, 2]), string ({'2', '1'}))), [1, 0]) %!assert_equal (class (setxor (categorical ([1, 2]), string ({'2', '1'}))), 'categorical') %!assert_equal (setxor ('2', categorical ([1, 2])), categorical (1, [1, 2])) %!assert_equal (setxor ({'1'}, categorical ([1, 2])), categorical (2, [1, 2])) %!assert_equal (setxor (string ('2'), categorical ([1, 2])), categorical (1, [1, 2])) %!error ... %! setxor (categorical, categorical, 'legacy') %!error ... %! setxor (categorical, 1) %!error ... %! setxor (categorical, categorical (1, 1, 'Ordinal', true)) %!error ... %! setxor (categorical (1, [1, 2], 'Ordinal', true), categorical (1, 1, 'Ordinal', true)) %!test %! A = categorical ([5, 7, 1]); %! B = categorical ([3, 1, 1]); %! [C, ixA, ixB] = union (A, B); %! assert_equal (C, categorical ([1, 3, 5, 7])); %! assert_equal (ixA, [3; 1; 2]); %! assert_equal (ixB, 1); %!test %! A = categorical ([5, 7, 1]); %! B = categorical ([3, 1, 1]); %! [C, ixA, ixB] = union (A, B, 'sorted'); %! assert_equal (C, categorical ([1, 3, 5, 7])); %! assert_equal (ixA, [3; 1; 2]); %! assert_equal (ixB, 1); %!test %! A = categorical ([5, 7, 1]); %! B = categorical ([3, 1, 1]); %! [C, ixA, ixB] = union (A, B, 'stable'); %! assert_equal (C, categorical ([5, 7, 1, 3])); %! assert_equal (ixA, [1; 2; 3]); %! assert_equal (ixB, 1); %!test %! A = categorical ([2, 2, 2; 0, 0, 1]); %! B = categorical ([1, 2, 3; 2, 2, 2; 2, 2, 2]); %! [C, ixA, ixB] = union (A, B, 'rows'); %! assert_equal (C, categorical ([0, 0, 1; 1, 2, 3; 2, 2, 2])); %! assert_equal (ixA, [2; 1]); %! assert_equal (ixB, 1); %!test %! A = categorical ([2, 2, 2; 0, 0, 1]); %! B = categorical ([1, 2, 3; 2, 2, 2; 2, 2, 2]); %! [C, ixA, ixB] = union (A, B, 'sorted', 'rows'); %! assert_equal (C, categorical ([0, 0, 1; 1, 2, 3; 2, 2, 2])); %! assert_equal (ixA, [2; 1]); %! assert_equal (ixB, 1); %!test %! A = categorical ([2, 2, 2; 0, 0, 1]); %! B = categorical ([1, 2, 3; 2, 2, 2; 2, 2, 2]); %! [C, ixA, ixB] = union (A, B, 'stable', 'rows'); %! assert_equal (C, categorical ([2, 2, 2; 0, 0, 1; 1, 2, 3])); %! assert_equal (ixA, [1; 2]); %! assert_equal (ixB, 1); %!assert_equal (union (categorical ([1, 2]), '2'), categorical ([1, 2], [1, 2])) %!assert_equal (union (categorical ([1, 2]), {'1'}), categorical ([1, 2], [1, 2])) %!assert_equal (union (categorical ([1, 2]), string ('3')), categorical ([1, 2, 3])) %!assert_equal (union ('2', categorical ([1, 2])), categorical ([1, 2])) %!assert_equal (union ({'1'}, categorical ([1, 2])), categorical ([1, 2])) %!assert_equal (union (string ('4'), categorical ([1, 2])), categorical ([1, 2, 4])) %!assert_equal (union (string ({'4', '5'}), categorical ([1, 2])), categorical ([1, 2, 4, 5])) %!error ... %! union (categorical, categorical, 'legacy') %!error ... %! union (categorical, 1) %!error ... %! union (categorical, categorical (1, 1, 'Ordinal', true)) %!error ... %! union (categorical (1, [1, 2], 'Ordinal', true), categorical (1, 1, 'Ordinal', true)) ################################################################################ ## ** Array Operations ** ## ################################################################################ ## Available Methods ## ## ## ## 'cat' 'horzcat' 'vertcat' 'repmat' ## ## 'repelem' 'repelems' 'reshape' 'circshift' ## ## 'permute' 'ipermute' 'transpose' 'ctranspose' ## ## ## ################################################################################ %!assert_equal (size (cat (1, categorical, categorical)), [0, 0]) %!assert_equal (size (cat (2, categorical, categorical)), [0, 0]) %!assert_equal (size (cat (1, categorical (ones (1, 0)), categorical (ones (1, 0)))), [2, 0]) %!assert_equal (size (cat (2, categorical (ones (0, 1)), categorical (ones (0, 1)))), [0, 2]) %!test %! C = cat (1, categorical (1), categorical ([1; 2])); %! assert_equal (categories (C), {'1'; '2'}); %! assert_equal (C, categorical ([1; 1; 2])); %!test %! C = cat (2, categorical (1), categorical ([1, 2])); %! assert_equal (categories (C), {'1'; '2'}); %! assert_equal (C, categorical ([1, 1, 2])); %!test %! C = cat (3, categorical (1), categorical (2)); %! assert_equal (categories (C), {'1'; '2'}); %! assert_equal (C, categorical (reshape ([1, 2], 1, 1, 2))); %!test %! C = cat (2, {'1', '3'}, categorical ([2, 1])); %! assert_equal (categories (C), {'1'; '2'; '3'}); %! assert_equal (C, categorical ([1, 3, 2, 1])); %! assert_equal (double (C), [1, 3, 2, 1]); %!test %! C = cat (2, '1', categorical ([2, 1])); %! assert_equal (categories (C), {'1'; '2'}); %! assert_equal (C, categorical ([1, 2, 1])); %! assert_equal (double (C), [1, 2, 1]); %!test %! C = cat (2, categorical ([2, 1]), '1'); %! assert_equal (categories (C), {'1'; '2'}); %! assert_equal (C, categorical ([2, 1, 1])); %! assert_equal (double (C), [2, 1, 1]); %!test %! C = cat (1, categorical ([2, 1]), {'1', '3'}); %! assert_equal (categories (C), {'1'; '2'; '3'}); %! assert_equal (C, categorical ([2, 1; 1, 3])); %! assert_equal (double (C), [2, 1; 1, 3]); %!test %! C = cat (1, categorical ([2; 1]), '13'); %! assert_equal (categories (C), {'1'; '2'; '13'}); %! assert_equal (C, categorical ([2; 1; 13])); %! assert_equal (double (C), [2; 1; 3]); %!test %! C = cat (2, categorical ([2, 1]), '13'); %! assert_equal (categories (C), {'1'; '2'; '13'}); %! assert_equal (C, categorical ([2, 1, 13])); %! assert_equal (double (C), [2, 1, 3]); %!test %! C = cat (1, '13', categorical ([2; 1])); %! assert_equal (categories (C), {'1'; '2'; '13'}); %! assert_equal (C, categorical ([13; 2; 1])); %! assert_equal (double (C), [3; 2; 1]); %!test %! C = cat (1, categorical ([3; 1]), categorical ([2; 1])); %! assert_equal (categories (C), {'1'; '3'; '2'}); %! assert_equal (C, categorical ([3; 1; 2; 1])); %! assert_equal (double (C), [2; 1; 3; 1]); %!test %! C = cat (2, categorical ([3, 1]), categorical ([2, 1])); %! assert_equal (categories (C), {'1'; '3'; '2'}); %! assert_equal (C, categorical ([3, 1, 2, 1])); %! assert_equal (double (C), [2, 1, 3, 1]); %!test %! C = cat (2, categorical (1, [1, 2], 'Ordinal', true), ... %! categorical ([1, 2, 2], [1, 2], 'Ordinal', true)); %! assert_equal (categories (C), {'1'; '2'}); %! assert_equal (C, categorical ([1, 1, 2, 2], [1, 2], 'Ordinal', true)); %! assert_equal (double (C), [1, 1, 2, 2]); %!test %! C = cat (2, categorical (1, [2, 1], 'Ordinal', true), ... %! categorical ([1, 2, 2], [2, 1], 'Ordinal', true)); %! assert_equal (categories (C), {'2'; '1'}); %! assert_equal (C, categorical ([1, 1, 2, 2], [2, 1], 'Ordinal', true)); %! assert_equal (double (C), [2, 2, 1, 1]); %!test %! C = cat (1, categorical ([1, 2], [1, 2], 'Ordinal', true), ... %! categorical ([2, 2], [1, 2], 'Ordinal', true)); %! assert_equal (categories (C), {'1'; '2'}); %! assert_equal (C, categorical ([1, 2; 2, 2], [1, 2], 'Ordinal', true)); %! assert_equal (double (C), [1, 2; 2, 2]); %!test %! C = cat (1, categorical ([1, 1], [2, 1], 'Ordinal', true), ... %! categorical ([1, 2], [2, 1], 'Ordinal', true)); %! assert_equal (categories (C), {'2'; '1'}); %! assert_equal (C, categorical ([1, 1; 1, 2], [2, 1], 'Ordinal', true)); %! assert_equal (double (C), [2, 2; 2, 1]); %!test %! C = cat (2, categorical (1, [2, 1], 'Protected', true), ... %! categorical ([1, 2, 2], [1, 2], 'Protected', true)); %! assert_equal (categories (C), {'2'; '1'}); %! assert_equal (C, categorical ([1, 1, 2, 2], [2, 1], 'Protected', true)); %! assert_equal (double (C), [2, 2, 1, 1]); %!test %! C = cat (2, categorical (1, [1, 2], 'Protected', true), ... %! categorical ([1, 2, 2], [1, 2], 'Protected', true)); %! assert_equal (categories (C), {'1'; '2'}); %! assert_equal (C, categorical ([1, 1, 2, 2], [2, 1], 'Protected', true)); %! assert_equal (double (C), [1, 1, 2, 2]); %!error ... %! cat (0, categorical, categorical) %!error ... %! cat (1, categorical (ones (2, 3, 4)), categorical (ones (1, 0))) %!error ... %! cat (1, categorical (ones (0, 1)), categorical (ones (1, 0))) %!error ... %! cat (1, categorical (1, 1, 'Ordinal', true), categorical (1, 2, 'Ordinal', true)) %!error ... %! cat (1, categorical (1, 1, 'Ordinal', true), categorical (1, 1, 'Ordinal', false)) %!error ... %! cat (1, categorical (1, 1, 'Ordinal', true), {'1'}) %!error ... %! cat (1, categorical (1, 1, 'Protected', true), categorical (1, 2, 'Protected', true)) %!test %! C = [categorical(3), categorical([4, 2]), categorical([0, 1, 0])]; %! assert_equal (C, categorical ([3, 4, 2, 0, 1, 0])); %! assert_equal (categories (C), {'3'; '2'; '4'; '0'; '1'}); %! assert_equal (double (C), [1, 3, 2, 4, 5, 4]); %!test %! C = [{'1', '2'}, categorical(3), categorical([4, 2]), categorical([0, 1, 0])]; %! assert_equal (C, categorical ([1, 2, 3, 4, 2, 0, 1, 0])); %! assert_equal (categories (C), {'3'; '1'; '2'; '4'; '0'}); %! assert_equal (double (C), [2, 3, 1, 4, 3, 5, 2, 5]); %!test %! C = [{'5', '3'}, categorical(3), categorical([4, 2]), '8', categorical([0, 1, 0])]; %! assert_equal (C, categorical ([5, 3, 3, 4, 2, 8, 0, 1, 0])); %! assert_equal (categories (C), {'3'; '5'; '2'; '4'; '8'; '0'; '1'}); %! assert_equal (double (C), [2, 1, 1, 4, 3, 5, 6, 7, 6]); %!test %! C = [categorical(3), categorical([4, 2]), {'1', '3'}, categorical([0, 1, 0])]; %! assert_equal (C, categorical ([3, 4, 2, 1, 3, 0, 1, 0])); %! assert_equal (categories (C), {'3'; '2'; '4'; '1'; '0'}); %! assert_equal (double (C), [1, 3, 2, 4, 1, 5, 4, 5]); %!test %! C = [{'5', '2'}, categorical(3), categorical([4, 2]), '8', categorical([0, 1, 0])]; %! assert_equal (C, categorical ([5, 2, 3, 4, 2, 8, 0, 1, 0])); %! assert_equal (categories (C), {'3'; '2'; '5'; '4'; '8'; '0'; '1'}); %! assert_equal (double (C), [3, 2, 1, 4, 2, 5, 6, 7, 6]); %!test %! C = [{'5'}, {'2'}, categorical(3), categorical([4, 2]), '8', categorical([0, 1, 0])]; %! assert_equal (C, categorical ([5, 2, 3, 4, 2, 8, 0, 1, 0])); %! assert_equal (categories (C), {'3'; '5'; '2'; '4'; '8'; '0'; '1'}); %! assert_equal (double (C), [2, 3, 1, 4, 3, 5, 6, 7, 6]); %!test %! C = [{'5', '2'}, {'8'}, categorical(3), categorical([4, 2]), '8', categorical([0, 1, 0])]; %! assert_equal (C, categorical ([5, 2, 8, 3, 4, 2, 8, 0, 1, 0])); %! assert_equal (categories (C), {'3'; '2'; '5'; '8'; '4'; '0'; '1'}); %! assert_equal (double (C), [3, 2, 4, 1, 5, 2, 4, 6, 7, 6]); %!test %! C = [{'5', '2'}, {'8'}, categorical([5, 3]), categorical([4, 2]), '8', categorical([0, 1, 0])]; %! assert_equal (C, categorical ([5, 2, 8, 5, 3, 4, 2, 8, 0, 1, 0])); %! assert_equal (categories (C), {'3'; '5'; '2'; '8'; '4'; '0'; '1'}); %! assert_equal (double (C), [2, 3, 4, 2, 1, 5, 3, 4, 6, 7, 6]); %!test %! C = [categorical([3, 1]); categorical([4, 2]); categorical([0, 1])]; %! assert_equal (C, categorical ([3, 1; 4, 2; 0, 1])); %! assert_equal (categories (C), {'1'; '3'; '2'; '4'; '0'}); %! assert_equal (double (C), [2, 1; 4, 3; 5, 1]); %!test %! C = [{'1', '2'}; categorical([3, 1]); categorical([4, 2]); categorical([1, 0])]; %! assert_equal (C, categorical ([1, 2; 3, 1; 4, 2; 1, 0])); %! assert_equal (categories (C), {'1'; '3'; '2'; '4'; '0'}); %! assert_equal (double (C), [1, 3; 2, 1; 4, 3; 1, 5]); %!test %! C = [{'5', '3'}; categorical([3, 2]); categorical([4, 2]); {'8', '2'}; categorical([0, 1])]; %! assert_equal (C, categorical ([5, 3; 3, 2; 4, 2; 8, 2; 0, 1])); %! assert_equal (categories (C), {'2'; '3'; '5'; '4'; '8'; '0'; '1'}); %! assert_equal (double (C), [3, 2; 2, 1; 4, 1; 5, 1; 6, 7]); %!test %! C = [categorical([3, 2]); categorical([4, 2]); {'1', '3'}; categorical([0, 0])]; %! assert_equal (C, categorical ([3, 2; 4, 2; 1, 3; 0, 0])); %! assert_equal (categories (C), {'2'; '3'; '4'; '1'; '0'}); %! assert_equal (double (C), [2, 1; 3, 1; 4, 2; 5, 5]); %!test %! C = [{'5', '2'}; categorical([1, 3]); categorical([4, 2]); {'8','8'}; categorical([1, 1])]; %! assert_equal (C, categorical ([5, 2; 1, 3; 4, 2; 8, 8; 1, 1])); %! assert_equal (categories (C), {'1'; '3'; '2'; '5'; '4'; '8'}); %! assert_equal (double (C), [4, 3; 1, 2; 5, 3; 6, 6; 1, 1]); %!test %! C = [{'5', '4'}; {'2', '5'}; categorical([2, 3]); categorical([4, 2]); {'9', '8'}; categorical([0, 0])]; %! assert_equal (C, categorical ([5, 4; 2, 5; 2, 3; 4, 2; 9, 8; 0, 0])); %! assert_equal (categories (C), {'2'; '3'; '4'; '5'; '8'; '9'; '0'}); %! assert_equal (double (C), [4, 3; 1, 4; 1, 2; 3, 1; 6, 5; 7, 7]); %!test %! C = [{'5', '2'}; {'8', '8'}; categorical([3, 3]); categorical([4, 2]); {'8', '8'}; categorical([0, 1])]; %! assert_equal (C, categorical ([5, 2; 8, 8; 3, 3; 4, 2; 8, 8; 0, 1])); %! assert_equal (categories (C), {'3'; '2'; '5'; '8'; '4'; '0'; '1'}); %! assert_equal (double (C), [3, 2; 4, 4; 1, 1; 5, 2; 4, 4; 6, 7]); %!test %! C = [{'5', '2'}; {'8', '6'}; categorical([5, 3]); categorical([4, 2]); {'8', '8'}; categorical([0, 0])]; %! assert_equal (C, categorical ([5, 2; 8, 6; 5, 3; 4, 2; 8, 8; 0, 0])); %! assert_equal (categories (C), {'3'; '5'; '2'; '6'; '8'; '4'; '0'}); %! assert_equal (double (C), [2, 3; 5, 4; 2, 1; 6, 3; 5, 5; 7, 7]); %!assert_equal (repmat (categorical ([1, 2]), 4), categorical (repmat ([1, 2], 4))) %!assert_equal (repmat (categorical ([1, 2]), 2, 3, 4), categorical (repmat ([1, 2], 2, 3, 4))) %!assert_equal (repmat (categorical ([1, 2]), [2, 3]), categorical (repmat ([1, 2], [2, 3]))) %!assert_equal (repelem (categorical ([1:3]), 4), categorical (repelem ([1:3], 4))) %!assert_equal (repelem (categorical ([1:3]), 2, 3, 4), categorical (repelem ([1:3], 2, 3, 4))) %!assert_equal (repelem (categorical ([1:3]), [2, 3, 3]), categorical (repelem ([1:3], [2, 3, 3]))) %!assert_equal (repelems (categorical ([1:5]), [1, 3, 5; 2, 1, 2]), categorical ([1, 1, 3, 5, 5])) %!assert_equal (reshape (categorical (magic (4)), 2, 2, 4), categorical (reshape (magic (4), 2, 2, 4))) %!assert_equal (reshape (categorical (magic (4)), [2, 2, 4]), categorical (reshape (magic (4), [2, 2, 4]))) %!assert_equal (reshape (categorical (magic (4)), [], 2, 2), categorical (reshape (magic (4), [], 2, 2))) %!assert_equal (circshift (categorical (magic (4)), 2), categorical (circshift (magic (4), 2))) %!assert_equal (circshift (categorical (magic (4)), 1, 2), categorical (circshift (magic (4), 1, 2))) %!assert_equal (circshift (categorical (magic (4)), 1, 1), categorical (circshift (magic (4), 1, 1))) %!assert_equal (circshift (categorical (magic (4)), 1, 1), categorical (circshift (magic (4), 1))) %!test %! X = randi (10, 2, 3, 4); %! D = categorical (X); %! assert_equal (size (permute (D, [3, 1, 2])), [4, 2, 3]); %! assert_equal (permute (D, [3, 1, 2]), categorical (permute (X, [3, 1, 2]))); %! assert_equal (D, ipermute (permute (D, [3, 1, 2]), [3, 1, 2])); %!assert_equal (transpose (categorical ([1, 2; 3, 4])), categorical ([1, 3; 2, 4])) %!assert_equal (transpose (categorical ([1, 2; 3, 4])), ctranspose (categorical ([1, 2; 3, 4]))) %!error ... %! transpose (categorical (ones (2, 2, 2))) %!assert_equal (ctranspose (categorical ([1, 2; 3, 4])), categorical ([1, 3; 2, 4])) %!assert_equal (ctranspose (categorical ([1, 2; 3, 4])), transpose (categorical ([1, 2; 3, 4]))) %!error ... %! ctranspose (categorical (ones (2, 2, 2))) ################################################################################ ## ** Reference and Assignment Operations ** ## ################################################################################ ## Available Methods ## ## ## ## 'end' 'subsref' 'subsasgn' ## ## ## ################################################################################ %!test %! X = magic (3); %! C = categorical (X); %! assert_equal (C(:, end), categorical (X(:,end))); %!test %! X = magic (3); %! C = categorical (X); %! assert_equal (C([1:end-1], end), categorical ([6; 7])); %!test %! C = categorical ([1:5]); %! out = C([2,3]); %! assert_equal (size (out), [1, 2]); %! assert_equal (out, categorical ([2, 3])); %!error ... %! C = categorical ([1:5]); C{1} %!error ... %! C = categorical ([1:5]); C.prop %!test %! C = categorical (1:5); %! C([1,3]) = []; %! assert_equal (C, categorical ([2, 4, 5], [1, 2, 3, 4, 5])); %! assert_equal (categories (C), {'1'; '2'; '3'; '4'; '5'}); %! assert_equal (double (C), [2, 4, 5]); %!test %! C = categorical (1:5); %! C([1,3]) = missing; %! assert_equal (C, categorical ([NaN, 2, NaN, 4, 5], [1, 2, 3, 4, 5])); %! assert_equal (categories (C), {'1'; '2'; '3'; '4'; '5'}); %! assert_equal (double (C), [NaN, 2, NaN, 4, 5]); %!test %! C = categorical (1:5); %! C(2) = ''; %! assert_equal (isequaln (C, categorical ([1, NaN, 3, 4, 5], [1, 2, 3, 4, 5])), true); %! assert_equal (categories (C), {'1'; '2'; '3'; '4'; '5'}); %! assert_equal (double (C), [1, NaN, 3, 4, 5]); %!test %! C = categorical (1:5); %! C(2) = {''}; %! assert_equal (isequaln (C, categorical ([1, NaN, 3, 4, 5], [1, 2, 3, 4, 5])), true); %! assert_equal (categories (C), {'1'; '2'; '3'; '4'; '5'}); %! assert_equal (double (C), [1, NaN, 3, 4, 5]); %!test %! C = categorical (1:5); %! C(2) = string (''); %! assert_equal (isequaln (C, categorical ([1, NaN, 3, 4, 5], [1, 2, 3, 4, 5])), true); %! assert_equal (categories (C), {'1'; '2'; '3'; '4'; '5'}); %! assert_equal (double (C), [1, NaN, 3, 4, 5]); %!test %! C = categorical (1:5); %! C(2) = string (missing); %! assert_equal (isequaln (C, categorical ([1, NaN, 3, 4, 5], [1, 2, 3, 4, 5])), true); %! assert_equal (categories (C), {'1'; '2'; '3'; '4'; '5'}); %! assert_equal (double (C), [1, NaN, 3, 4, 5]); %!test %! C = categorical (1:5); %! C(1) = '5'; %! assert_equal (C, categorical ([5, 2, 3, 4, 5])); %!test %! C = categorical (1:5); %! C([1,3]) = {'5', '6'}; %! assert_equal (C, categorical ([5, 2, 6, 4, 5])); %! assert_equal (categories (C), {'1'; '2'; '3'; '4'; '5'; '6'}); %! assert_equal (double (C), [5, 2, 6, 4, 5]); %!test %! C = categorical (1:5); %! C(3) = {'0'}; %! assert_equal (C, categorical ([1, 2, 0, 4, 5])); %! C([1,3]) = {'5', '6'}; %! assert_equal (C, categorical ([5, 2, 6, 4, 5])); %! assert_equal (categories (C), {'1'; '2'; '3'; '4'; '5'; '0'; '6'}); %!test %! C = categorical (1:5, 1:5, 'Ordinal', true); %! C([1,3]) = categorical ([5, 5], 1:5, 'Ordinal', true); %! assert_equal (C, categorical ([5, 2, 5, 4, 5], 1:5, 'Ordinal', true)); %! assert_equal (categories (C), {'1'; '2'; '3'; '4'; '5'}); %! assert_equal (double (C), [5, 2, 5, 4, 5]); %!test %! C = categorical (1:5, 1:5, 'Protected', true); %! C([1,3]) = categorical ([5, 5], 'Protected', true); %! assert_equal (C, categorical ([5, 2, 5, 4, 5], 1:5, 'Protected', true)); %! assert_equal (categories (C), {'1'; '2'; '3'; '4'; '5'}); %! assert_equal (double (C), [5, 2, 5, 4, 5]); %!test %! C = categorical (1:5, 1:5, 'Protected', true); %! C([1,3]) = categorical ([5, 5]); %! assert_equal (C, categorical ([5, 2, 5, 4, 5], 1:5, 'Protected', true)); %! assert_equal (categories (C), {'1'; '2'; '3'; '4'; '5'}); %! assert_equal (double (C), [5, 2, 5, 4, 5]); %!test %! C = categorical (1:5); %! C([1,3]) = categorical ({'5', '6'}); %! assert_equal (C, categorical ([5, 2, 6, 4, 5])); %! assert_equal (categories (C), {'1'; '2'; '3'; '4'; '5'; '6'}); %! assert_equal (double (C), [5, 2, 6, 4, 5]); %!test %! C = categorical (1:5); %! C([1,3]) = categorical ([5, 6]); %! assert_equal (C, categorical ([5, 2, 6, 4, 5])); %! assert_equal (categories (C), {'1'; '2'; '3'; '4'; '5'; '6'}); %! assert_equal (double (C), [5, 2, 6, 4, 5]); %!test %! C = categorical ([1, 2, 4, 5]); %! C([1,3]) = categorical ([2, 6]); %! assert_equal (C, categorical ([2, 2, 6, 5], [1, 2, 4, 5, 6])); %! assert_equal (categories (C), {'1'; '2'; '4'; '5'; '6'}); %! assert_equal (double (C), [2, 2, 5, 4]); %!test %! C = categorical ([1, 2, 4, 5]); %! C([1,3]) = categorical ([2, 3]); %! assert_equal (C, categorical ([2, 2, 3, 5], [1, 2, 4, 5, 3])); %! assert_equal (categories (C), {'1'; '2'; '4'; '5'; '3'}); %! assert_equal (double (C), [2, 2, 5, 4]); %!error ... %! C = categorical (1:5); C(1:3)(2) = {'3'} %!error ... %! C = categorical (1:5); C(2) = {} %!error ... %! C = categorical (1:5); C(2) = 1 %!error ... %! C = categorical (1:5, 1:5, 'Ordinal', true); C(2) = '1' %!error ... %! C = categorical (1:5, 1:5, 'Ordinal', true); C(2) = categorical (1) %!error ... %! C = categorical (1:5, 1:5, 'Ordinal', false); C(2) = categorical (1, 1:5, 'Ordinal', true) %!error ... %! C = categorical (1:5, 1:5, 'Protected', true); C(2) = '6' %!error ... %! C = categorical ([1:5]); C{2} = '1' %!error ... %! C = categorical ([1:5]); C.prop = 1 pr0m1th3as-datatypes-9c9a8d3/inst/tests/datetime.m-tst000066400000000000000000002667461522766574100230030ustar00rootroot00000000000000## Copyright (C) 2024-2026 Andreas Bertsatos ## ## This file is part of the datatypes package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ################################################################################ ## ** Create and convert 'datetime' type ** ## ################################################################################ ## Available Methods ## ## ## ## 'datetime' 'dispstrings' 'cellstr' 'char' ## ## 'ymd' 'hms' 'year' 'quarter' ## ## 'month' 'week' 'day' 'hour' ## ## 'minute' 'second' 'timeofday' 'tzoffset' ## ## ## ################################################################################ ## Test 'datetime' constructor -- no-argument and basic construction %!assert_equal (class (datetime), 'datetime') %!assert_equal (isa (datetime, 'datetime'), true) %!assert_equal (isscalar (datetime ()), true) %!assert_equal (isscalar (datetime (2020, 1, 1)), true) %!assert_equal (class (datetime (2020, 1, 1)), 'datetime') ## Test 'datetime' constructor -- Y,M,D components and broadcasting %!assert_equal (year (datetime (2020, 1, 1)), 2020) %!assert_equal (month (datetime (2020, 6, 1)), 6) %!assert_equal (day (datetime (2020, 6, 15)), 15) %!assert_equal (size (datetime (2020, 1, [1; 2; 3])), [3, 1]) %!assert_equal (size (datetime (2020, 1, [1, 2, 3])), [1, 3]) %!assert_equal (size (datetime (2020, [1, 2; 3, 4], 1)), [2, 2]) %!assert_equal (year (datetime (2020, 1, [1; 2])), [2020; 2020]) %!assert_equal (day (datetime (2020, 1, [1; 2; 3])), [1; 2; 3]) %!assert_equal (size (datetime ([2019, 2020], 1, 1)), [1, 2]) ## Test 'datetime' constructor -- month/day rollover normalisation %!assert_equal (year (datetime (2020, 13, 1)), 2021) %!assert_equal (month (datetime (2020, 13, 1)), 1) %!assert_equal (month (datetime (2020, 0, 1)), 12) %!assert_equal (year (datetime (2020, 0, 1)), 2019) %!assert_equal (month (datetime (2020, 1, 32)), 2) %!assert_equal (day (datetime (2020, 1, 32)), 1) %!assert_equal (day (datetime (2020, 3, 0)), 29) %!assert_equal (month (datetime (2020, 3, 0)), 2) ## Test 'datetime' constructor -- H,MI,S components %!assert_equal (hour (datetime (2020, 1, 1, 13, 0, 0)), 13) %!assert_equal (minute (datetime (2020, 1, 1, 13, 45, 0)), 45) %!assert_equal (second (datetime (2020, 1, 1, 13, 45, 30)), 30) %!assert_equal (second (datetime (2020, 1, 1, 0, 0, 30.5)), 30.5) %!assert_equal (hour (datetime (2020, 1, 1, 25, 0, 0)), 1) %!assert_equal (day (datetime (2020, 1, 1, 25, 0, 0)), 2) %!assert_equal (minute (datetime (2020, 1, 1, 0, 75, 0)), 15) %!assert_equal (hour (datetime (2020, 1, 1, 0, 75, 0)), 1) ## Test 'datetime' constructor -- milliseconds component %!assert_equal (second (datetime (2020, 1, 1, 0, 0, 0, 500)), 0.5) %!assert_equal (second (datetime (2020, 1, 1, 0, 0, 1, 250)), 1.25) ## Test 'datetime' constructor -- from datevec matrix (3 or 6 columns) %!assert_equal (year (datetime ([2019, 6, 1; 2020, 7, 2])), [2019; 2020]) %!assert_equal (month (datetime ([2019, 6, 1; 2020, 7, 2])), [6; 7]) %!assert_equal (hour (datetime ([2020, 1, 1, 5, 0, 0])), 5) %!assert_equal (size (datetime ([2019, 6, 1; 2020, 7, 2])), [2, 1]) ## Test 'datetime' constructor -- from date strings %!assert_equal (year (datetime ('2020-06-15')), 2020) %!assert_equal (month (datetime ('2020-06-15')), 6) %!assert_equal (day (datetime ('2020-06-15')), 15) %!assert_equal (year (datetime ({'2019-01-01'; '2020-01-01'})), [2019; 2020]) ## Test 'datetime' constructor -- ConvertFrom posixtime/datenum/excel %!assert_equal (cellstr (datetime (0, 'ConvertFrom', 'posixtime')), {'01-Jan-1970'}) %!assert_equal (cellstr (datetime (86400, 'ConvertFrom', 'posixtime')), {'02-Jan-1970'}) %!assert_equal (cellstr (datetime (719529, 'ConvertFrom', 'datenum')), {'01-Jan-1970'}) %!assert_equal (year (datetime (0, 'ConvertFrom', 'posixtime')), 1970) %!assert_equal (isnat (datetime (NaN, 'ConvertFrom', 'datenum')), true) ## Test 'datetime' constructor -- NaT and Inf propagation %!assert_equal (isnat (datetime (NaN, NaN, NaN)), true) %!assert_equal (isnat (datetime (2020, 1, 1)), false) %!assert_equal (isinf (datetime (Inf, Inf, Inf)), true) %!assert_equal (year (datetime (Inf, Inf, Inf)), Inf) %!assert_equal (year (datetime (-Inf, -Inf, -Inf)), -Inf) ## Test 'datetime' constructor -- TimeZone and Format properties %!assert_equal (datetime (2020, 1, 1, 'TimeZone', 'UTC').TimeZone, 'UTC') %!assert_equal (datetime (2020, 1, 1, 'Format', 'default').Format, 'default') %!assert_equal (datetime (2020, 1, 1).TimeZone, '') ## Test 'datetime' constructor -- error branches %!error ... %! datetime (2020, 1, 1, 'Format', 5) %!error ... %! datetime ('2020', 'InputFormat', 5) %!error ... %! datetime ('2020', 'PivotYear', 50) %!error ... %! datetime ('20', 'InputFormat', 'yy', 'PivotYear', 'x') %!error ... %! datetime ('20', 'InputFormat', 'yy', 'PivotYear', 50.5) ## Test 'dispstrings' and 'cellstr' %!assert_equal (dispstrings (datetime (2020, 1, 1)), {'01-Jan-2020'}) %!assert_equal (dispstrings (datetime (2020, 1, 1, 12, 30, 0)), {'01-Jan-2020 12:30:00'}) %!assert_equal (dispstrings (datetime (2020, 1, 1, 12, 30, 45.5)), {'01-Jan-2020 12:30:45.500'}) %!assert_equal (cellstr (datetime (2020, 1, 1)), {'01-Jan-2020'}) %!assert_equal (cellstr (datetime (2020, 12, 25)), {'25-Dec-2020'}) %!assert_equal (cellstr (NaT), {'NaT'}) %!assert_equal (class (cellstr (datetime (2020, 1, 1))), 'cell') %!assert_equal (size (cellstr (datetime (2020, 1, [1; 2; 3]))), [3, 1]) ## Test 'char' %!assert_equal (char (datetime (2020, 1, 1)), '01-Jan-2020') %!assert_equal (class (char (datetime (2020, 1, 1))), 'char') %!assert_equal (rows (char (datetime (2020, 1, [1; 2]))), 2) ## Test 'ymd' %!test %! [y, m, d] = ymd (datetime (2020, 7, 4)); %! assert_equal (y, 2020); %! assert_equal (m, 7); %! assert_equal (d, 4); %!test %! [y, m, d] = ymd (datetime (2020, 1, [1; 2])); %! assert_equal (y, [2020; 2020]); %! assert_equal (d, [1; 2]); ## Test 'hms' %!test %! [h, m, s] = hms (datetime (2020, 1, 1, 13, 45, 30.5)); %! assert_equal (h, 13); %! assert_equal (m, 45); %! assert_equal (s, 30.5); ## Test 'year' %!assert_equal (year (datetime (2020, 1, 1)), 2020) %!assert_equal (year (datetime ([1999, 2, 1; 2021, 3, 1])), [1999; 2021]) %!assert_equal (year (NaT), NaN) ## Test 'quarter' %!assert_equal (quarter (datetime (2020, 1, 1)), 1) %!assert_equal (quarter (datetime (2020, 4, 1)), 2) %!assert_equal (quarter (datetime (2020, 7, 1)), 3) %!assert_equal (quarter (datetime (2020, 10, 1)), 4) %!assert_equal (quarter (datetime (2020, 12, 31)), 4) %!assert_equal (quarter (datetime (2020, [1, 4, 7, 10], 1)), [1, 2, 3, 4]) ## Test 'month' with type options %!assert_equal (month (datetime (2020, 6, 1)), 6) %!assert_equal (month (datetime (2020, 6, 1), 'monthofyear'), 6) %!assert_equal (month (datetime (2020, 7, 1), 'name'), {'July'}) %!assert_equal (month (datetime (2020, 7, 1), 'shortname'), {'Jul'}) %!assert_equal (month (datetime (2020, 1, 1), 'name'), {'January'}) %!assert_equal (month (datetime (2020, 12, 1), 'shortname'), {'Dec'}) %!error month (datetime (2020, 1, 1), 'bogus') ## Test 'day' with dayofmonth (default) %!assert_equal (day (datetime (2020, 6, 15)), 15) %!assert_equal (day (datetime (2020, 6, 15), 'dayofmonth'), 15) %!assert_equal (day (datetime (2020, 1, [1; 15; 31])), [1; 15; 31]) ## Test 'day' with dayofweek (1=Sunday..7=Saturday) %!assert_equal (day (datetime (2020, 1, 1), 'dayofweek'), 4) %!assert_equal (day (datetime (2000, 1, 1), 'dayofweek'), 7) %!assert_equal (day (datetime (2020, 2, 29), 'dayofweek'), 7) %!assert_equal (day (datetime (2024, 2, 29), 'dayofweek'), 5) %!assert_equal (day (datetime (1900, 1, 1), 'dayofweek'), 2) %!assert_equal (day (datetime (2020, 3, 1), 'dayofweek'), 1) %!assert_equal (day (datetime (2020, 1, [1; 8; 15]), 'dayofweek'), [4; 4; 4]) %!assert_equal (day (datetime (2020, [1, 2], [1, 29]), 'dayofweek'), [4, 7]) ## Test 'day' with iso-dayofweek (1=Monday..7=Sunday) %!assert_equal (day (datetime (2020, 1, 1), 'iso-dayofweek'), 3) %!assert_equal (day (datetime (2020, 1, 5), 'iso-dayofweek'), 7) %!assert_equal (day (datetime (2020, 1, 6), 'iso-dayofweek'), 1) ## Test 'day' with name/shortname %!assert_equal (day (datetime (2020, 1, 1), 'name'), {'Wednesday'}) %!assert_equal (day (datetime (2000, 1, 1), 'name'), {'Saturday'}) %!assert_equal (day (datetime (2020, 1, 1), 'shortname'), {'Wed'}) %!assert_equal (day (datetime (2020, 3, 1), 'shortname'), {'Sun'}) ## Test 'day' with dayofyear (regression: shape + leap-year borrow) %!assert_equal (day (datetime (2020, 1, 1), 'dayofyear'), 1) %!assert_equal (day (datetime (2020, 12, 31), 'dayofyear'), 366) %!assert_equal (day (datetime (2021, 12, 31), 'dayofyear'), 365) %!assert_equal (day (datetime (2020, 3, 1), 'dayofyear'), 61) %!assert_equal (day (datetime (2021, 3, 1), 'dayofyear'), 60) %!assert_equal (day (datetime (2020, 2, 29), 'dayofyear'), 60) %!assert_equal (day (datetime (2020, 3, [1; 2]), 'dayofyear'), [61; 62]) %!assert_equal (day (datetime (2020, [1, 3; 6, 12], [1, 1; 1, 31]), 'dayofyear'), [1, 61; 153, 366]) %!assert_equal (day (NaT, 'dayofyear'), NaN) %!error day (datetime (2020, 1, 1), 'bogus') ## Test 'hour' %!assert_equal (hour (datetime (2020, 1, 1, 23, 0, 0)), 23) %!assert_equal (hour (datetime (2020, 1, 1, 0, 0, 0)), 0) %!assert_equal (hour (NaT), NaN) ## Test 'minute' %!assert_equal (minute (datetime (2020, 1, 1, 0, 59, 0)), 59) %!assert_equal (minute (NaT), NaN) ## Test 'second' with type options %!assert_equal (second (datetime (2020, 1, 1, 0, 0, 45)), 45) %!assert_equal (second (datetime (2020, 1, 1, 0, 0, 45.75)), 45.75) %!assert_equal (second (datetime (2020, 1, 1, 0, 0, 0), 'secondofminute'), 0) %!assert_equal (second (datetime (2020, 1, 1, 2, 3, 4), 'secondofday'), 7384) %!assert_equal (second (datetime (2020, 1, 1, 1, 0, 0), 'secondofday'), 3600) %!error ... %! second (datetime (2020, 1, 1), 'bogus') ################################################################################ ## ** Summary Information ** ## ################################################################################ ## Test 'size' %!assert_equal (size (datetime (2020, 1, 1)), [1, 1]) %!assert_equal (size (datetime (2020, 1, [1; 2; 3])), [3, 1]) %!assert_equal (size (datetime (2020, 1, [1, 2, 3])), [1, 3]) %!assert_equal (size (datetime (2020, ones (2, 4, 3), 1)), [2, 4, 3]) %!test %! [r, c] = size (datetime (2020, 1, [1, 2, 3])); %! assert_equal (r, 1); %! assert_equal (c, 3); %!assert_equal (size (datetime (2020, 1, [1; 2; 3]), 1), 3) %!assert_equal (size (datetime (2020, 1, [1; 2; 3]), 2), 1) ## Test 'ndims' %!assert_equal (ndims (datetime (2020, 1, 1)), 2) %!assert_equal (ndims (datetime (2020, 1, [1; 2])), 2) %!assert_equal (ndims (datetime (2020, ones (2, 3, 4), 1)), 3) ## Test 'numel' %!assert_equal (numel (datetime (2020, 1, 1)), 1) %!assert_equal (numel (datetime (2020, 1, [1; 2; 3])), 3) %!assert_equal (numel (datetime (2020, ones (2, 3), 1)), 6) ## Test 'length' %!assert_equal (length (datetime (2020, 1, 1)), 1) %!assert_equal (length (datetime (2020, 1, [1; 2; 3])), 3) %!assert_equal (length (datetime (2020, ones (2, 5), 1)), 5) ## Test 'keyHash' (regression: previously read a non-existent property) %!assert_equal (class (keyHash (datetime (2020, 1, 1))), 'uint64') %!assert_equal (isscalar (keyHash (datetime (2020, 1, [1; 2; 3]))), true) %!assert_equal (keyHash (datetime (2020, 1, 1)), keyHash (datetime (2020, 1, 1))) %!assert_equal (keyHash (datetime (2020, 1, 1)) != keyHash (datetime (2020, 1, 2)), true) %!assert_equal (class (keyHash (datetime (2020, 1, 1), uint64 (7))), 'uint64') %!error ... %! keyHash (datetime (2020, 1, 1), 7) ################################################################################ ## ** Convert to other Numeric Representations ** ## ################################################################################ ## Test 'datevec' %!assert_equal (datevec (datetime (2020, 7, 4, 1, 2, 3)), [2020, 7, 4, 1, 2, 3]) %!assert_equal (size (datevec (datetime (2020, 1, [1; 2]))), [2, 6]) %!test %! [y, m, d, h, mi, s] = datevec (datetime (2020, 7, 4, 1, 2, 3)); %! assert_equal ([y, m, d, h, mi, s], [2020, 7, 4, 1, 2, 3]); ################################################################################ ## ** Query Operations ** ## ################################################################################ ## Test 'iscolumn' / 'isrow' / 'isscalar' / 'isvector' / 'ismatrix' %!assert_equal (iscolumn (datetime (2020, 1, [1; 2; 3])), true) %!assert_equal (iscolumn (datetime (2020, 1, [1, 2, 3])), false) %!assert_equal (isrow (datetime (2020, 1, [1, 2, 3])), true) %!assert_equal (isrow (datetime (2020, 1, [1; 2; 3])), false) %!assert_equal (isscalar (datetime (2020, 1, 1)), true) %!assert_equal (isscalar (datetime (2020, 1, [1; 2])), false) %!assert_equal (isvector (datetime (2020, 1, [1; 2; 3])), true) %!assert_equal (isvector (datetime (2020, ones (2, 2), 1)), false) %!assert_equal (ismatrix (datetime (2020, ones (2, 2), 1)), true) ## Test 'isempty' %!assert_equal (isempty (datetime (2020, 1, 1)), false) %!assert_equal (isempty (datetime (zeros (0, 3))), true) ## Test 'isfinite' / 'isinf' / 'isnat' %!assert_equal (isfinite (datetime (2020, 1, 1)), true) %!assert_equal (isfinite (NaT), false) %!assert_equal (isfinite (datetime (Inf, Inf, Inf)), false) %!assert_equal (isinf (datetime (Inf, Inf, Inf)), true) %!assert_equal (isinf (datetime (2020, 1, 1)), false) %!assert_equal (isnat (NaT), true) %!assert_equal (isnat (datetime (2020, 1, 1)), false) %!assert_equal (isnat (datetime (2020, 1, [1; 2])), [false; false]) ## Test 'ismissing' %!assert_equal (ismissing (datetime (2020, 1, 1)), false) %!assert_equal (ismissing (NaT), true) %!test %! d = datetime (2020, 1, [1; 2; 3]); %! d(2) = datetime (NaN, NaN, NaN); %! assert_equal (ismissing (d), [false; true; false]); %!assert_equal (ismissing (datetime (2020, 1, [1; 2; 3]), datetime (2020, 1, 3)), [false; false; true]) %!error ... %! ismissing (datetime (2020, 1, 1), datetime (2020, 1, 1), 3) %!error ... %! ismissing (datetime (2020, 1, 1), 5) ## Test 'isbetween' -- the default interval is closed and returns a logical %!test %! x = datetime (2026, 1, [1 2 3 4 5]); %! tf = isbetween (x, datetime (2026, 1, 2), datetime (2026, 1, 4)); %! assert_equal (class (tf), 'logical'); %! assert_equal (tf, logical ([0 1 1 1 0])); ## Test 'isbetween' -- the four interval types include the right endpoints %!test %! x = datetime (2026, 1, [1 2 3 4 5]); %! lo = datetime (2026, 1, 2); %! hi = datetime (2026, 1, 4); %! assert_equal (isbetween (x, lo, hi, 'closed'), logical ([0 1 1 1 0])); %! assert_equal (isbetween (x, lo, hi, 'open'), logical ([0 0 1 0 0])); %! assert_equal (isbetween (x, lo, hi, 'openleft'), logical ([0 0 1 1 0])); %! assert_equal (isbetween (x, lo, hi, 'openright'), logical ([0 1 1 0 0])); ## Test 'isbetween' -- Not-A-Time in any input is false; inverted bounds are false %!assert_equal (isbetween ([NaT datetime(2026,1,3)], datetime (2026, 1, 2), ... %! datetime (2026, 1, 4)), logical ([0 1])) %!assert_equal (isbetween (datetime (2026, 1, [1 2 3 4 5]), NaT, ... %! datetime (2026, 1, 4)), logical ([0 0 0 0 0])) %!assert_equal (isbetween (datetime (2026, 1, [1 2 3 4 5]), datetime (2026, 1, 4), ... %! datetime (2026, 1, 2)), logical ([0 0 0 0 0])) ## Test 'isbetween' -- bounds broadcast and may be date/time text %!assert_equal (isbetween (datetime (2026, 1, [1 2 3 4 5]), ... %! datetime (2026, 1, [1 2 3 4 5]), datetime (2026, 1, 4)), ... %! logical ([1 1 1 1 0])) %!assert_equal (isbetween (datetime (2026, 1, [1 2 3 4 5]), '02-Jan-2026', ... %! '04-Jan-2026'), logical ([0 1 1 1 0])) ## Test 'isbetween' -- comparison is by absolute instant across time zones %!assert_equal (isbetween (datetime (2026, 1, 1, 5, 0, 0, 'TimeZone', 'UTC'), ... %! datetime (2026, 1, 1, 0, 0, 0, 'TimeZone', 'America/New_York'), ... %! datetime (2026, 1, 1, 1, 0, 0, 'TimeZone', 'America/New_York')), true) ## Test 'isbetween' -- invalid arguments are rejected %!error ... %! isbetween (datetime (2026, 1, 1), datetime (2026, 1, 2)); %!error ... %! isbetween (datetime (2026, 1, 1), datetime (2026, 1, 1), datetime (2026, 1, 2), 'closed', 1); %!error ... %! isbetween (datetime (2026, 1, 1), datetime (2026, 1, 1), datetime (2026, 1, 2), 'bogus'); %!error ... %! isbetween (datetime (2026, 1, 2), datetime (2026, 1, 1, 'TimeZone', 'UTC'), ... %! datetime (2026, 1, 3, 'TimeZone', 'UTC')); %!error ... %! isbetween (datetime (2026, 1, 1), days (1), days (2)); ## Test 'unique' -- sorted order with the mapping indices %!test %! [b, ia, ib] = unique (datetime (2021, 1, [1 1 2])); %! assert_equal (b.Day, [1 2]); %! assert_equal (ia, [1; 3]); %! assert_equal (ib, [1; 1; 2]); ## Test 'unique' -- round-trips B = A(ia) and A = B(ib) %!test %! a = datetime (2021, 1, [3 1 2 1]); %! [b, ia, ib] = unique (a); %! assert_equal (isequaln (b, a(ia)), true); %! assert_equal (isequaln (a, b(ib)), true); ## Test 'unique' -- each Not-A-Time is kept as a distinct value, sorted last %!test %! a = datetime (2021, 1, [3 1 NaN 1 NaN 2]); %! [b, ia, ib] = unique (a); %! assert_equal (size (b), [1 5]); %! assert_equal (b.Day(1:3), [1 2 3]); %! assert_equal (isnat (b), logical ([0 0 0 1 1])); %! assert_equal (ia, [2; 6; 1; 3; 5]); %! assert_equal (ib, [3; 1; 4; 1; 5; 2]); ## Test 'unique' -- an all-NaT array keeps every element %!assert_equal (sum (isnat (unique (datetime ([NaN NaN NaN], 1, 1)))), 3) ## Test 'unique' -- 'stable' keeps first-seen order, NaT included %!test %! a = datetime (2021, 1, [3 1 NaN 1 NaN 2]); %! [b, ia, ib] = unique (a, 'stable'); %! assert_equal (b.Day([1 2 5]), [3 1 2]); %! assert_equal (isnat (b), logical ([0 0 1 1 0])); %! assert_equal (ia, [1; 2; 3; 5; 6]); %! assert_equal (ib, [1; 2; 3; 2; 4; 5]); ## Test 'unique' -- 'first' (default) and 'last' occurrence indices %!test %! [~, ia] = unique (datetime (2020, 1, [2 1 2 1]), 'first'); %! assert_equal (ia, [2; 1]); %! [~, ia] = unique (datetime (2020, 1, [2 1 2 1]), 'last'); %! assert_equal (ia, [4; 3]); ## Test 'unique' -- 'rows' treats each row as a single value %!test %! [b, ia, ib] = unique (datetime (2021, 1, [1 2; 3 4; 1 2]), 'rows'); %! assert_equal (b.Day, [1 2; 3 4]); %! assert_equal (ia, [1; 2]); %! assert_equal (ib, [1; 2; 1]); ## Test 'unique' -- orientation follows the input, empties give a column %!assert_equal (size (unique (datetime (2021, 1, [3 1 2]))), [1 3]) %!assert_equal (size (unique (datetime (2021, 1, [3; 1; 2]))), [3 1]) %!assert_equal (size (unique (datetime (2021, 1, [3 1; 2 1]))), [3 1]) %!assert_equal (size (unique (datetime (2021, 1, []))), [0 1]) ## Test 'unique' -- values are compared by absolute instant; the zone is kept %!test %! z = unique (datetime (2021, 1, [2 1 2], 'TimeZone', 'America/New_York')); %! assert_equal (z.Day, [1 2]); %! assert_equal (z.TimeZone, 'America/New_York'); ## Test 'unique' -- the 'legacy' option is rejected %!error ... %! unique (datetime (2021, 1, [1 2 1]), 'legacy'); ## Test 'intersect' -- common values, sorted, carrying A's format %!test %! a = datetime (2026, 1, [1 2 3 4]); a.Format = 'dd/MM/uuuu'; %! b = datetime (2026, 1, [3 4 5 6]); b.Format = 'uuuu-MM-dd'; %! c = intersect (a, b); %! assert_equal (day (c), [3 4]); %! assert_equal (c.Format, 'dd/MM/uuuu'); %! assert_equal (intersect (b, a).Format, 'uuuu-MM-dd'); ## Test 'intersect' -- index outputs satisfy C = A(ixA) = B(ixB) %!test %! a = datetime (2026, 1, [1 2 3 4]); %! b = datetime (2026, 1, [3 4 5 6]); %! [c, ixA, ixB] = intersect (a, b); %! assert_equal (isequaln (c, a(ixA)), true); %! assert_equal (isequaln (c, b(ixB)), true); ## Test 'intersect' -- membership is by absolute instant, result keeps A's zone %!test %! a = datetime (2026, 1, 1, 12, 0, 0, 'TimeZone', 'UTC'); %! b = datetime (2026, 1, 1, 7, 0, 0, 'TimeZone', 'America/New_York'); %! c = intersect (a, b); %! assert_equal (numel (c), 1); %! assert_equal (c.TimeZone, 'UTC'); %! assert_equal (hour (c), 12); ## Test 'intersect' -- two zones whose wall clocks never share an instant %!test %! z1 = datetime (2026, 1, [1 2 3], 'TimeZone', 'America/New_York'); %! z2 = datetime (2026, 1, [2 3 4], 'TimeZone', 'Europe/Athens'); %! c = intersect (z1, z2); %! assert_equal (numel (c), 0); %! assert_equal (c.TimeZone, 'America/New_York'); ## Test 'intersect' -- each NaT is distinct and never matches %!test %! n1 = [datetime(2026,1,1), NaT, datetime(2026,1,2), NaT]; %! n2 = [NaT, datetime(2026,1,2)]; %! c = intersect (n1, n2); %! assert_equal (day (c), 2); %! assert_equal (numel (c), 1); ## Test 'intersect' -- orientation: column unless both inputs are rows %!assert_equal (size (intersect (datetime(2026,1,(1:4)'), datetime(2026,1,[3 4 5 6]))), [2 1]) %!assert_equal (size (intersect (datetime(2026,1,[1 2 3 4]), datetime(2026,1,(3:6)'))), [2 1]) %!assert_equal (size (intersect (datetime(2026,1,[1 2 3 4]), datetime(2026,1,[3 4 5 6]))), [1 2]) ## Test 'intersect' -- 'stable' returns values in order of appearance in A %!test %! a = datetime (2026, 1, [4 3 2 1]); %! b = datetime (2026, 1, [3 4 5 6]); %! assert_equal (day (intersect (a, b, 'stable')), [4 3]); ## Test 'intersect' -- 'rows' matches whole rows %!test %! m1 = datetime (2026, 1, [1 2; 3 4]); %! m2 = datetime (2026, 1, [3 4; 5 6]); %! assert_equal (day (intersect (m1, m2, 'rows')), [3 4]); ## Test 'intersect' -- empty operand gives a 0-by-1 result %!assert_equal (size (intersect (datetime (2026, 1, []), datetime (2026, 1, [3 4]))), [0 1]) ## Test 'intersect' -- infinite elements compare as ordinary sortable values %!test %! p = datetime (2026, 1, 1) + days (Inf); %! a = [datetime(2026,1,1), p, datetime(2026,1,1) - days(Inf)]; %! b = [p, datetime(2026,1,9)]; %! c = intersect (a, b); %! assert_equal (numel (c), 1); %! assert_equal (isinf (c) & ! isnat (c), true); ## Test 'intersect' -- text operands are promoted to datetime %!assert_equal (day (intersect (datetime (2026, 1, [1 2 3 4]), '02-Jan-2026')), 2) %!assert_equal (day (intersect (datetime (2026, 1, [1 2 3 4]), ... %! {'02-Jan-2026', '03-Jan-2026'})), [2 3]) ## Test 'intersect' -- invalid operands and options are rejected %!error ... %! intersect (datetime (2026, 1, [1 2]), [736000 736001]); %!error ... %! intersect (datetime (2026, 1, [1 2]), days (1)); %!error ... %! intersect (datetime (2026, 1, 1), datetime (2026, 1, 1, 'TimeZone', 'UTC')); %!error ... %! intersect (datetime (2026, 1, [1 2]), datetime (2026, 1, [2 3]), 'legacy'); ## Test 'union' -- all unique values, sorted, carrying A's format %!test %! a = datetime (2026, 1, [1 2 3 4]); a.Format = 'dd/MM/uuuu'; %! b = datetime (2026, 1, [3 4 5 6]); %! c = union (a, b); %! assert_equal (day (c), [1 2 3 4 5 6]); %! assert_equal (c.Format, 'dd/MM/uuuu'); ## Test 'union' -- cross-zone values are interleaved by absolute instant %!test %! z1 = datetime (2026, 1, [1 2 3], 'TimeZone', 'America/New_York'); %! z2 = datetime (2026, 1, [2 3 4], 'TimeZone', 'Europe/Athens'); %! c = union (z1, z2); %! assert_equal (day (c), [1 1 2 2 3 3]); %! assert_equal (hour (c), [0 17 0 17 0 17]); %! assert_equal (c.TimeZone, 'America/New_York'); ## Test 'union' -- every distinct NaT is retained, sorted last %!test %! n1 = [datetime(2026,1,1), NaT, datetime(2026,1,2), NaT]; %! n2 = [NaT, datetime(2026,1,2)]; %! c = union (n1, n2); %! assert_equal (numel (c), 5); %! assert_equal (day (c)(1:2), [1 2]); %! assert_equal (isnat (c), logical ([0 0 1 1 1])); ## Test 'union' -- 'stable' keeps A's values then B's new values in order %!assert_equal (day (union (datetime (2026, 1, [4 1]), ... %! datetime (2026, 1, [1 7]), 'stable')), [4 1 7]) ## Test 'union' -- 'rows' combines unique rows %!test %! m1 = datetime (2026, 1, [1 2; 3 4]); %! m2 = datetime (2026, 1, [3 4; 5 6]); %! assert_equal (day (union (m1, m2, 'rows')), [1 2; 3 4; 5 6]); ## Test 'union' -- orientation and an empty operand %!assert_equal (size (union (datetime(2026,1,(1:2)'), datetime(2026,1,[2 3]))), [3 1]) %!assert_equal (size (union (datetime (2026, 1, []), datetime (2026, 1, [3 4]))), [2 1]) ## Test 'union' -- index outputs and text promotion %!assert_equal (day (union (datetime (2026, 1, [1 2]), '03-Jan-2026')), [1 2 3]) %!error ... %! union (datetime (2026, 1, 1), datetime (2026, 1, 1, 'TimeZone', 'UTC')); ## Test 'setdiff' -- values in A not in B, with the index output %!test %! a = datetime (2026, 1, [1 2 3 4]); %! b = datetime (2026, 1, [3 4 5 6]); %! [c, ixA] = setdiff (a, b); %! assert_equal (day (c), [1 2]); %! assert_equal (isequaln (c, a(ixA)), true); ## Test 'setdiff' -- A's NaT elements are all retained %!test %! n1 = [datetime(2026,1,1), NaT, datetime(2026,1,2), NaT]; %! n2 = [NaT, datetime(2026,1,2)]; %! c = setdiff (n1, n2); %! assert_equal (numel (c), 3); %! assert_equal (day (c)(1), 1); %! assert_equal (isnat (c), logical ([0 1 1])); ## Test 'setdiff' -- 'stable', 'rows', orientation %!assert_equal (day (setdiff (datetime (2026, 1, [4 1 2]), ... %! datetime (2026, 1, 2), 'stable')), [4 1]) %!test %! m1 = datetime (2026, 1, [1 2; 3 4]); %! m2 = datetime (2026, 1, [3 4; 5 6]); %! assert_equal (day (setdiff (m1, m2, 'rows')), [1 2]); %!assert_equal (size (setdiff (datetime (2026, 1, []), datetime (2026, 1, [3 4]))), [0 1]) ## Test 'setxor' -- values in exactly one input, sorted %!test %! a = datetime (2026, 1, [1 2 3 4]); %! b = datetime (2026, 1, [3 4 5 6]); %! assert_equal (day (setxor (a, b)), [1 2 5 6]); ## Test 'setxor' -- distinct NaT elements from both inputs are retained %!test %! n1 = [datetime(2026,1,1), NaT, datetime(2026,1,2), NaT]; %! n2 = [NaT, datetime(2026,1,2)]; %! c = setxor (n1, n2); %! assert_equal (numel (c), 4); %! assert_equal (day (c)(1), 1); %! assert_equal (isnat (c), logical ([0 1 1 1])); ## Test 'setxor' -- 'stable' and 'rows' %!assert_equal (day (setxor (datetime (2026, 1, [4 1]), ... %! datetime (2026, 1, [1 7]), 'stable')), [4 7]) %!test %! m1 = datetime (2026, 1, [1 2; 3 4]); %! m2 = datetime (2026, 1, [3 4; 5 6]); %! assert_equal (day (setxor (m1, m2, 'rows')), [1 2; 5 6]); ## Test 'ismember' -- membership, index, and same-instant across zones %!assert_equal (ismember (datetime (2020, 1, [1; 2; 3]), datetime (2020, 1, [2; 3])), ... %! [false; true; true]) %!test %! [tf, idx] = ismember (datetime (2020, 1, [1; 2; 3]), datetime (2020, 1, [2; 3])); %! assert_equal (tf, [false; true; true]); %! assert_equal (idx, [0; 1; 2]); %!assert_equal (ismember (datetime (2026, 1, 1, 12, 0, 0, 'TimeZone', 'UTC'), ... %! datetime (2026, 1, 1, 7, 0, 0, 'TimeZone', 'America/New_York')), true) ## Test 'ismember' -- NaT never matches; text first operand is promoted %!test %! n1 = [datetime(2026,1,1), NaT, datetime(2026,1,2), NaT]; %! n2 = [NaT, datetime(2026,1,2)]; %! [tf, idx] = ismember (n1, n2); %! assert_equal (tf, logical ([0 0 1 0])); %! assert_equal (idx, [0 0 2 0]); %!assert_equal (ismember ('02-Jan-2026', datetime (2026, 1, [1 2 3 4])), true) ## Test 'ismember' -- 'rows' and an empty first operand (TF follows A's size) %!test %! m1 = datetime (2026, 1, [1 2; 3 4]); %! m2 = datetime (2026, 1, [3 4; 5 6]); %! [tf, idx] = ismember (m1, m2, 'rows'); %! assert_equal (tf, [false; true]); %! assert_equal (idx, [0; 1]); %!assert_equal (size (ismember (datetime (2026, 1, []), datetime (2026, 1, [3 4]))), [0 0]) ## Test 'ismember' -- invalid options and zone mismatch are rejected %!error ... %! ismember (datetime (2026, 1, reshape (1:8, [2 2 2])), ... %! datetime (2026, 1, [1 2; 3 4]), 'rows'); %!error ... %! ismember (datetime (2026, 1, [1 2; 3 4]), ... %! datetime (2026, 1, [1 2 3; 4 5 6]), 'rows'); %!error ... %! ismember (datetime (2026, 1, [1 2]), datetime (2026, 1, [2 3]), 'bogus'); %!error ... %! ismember (datetime (2026, 1, 1), datetime (2026, 1, 1, 'TimeZone', 'UTC')); ################################################################################ ## ** Array Operations ** ## ################################################################################ ## Test 'cat' / 'horzcat' / 'vertcat' %!assert_equal (size ([datetime(2020,1,1), datetime(2020,1,2)]), [1, 2]) %!assert_equal (size ([datetime(2020,1,1); datetime(2020,1,2)]), [2, 1]) %!assert_equal (day ([datetime(2020,1,1), datetime(2020,1,2)]), [1, 2]) %!assert_equal (size (cat (1, datetime (2020, 1, [1; 2]), datetime (2020, 1, 3))), [3, 1]) %!assert_equal (day (vertcat (datetime (2020, 1, 1), datetime (2020, 1, 2))), [1; 2]) %!error cat (2, datetime (2020, 1, 1), 5) ## Test 'repmat' / 'repelem' %!assert_equal (size (repmat (datetime (2020, 1, 1), 2, 3)), [2, 3]) %!assert_equal (day (repmat (datetime (2020, 1, 5), 1, 3)), [5, 5, 5]) %!assert_equal (day (repelem (datetime (2020, 1, [1, 2]), 1, 2)), [1, 1, 2, 2]) ## Test 'reshape' %!assert_equal (size (reshape (datetime (2020, 1, 1:6), 2, 3)), [2, 3]) %!assert_equal (size (reshape (datetime (2020, 1, 1:6), 3, [])), [3, 2]) %!assert_equal (day (reshape (datetime (2020, 1, 1:4), 2, 2)), [1, 3; 2, 4]) ## Test 'circshift' %!assert_equal (day (circshift (datetime (2020, 1, [1; 2; 3]), 1)), [3; 1; 2]) ## Test 'permute' / 'ipermute' %!assert_equal (size (permute (datetime (2020, 1, [1, 2, 3]), [2, 1])), [3, 1]) %!assert_equal (day (permute (datetime (2020, 1, [1, 2]), [2, 1])), [1; 2]) %!test %! d = datetime (2020, ones (2, 3), 1); %! assert_equal (size (ipermute (permute (d, [2, 1]), [2, 1])), [2, 3]); ## Test 'transpose' / 'ctranspose' %!assert_equal (size (transpose (datetime (2020, 1, [1; 2; 3]))), [1, 3]) %!assert_equal (day (datetime (2020, 1, [1; 2; 3]).'), [1, 2, 3]) %!assert_equal (day (datetime (2020, 1, [1; 2; 3])'), [1, 2, 3]) ################################################################################ ## ** Reference and Assignment Operations ** ## ################################################################################ ## Test 'end' and subscripted reference %!assert_equal (day (datetime (2020, 1, [1; 2; 3])(end)), 3) %!assert_equal (day (datetime (2020, 1, [1; 2; 3])(2)), 2) %!assert_equal (day (datetime (2020, 1, [1, 2, 3])(1:2)), [1, 2]) %!assert_equal (class (datetime (2020, 1, [1; 2; 3])(2)), 'datetime') ## Test property reference via subsref %!assert_equal (datetime (2020, 6, 15).Year, 2020) %!assert_equal (datetime (2020, 6, 15).Month, 6) %!assert_equal (datetime (2020, 6, 15).Day, 15) %!assert_equal (datetime (2020, 1, 1, 3, 4, 5).Hour, 3) %!assert_equal (datetime (2020, 1, 1).Format, 'default') %!error datetime (2020, 1, 1).bogus %!error ... %! datetime (2020, 1, 1){1} ## Test subscripted assignment -- element assignment %!test %! d = datetime (2020, 1, [1; 2; 3]); %! d(2) = datetime (2020, 1, 20); %! assert_equal (day (d), [1; 20; 3]); %!test %! d = datetime (2020, 1, [1; 2; 3]); %! d(2) = []; %! assert_equal (day (d), [1; 3]); %!error ... %! d = datetime (2020, 1, 1); d(1) = 5; ## Test subscripted assignment -- component setters re-canonicalise (rollover) %!test # d.Month = 13 rolls into the following year (MATLAB parity) %! d = datetime (2020, 1, 15); %! d.Month = 13; %! assert_equal ([d.Year, d.Month, d.Day], [2021, 1, 15]); %!test # d.Day = 32 rolls into the next month %! d = datetime (2020, 1, 15); %! d.Day = 32; %! assert_equal ([d.Year, d.Month, d.Day], [2020, 2, 1]); %!test # d.Hour = 25 rolls into the next day %! d = datetime (2020, 1, 1); %! d.Hour = 25; %! assert_equal ([d.Day, d.Hour], [2, 1]); %!test # setting a component preserves sub-second precision %! d = datetime (2020, 1, 1, 0, 0, 0.25); %! d.Minute = 30; %! assert_equal (second (d), 0.25); %!test # direct component assignment on a single element of a vector %! d = datetime (2020, 1, [1; 2; 3]); %! d.Year(2) = 1999; %! assert_equal (year (d), [2020; 1999; 2020]); ## Test 'proxyArray' (regression: was undefined, breaking table interop) %!assert_equal (datetime (2020, 6, 15).proxyArray, [2020, 6, 15, 0, 0, 0]) %!assert_equal (size (datetime (2020, 1, [1; 2; 3]).proxyArray), [3, 6]) %!test %! d = datetime (2020, 1, [1; 2]); %! assert_equal (d.proxyArray, [2020, 1, 1, 0, 0, 0; 2020, 1, 2, 0, 0, 0]); %!test # NaT maps to NaN across its component columns %! d = datetime (2020, 1, [1; 2]); %! d(1) = NaT; %! p = d.proxyArray; %! assert_equal (p(1, :), [NaN, NaN, NaN, NaN, NaN, NaN]); ################################################################################ ## ** TimeZone handling ** ## ################################################################################ ## Test attaching a zone to an unzoned array does NOT shift the wall clock %!test %! d = datetime (2020, 6, 1, 12, 0, 0); %! d.TimeZone = 'America/New_York'; %! assert_equal ([d.Hour, d.Minute], [12, 0]); %! assert_equal (d.TimeZone, 'America/New_York'); ## Test converting between zones preserves the instant (wall clock shifts) %!test # UTC noon in January is 07:00 EST (UTC-5) %! d = datetime (2020, 1, 1, 12, 0, 0, 'TimeZone', 'UTC'); %! d.TimeZone = 'America/New_York'; %! assert_equal ([d.Hour, d.Minute], [7, 0]); %!test # UTC noon in July is 08:00 EDT (UTC-4, daylight saving) %! d = datetime (2020, 7, 1, 12, 0, 0, 'TimeZone', 'UTC'); %! d.TimeZone = 'America/New_York'; %! assert_equal ([d.Hour, d.Minute], [8, 0]); ## Test dropping the zone keeps the current wall-clock values %!test %! d = datetime (2020, 1, 1, 12, 0, 0, 'TimeZone', 'UTC'); %! d.TimeZone = ''; %! assert_equal ([d.Hour, d.Minute], [12, 0]); %! assert_equal (d.TimeZone, ''); ## Test invalid TimeZone assignment errors %!error ... %! d = datetime (2020, 1, 1); d.TimeZone = 5; ################################################################################ ## ** Arithmetic Operations ** ## ################################################################################ ## Available Methods ## ## ## ## 'minus' ## ## ## ################################################################################ ## Test 'minus' -- datetime minus datetime returns a duration %!assert_equal (class (datetime (2021, 1, 2) - datetime (2021, 1, 1)), 'duration') %!assert_equal (seconds (datetime (2021, 1, 2) - datetime (2021, 1, 1)), 86400) %!assert_equal (seconds (datetime (2021, 1, 2, 6, 30, 15) ... %! - datetime (2021, 1, 1, 0, 0, 0)), 109815) %!assert_equal (days (datetime (2021, 1, 2) - datetime (2021, 1, 1)), 1) %!assert_equal (seconds (datetime (2021, 1, 1) - datetime (2021, 1, 2)), -86400) ## Test 'minus' -- datetime difference is element-wise and size compatible %!assert_equal (seconds (datetime (2021, 1, [2, 3, 4]) - datetime (2021, 1, 1)), ... %! [86400, 172800, 259200]) %!assert_equal (size (datetime (2021, 1, [1, 2; 3, 4]) - datetime (2021, 1, 1)), ... %! [2, 2]) ## Test 'minus' -- zoned difference uses absolute instants across zones %!assert_equal (hours (datetime (2021, 1, 1, 'TimeZone', 'Europe/London') ... %! - datetime (2021, 1, 1, 'TimeZone', 'America/New_York')), -5) %!assert_equal (seconds (datetime (2021, 7, 1, 12, 0, 0, 'TimeZone', 'UTC') ... %! - datetime (2021, 7, 1, 12, 0, 0, 'TimeZone', 'UTC')), 0) ## Test 'minus' -- mixing a zoned and an unzoned datetime errors %!error ... %! datetime (2021, 1, 1, 'TimeZone', 'UTC') - datetime (2021, 1, 1); %!error ... %! datetime (2021, 1, 1) - datetime (2021, 1, 1, 'TimeZone', 'UTC'); ## Test 'minus' -- datetime minus duration returns a shifted datetime %!assert_equal (class (datetime (2021, 1, 1) - hours (30)), 'datetime') %!assert_equal (datevec (datetime (2021, 1, 1, 0, 0, 0) - hours (30)), ... %! [2020, 12, 30, 18, 0, 0]) %!assert_equal (datevec (datetime (2021, 1, 1) - days (1)), [2020, 12, 31, 0, 0, 0]) %!assert_equal (datevec (datetime (2021, 1, 1, 0, 0, 0) - minutes (90)), ... %! [2020, 12, 31, 22, 30, 0]) ## Test 'minus' -- duration subtraction on a zoned array is instant-based (DST) %!assert_equal (datevec (datetime (2021, 3, 14, 3, 0, 0, ... %! 'TimeZone', 'America/New_York') - hours (2)), ... %! [2021, 3, 14, 0, 0, 0]) ## Test 'minus' -- numeric and logical operands are a number of 24-hour days %!assert_equal (class (datetime (2021, 1, 1) - 5), 'datetime') %!assert_equal (datevec (datetime (2021, 1, 1) - 5), [2020, 12, 27, 0, 0, 0]) %!assert_equal (datevec (datetime (2021, 1, 1, 0, 0, 0) - 1.5), ... %! [2020, 12, 30, 12, 0, 0]) %!assert_equal (datevec (datetime (2021, 1, 1) - true), [2020, 12, 31, 0, 0, 0]) %!assert_equal (datevec (datetime (2021, 1, 1) - [1, 2, 3]), ... %! [2020, 12, 31, 0, 0, 0; 2020, 12, 30, 0, 0, 0; 2020, 12, 29, 0, 0, 0]) ## Test 'minus' -- calendarDuration subtracts whole months with day clamping %!assert_equal (class (datetime (2021, 1, 1) - calmonths (1)), 'datetime') %!assert_equal (datevec (datetime (2021, 1, 1) - calmonths (1)), [2020, 12, 1, 0, 0, 0]) %!assert_equal (datevec (datetime (2021, 3, 31) - calmonths (1)), [2021, 2, 28, 0, 0, 0]) %!assert_equal (datevec (datetime (2021, 3, 30) - calmonths (1)), [2021, 2, 28, 0, 0, 0]) %!assert_equal (datevec (datetime (2020, 3, 31) - calmonths (13)), [2019, 2, 28, 0, 0, 0]) %!assert_equal (datevec (datetime (2020, 2, 29) - calyears (1)), [2019, 2, 28, 0, 0, 0]) ## Test 'minus' -- calendarDuration applies months, then days, then time %!assert_equal (datevec (datetime (2021, 1, 1) ... %! - calendarDuration (0, 1, 15, 0, 0, 0)), [2020, 11, 16, 0, 0, 0]) %!assert_equal (datevec (datetime (2021, 1, 31) - calmonths (1) - caldays (1)), ... %! [2020, 12, 30, 0, 0, 0]) %!assert_equal (datevec (datetime (2021, 1, 1, 0, 0, 0) ... %! - calendarDuration (0, 0, 0, 6, 0, 0)), [2020, 12, 31, 18, 0, 0]) ## Test 'minus' -- Not-A-Time and infinite elements propagate %!assert_equal (isnan (seconds (NaT - datetime (2021, 1, 1))), true) %!assert_equal (isnat (NaT - hours (1)), true) %!assert_equal (isnat (datetime (2021, 1, 1) - days (NaN)), true) %!assert_equal (isinf (year (datetime (2021, 1, 1) - days (Inf))), true) %!assert_equal (sign (year (datetime (2021, 1, 1) - days (Inf))), -1) %!assert_equal (sign (year (datetime (2021, 1, 1) - days (-Inf))), 1) ## Test 'minus' -- empty arrays return empty %!assert_equal (isempty (datetime (2021, 1, []) - datetime (2021, 1, [])), true) %!assert_equal (isempty (datetime (2021, 1, []) - 1), true) ## Test 'minus' -- integer, char, string, and cell operands error %!error ... %! datetime (2021, 1, 1) - int8 (5); %!error ... %! datetime (2021, 1, 1) - 'A'; %!error ... %! datetime (2021, 1, 1) - string ("A"); %!error ... %! datetime (2021, 1, 1) - {1}; ## Test 'minus' -- subtracting a datetime from a non-datetime left operand errors %!error ... %! 5 - datetime (2021, 1, 1); %!error ... %! int8 (5) - datetime (2021, 1, 1); %!error ... %! true - datetime (2021, 1, 1); %!error ... %! 'A' - datetime (2021, 1, 1); ## Test 'diff' -- successive differences return a duration in 'hh:mm:ss' %!test %! d = diff (datetime (2026, 1, [1 2 4 8])); %! assert_equal (class (d), 'duration'); %! assert_equal (hours (d), [24 48 96]); %! assert_equal (d.Format, 'hh:mm:ss'); ## Test 'diff' -- N-th order difference is still a duration %!assert_equal (hours (diff (datetime (2026, 1, [1 2 4 8]), 2)), [24 48]) %!assert_equal (hours (diff (datetime (2026, 1, [1 2 4 8]), 3)), 24) %!assert_equal (class (diff (datetime (2026, 1, [1 2 4 8]), 2)), 'duration') ## Test 'diff' -- default operates along dim 1, DIM selects another %!assert_equal (hours (diff (datetime (2026, 1, [1 2; 4 8]))), [72 144]) %!assert_equal (hours (diff (datetime (2026, 1, [1 2; 4 8]), 1, 2)), [24; 96]) ## Test 'diff' -- a scalar yields a 0-by-0 empty duration %!assert_equal (size (diff (datetime (2026, 1, 1))), [0 0]) %!assert_equal (class (diff (datetime (2026, 1, 1))), 'duration') ## Test 'diff' -- Not-A-Time propagates as a NaN duration %!assert_equal (isnan (hours (diff ([datetime(2026,1,1), NaT, datetime(2026,1,5)]))), ... %! [true true]) ## Test 'diff' -- differencing along a trailing singleton dimension is empty %!assert_equal (size (diff (datetime (2026, 1, [1 2 4 8]), 1, 3)), [1 4 0]) ## Test 'diff' -- zoned differences are daylight-saving aware (spring forward) %!test %! zs = datetime (2026, 3, 28, 12, 0, 0, 'TimeZone', 'Europe/Athens'); %! ze = datetime (2026, 3, 30, 12, 0, 0, 'TimeZone', 'Europe/Athens'); %! assert_equal (hours (diff ([zs ze])), 47); ## Test 'diff' -- N must be a positive integer scalar %!error ... %! diff (datetime (2026, 1, [1 2 3]), -1); %!error ... %! diff (datetime (2026, 1, [1 2 3]), 0); %!error ... %! diff (datetime (2026, 1, [1 2 3]), 1.5); %!error ... %! diff (datetime (2026, 1, [1 2 3]), 1, 0); %!error ... %! diff (datetime (2026, 1, [1 2 3]), 1, 2, 3); ## Test 'caldiff' -- consecutive calendar differences as a calendarDuration %!test %! d = caldiff ([datetime(2026,1,31) datetime(2026,3,31) datetime(2027,5,1)]); %! assert_equal (class (d), 'calendarDuration'); %! assert_equal (dispstrings (d), {'2mo', '1y 1mo 1d'}); ## Test 'caldiff' -- a single requested component expresses the whole difference %!test %! tt = [datetime(2026,1,31) datetime(2026,3,31) datetime(2027,5,1)]; %! assert_equal (dispstrings (caldiff (tt, 'Time')), {'1416h 0m 0s', '9504h 0m 0s'}); %! assert_equal (dispstrings (caldiff (tt, 'Day')), {'59d', '396d'}); %! assert_equal (dispstrings (caldiff (tt, 'Month')), {'2mo', '13mo'}); %! assert_equal (dispstrings (caldiff (tt, 'q')), {'0d', '4q'}); %! assert_equal (calmonths (caldiff (tt, 'Year')), [0 12]); ## Test 'caldiff' -- an empty COMPONENTS string selects the default set %!assert_equal (dispstrings (caldiff ([datetime(2026,1,31) datetime(2027,5,1)], '')), ... %! {'1y 3mo 1d'}) ## Test 'caldiff' -- whole months honour month-end day clamping %!assert_equal (dispstrings (caldiff ([datetime(2026,1,31) datetime(2026,2,28)])), {'1mo'}) %!assert_equal (dispstrings (caldiff ([datetime(2026,1,31), datetime(2026,2,28), ... %! datetime(2026,3,31)])), {'1mo', '1mo 3d'}) ## Test 'caldiff' -- a time-of-day remainder appears as the Time component %!assert_equal (dispstrings (caldiff ([datetime(2026,1,1,6,0,0), ... %! datetime(2026,3,15,18,30,0)])), {'2mo 14d 12h 30m 0s'}) %!assert_equal (dispstrings (caldiff ([datetime(2026,1,1,0,0,0.25), ... %! datetime(2026,1,1,0,0,1.75)])), {'0h 0m 1.5s'}) ## Test 'caldiff' -- a scalar yields a 0-by-1 empty, Not-A-Time yields NaN %!assert_equal (size (caldiff (datetime (2026, 1, 1))), [0 1]) %!assert_equal (class (caldiff (datetime (2026, 1, 1))), 'calendarDuration') %!assert_equal (dispstrings (caldiff ([datetime(2026,1,1), NaT, datetime(2026,5,5)])), ... %! {'NaN', 'NaN'}) ## Test 'caldiff' -- a matrix differences along dimension 1 by default %!assert_equal (dispstrings (caldiff ([datetime(2026,1,31) datetime(2026,3,31); ... %! datetime(2026,5,1) datetime(2027,1,1)])), {'3mo 1d', '9mo 1d'}) ## Test 'caldiff' -- an invalid COMPONENTS is rejected %!error ... %! caldiff ([datetime(2026,1,1) datetime(2026,2,1)], 'ym'); ## Test 'between' -- signed calendar difference from A to B %!test %! d = between (datetime (2026, 1, 31), datetime (2026, 3, 30)); %! assert_equal (class (d), 'calendarDuration'); %! assert_equal (dispstrings (d), {'1mo 30d'}); %!assert_equal (dispstrings (between (datetime (2026, 3, 30), datetime (2026, 1, 31))), ... %! {'-1mo -28d'}) %!assert_equal (dispstrings (between (datetime (2026, 1, 31), datetime (2026, 1, 31))), {'0d'}) ## Test 'between' -- month-end clamping and leap-day cases %!assert_equal (dispstrings (between (datetime (2026, 1, 31), datetime (2026, 2, 28))), {'1mo'}) %!assert_equal (dispstrings (between (datetime (2026, 3, 31), datetime (2026, 2, 28))), {'-1mo'}) %!assert_equal (dispstrings (between (datetime (2026, 1, 31), datetime (2026, 3, 1))), {'1mo 1d'}) %!assert_equal (dispstrings (between (datetime (2024, 2, 29), datetime (2025, 2, 28))), {'1y'}) ## Test 'between' -- the whole-unit borrow is gated by the time of day %!assert_equal (dispstrings (between (datetime (2026, 1, 31, 10, 0, 0), ... %! datetime (2026, 2, 28, 8, 0, 0))), {'27d 22h 0m 0s'}) %!assert_equal (dispstrings (between (datetime (2024, 2, 29, 6, 0, 0), ... %! datetime (2027, 5, 10, 20, 30, 15))), {'3y 2mo 12d 14h 30m 15s'}) ## Test 'between' -- broadcasting a scalar against a vector %!assert_equal (dispstrings (between ([datetime(2026,1,1) datetime(2026,6,1)], ... %! datetime (2026, 3, 15))), {'2mo 14d', '-2mo -17d'}) ## Test 'between' -- Not-A-Time yields NaN %!assert_equal (dispstrings (between (datetime (2026, 1, 1), ... %! [datetime(2026,3,15) NaT])), {'2mo 14d', 'NaN'}) ## Test 'between' -- calendar differences ignore daylight saving; Time does not %!test %! zs = datetime (2026, 3, 28, 12, 0, 0, 'TimeZone', 'Europe/Athens'); %! ze = datetime (2026, 3, 30, 12, 0, 0, 'TimeZone', 'Europe/Athens'); %! assert_equal (dispstrings (between (zs, ze)), {'2d'}); %! assert_equal (dispstrings (between (zs, ze, 'Time')), {'47h 0m 0s'}); ## Test 'between' -- invalid operands and options are rejected %!error ... %! between (datetime (2026, 1, [1 2 3]), datetime (2026, 1, [1 2])); %!error ... %! between (datetime (2026, 1, 1), datetime (2026, 1, 1, 'TimeZone', 'UTC')); %!error ... %! between (datetime (2026, 1, 31), days (1)); %!error ... %! between (datetime (2026, 1, 31), datetime (2026, 3, 30), 'bogus'); ## Test 'mean' -- average of the instants as a datetime %!test %! m = mean (datetime (2026, 1, [1 2 4 8])); %! assert_equal (class (m), 'datetime'); %! assert_equal (dispstrings (m), {'03-Jan-2026 18:00:00'}); %!assert_equal (dispstrings (mean (datetime (2026, 1, [1 2]))), {'01-Jan-2026 12:00:00'}) %!assert_equal (dispstrings (mean (datetime (2026, 1, 5))), {'05-Jan-2026'}) ## Test 'mean' -- Not-A-Time is kept by default and dropped with 'omitnat' %!test %! rn = [datetime(2026,1,1) NaT datetime(2026,1,5)]; %! assert_equal (isnat (mean (rn)), true); %! assert_equal (dispstrings (mean (rn, 'omitnat')), {'03-Jan-2026'}); %! assert_equal (dispstrings (mean (rn, 'omitmissing')), {'03-Jan-2026'}); %! assert_equal (isnat (mean (rn, 'includenat')), true); ## Test 'mean' -- dimension arguments and an empty array %!test %! rm = datetime (2026, 1, [1 2; 4 8]); %! assert_equal (dispstrings (mean (rm)), {'02-Jan-2026 12:00:00', '05-Jan-2026'}); %! assert_equal (dispstrings (mean (rm, 2)), ... %! {'01-Jan-2026 12:00:00'; '06-Jan-2026'}); %! assert_equal (dispstrings (mean (rm, 'all')), {'03-Jan-2026 18:00:00'}); %!assert_equal (isnat (mean (datetime (2026, 1, []))), true) ## Test 'mean' -- the Format and TimeZone are inherited; zoned mean is DST-aware %!test %! rf = datetime (2026, 1, [1 2 4 8]); %! rf.Format = 'dd/MM/uuuu HH:mm'; %! assert_equal (mean (rf).Format, 'dd/MM/uuuu HH:mm'); %!test %! z = mean (datetime (2026, 1, [1 2 4], 'TimeZone', 'Europe/Athens')); %! assert_equal (z.TimeZone, 'Europe/Athens'); %! assert_equal (dispstrings (z), {'02-Jan-2026 08:00:00'}); ## Test 'mean' -- infinite instants propagate %!assert_equal (isinf (mean ([datetime(2026,1,1), datetime(2026,1,1) + days(Inf)])), true) %!assert_equal (isnat (mean ([datetime(2026,1,1) + days(Inf), ... %! datetime(2026,1,1) - days(Inf)])), true) ## Test 'median' -- middle instant, averaging the two middle for an even count %!assert_equal (dispstrings (median (datetime (2026, 1, [1 2 4 8]))), {'03-Jan-2026'}) %!assert_equal (dispstrings (median (datetime (2026, 1, [1 2 3 4]))), ... %! {'02-Jan-2026 12:00:00'}) %!assert_equal (dispstrings (median (datetime (2026, 1, [1 2 3]))), {'02-Jan-2026'}) %!assert_equal (class (median (datetime (2026, 1, [1 2 3]))), 'datetime') %!test %! rn = [datetime(2026,1,1) NaT datetime(2026,1,5)]; %! assert_equal (isnat (median (rn)), true); %! assert_equal (dispstrings (median (rn, 'omitnat')), {'03-Jan-2026'}); ## Test 'mode' -- smallest most-frequent value, ignoring Not-A-Time %!assert_equal (dispstrings (mode (datetime (2026, 1, [1 2 4 8]))), {'01-Jan-2026'}) %!assert_equal (dispstrings (mode ([datetime(2026,1,1) NaT datetime(2026,1,5)])), ... %! {'01-Jan-2026'}) %!assert_equal (class (mode (datetime (2026, 1, [1 2 2]))), 'datetime') ## Test 'mode' -- the frequency and the list of all modal values %!test %! [m, f, c] = mode (datetime (2026, 1, [1 1 2 2 3])); %! assert_equal (dispstrings (m), {'01-Jan-2026'}); %! assert_equal (f, 2); %! assert_equal (dispstrings (c{1}), {'01-Jan-2026'; '02-Jan-2026'}); ## Test 'std' -- standard deviation of the instants as a duration %!test %! s = std (datetime (2026, 1, [1 2 4 8])); %! assert_equal (class (s), 'duration'); %! assert_equal (dispstrings (s), {'74:17:48'}); ## Test 'std' -- the weight selects N-1 (default) or N normalisation %!assert_equal (dispstrings (std (datetime (2026, 1, [1 2 4 8]), 1)), {'64:20:34'}) %!assert_equal (dispstrings (std (datetime (2026, 1, [1 2; 4 8]), 0, 2)), ... %! {'16:58:14'; '67:52:56'}) ## Test 'std' -- 'omitnat' skips missing values and the mean is a second output %!assert_equal (dispstrings (std ([datetime(2026,1,1) NaT datetime(2026,1,5)], ... %! 'omitnat')), {'67:52:56'}) %!test %! [s, m] = std (datetime (2026, 1, [1 2 4 8])); %! assert_equal (class (s), 'duration'); %! assert_equal (class (m), 'datetime'); %! assert_equal (dispstrings (m), {'03-Jan-2026 18:00:00'}); ## Test 'dateshift' -- start of each calendar unit (a week starts on Sunday) %!test %! sh = datetime (2026, 7, 17, 13, 45, 30); %! assert_equal (class (dateshift (sh, 'start', 'year')), 'datetime'); %! assert_equal (dispstrings (dateshift (sh, 'start', 'year')), {'01-Jan-2026'}); %! assert_equal (dispstrings (dateshift (sh, 'start', 'quarter')), {'01-Jul-2026'}); %! assert_equal (dispstrings (dateshift (sh, 'start', 'month')), {'01-Jul-2026'}); %! assert_equal (dispstrings (dateshift (sh, 'start', 'week')), {'12-Jul-2026'}); %! assert_equal (dispstrings (dateshift (sh, 'start', 'day')), {'17-Jul-2026'}); %! assert_equal (dispstrings (dateshift (sh, 'start', 'hour')), {'17-Jul-2026 13:00:00'}); %! assert_equal (dispstrings (dateshift (sh, 'start', 'minute')), {'17-Jul-2026 13:45:00'}); %! assert_equal (dispstrings (dateshift (sh, 'start', 'second')), {'17-Jul-2026 13:45:30'}); ## Test 'dateshift' -- end of each unit (next boundary below a day, last day above) %!test %! sh = datetime (2026, 7, 17, 13, 45, 30); %! assert_equal (dispstrings (dateshift (sh, 'end', 'year')), {'31-Dec-2026'}); %! assert_equal (dispstrings (dateshift (sh, 'end', 'quarter')), {'30-Sep-2026'}); %! assert_equal (dispstrings (dateshift (sh, 'end', 'month')), {'31-Jul-2026'}); %! assert_equal (dispstrings (dateshift (sh, 'end', 'week')), {'18-Jul-2026'}); %! assert_equal (dispstrings (dateshift (sh, 'end', 'day')), {'18-Jul-2026'}); %! assert_equal (dispstrings (dateshift (sh, 'end', 'hour')), {'17-Jul-2026 14:00:00'}); %! assert_equal (dispstrings (dateshift (sh, 'end', 'minute')), {'17-Jul-2026 13:46:00'}); %! assert_equal (dispstrings (dateshift (sh, 'end', 'second')), {'17-Jul-2026 13:45:31'}); ## Test 'dateshift' -- end of a day is the following midnight %!assert_equal (dispstrings (dateshift (datetime (2026, 1, 1), 'end', 'day')), {'02-Jan-2026'}) %!assert_equal (dispstrings (dateshift (datetime (2026, 1, 1), 'end', 'year')), {'31-Dec-2026'}) ## Test 'dateshift' -- a rule shifts by whole units before truncating %!test %! sh = datetime (2026, 7, 17, 13, 45, 30); %! assert_equal (dispstrings (dateshift (sh, 'start', 'week', 'next')), {'19-Jul-2026'}); %! assert_equal (dispstrings (dateshift (sh, 'start', 'week', 'previous')), {'05-Jul-2026'}); %! assert_equal (dispstrings (dateshift (sh, 'start', 'month', 'next')), {'01-Aug-2026'}); %! assert_equal (dispstrings (dateshift (sh, 'end', 'month', 'next')), {'31-Aug-2026'}); %! assert_equal (dispstrings (dateshift (sh, 'start', 'day', 2)), {'19-Jul-2026'}); %! assert_equal (dispstrings (dateshift (sh, 'start', 'day', -3)), {'14-Jul-2026'}); %! assert_equal (dispstrings (dateshift (sh, 'start', 'month', 0)), {'01-Jul-2026'}); ## Test 'dateshift' -- 'dayofweek' moves to a weekday, keeping the time of day %!test %! sh = datetime (2026, 7, 17, 13, 45, 30); %! assert_equal (dispstrings (dateshift (sh, 'dayofweek', 1)), {'19-Jul-2026 13:45:30'}); %! assert_equal (dispstrings (dateshift (sh, 'dayofweek', 2)), {'20-Jul-2026 13:45:30'}); %! assert_equal (dispstrings (dateshift (sh, 'dayofweek', 'Monday')), {'20-Jul-2026 13:45:30'}); %! assert_equal (dispstrings (dateshift (sh, 'dayofweek', 2, 'previous')), {'13-Jul-2026 13:45:30'}); %! assert_equal (dispstrings (dateshift (sh, 'dayofweek', 2, 'nearest')), {'20-Jul-2026 13:45:30'}); %! assert_equal (dispstrings (dateshift (sh, 'dayofweek', 6, 'current')), {'17-Jul-2026 13:45:30'}); %! assert_equal (dispstrings (dateshift (sh, 'dayofweek', 6, 2)), {'24-Jul-2026 13:45:30'}); %! assert_equal (dispstrings (dateshift (sh, 'dayofweek', 6, -1)), {'17-Jul-2026 13:45:30'}); ## Test 'dateshift' -- across a week vector, and matrix / empty shapes %!test %! ww = datetime (2026, 7, [13 14 15 16 17 18 19]); %! assert_equal (day (dateshift (ww, 'start', 'week')), [12 12 12 12 12 12 19]); %! assert_equal (day (dateshift (ww, 'end', 'week')), [18 18 18 18 18 18 25]); %!assert_equal (day (dateshift (datetime (2026, 1, [15 20; 25 30]), 'start', 'month')), ... %! [1 1; 1 1]) %!assert_equal (size (dateshift (datetime (2026, 1, []), 'start', 'month')), [0 0]) ## Test 'dateshift' -- Not-A-Time and infinite elements pass through %!assert_equal (isnat (dateshift (NaT, 'start', 'month')), true) %!assert_equal (isinf (dateshift (datetime (2026, 1, 1) + days (Inf), 'start', 'month')), true) %!test %! v = dateshift ([datetime(2026,1,15) NaT], 'start', 'month'); %! assert_equal (day (v(1)), 1); %! assert_equal (isnat (v(2)), true); ## Test 'dateshift' -- month-end respects leap years, and zoned start keeps the zone %!assert_equal (dispstrings (dateshift (datetime (2024, 2, 10), 'end', 'month')), {'29-Feb-2024'}) %!assert_equal (dispstrings (dateshift (datetime (2026, 2, 10), 'end', 'month')), {'28-Feb-2026'}) %!test %! z = dateshift (datetime (2026, 3, 29, 12, 0, 0, 'TimeZone', 'Europe/Athens'), ... %! 'start', 'day'); %! assert_equal (z.TimeZone, 'Europe/Athens'); %! assert_equal (dispstrings (z), {'29-Mar-2026'}); ## Test 'dateshift' -- invalid arguments are rejected %!error ... %! dateshift (datetime (2026, 1, 1), 'bogus', 'month'); %!error ... %! dateshift (datetime (2026, 1, 1), 'start', 'bogus'); %!error ... %! dateshift (datetime (2026, 1, 1), 'start'); %!error ... %! dateshift (datetime (2026, 1, 1), 'dayofweek', 8); %!error ... %! dateshift (datetime (2026, 1, 1), 'dayofweek', 'Funday'); %!error ... %! dateshift (datetime (2026, 1, 1), 'start', 'month', 'bogus'); %!error ... %! dateshift (datetime (2026, 1, 1), 'start', 'week', 1.5); %!error ... %! dateshift (datetime (2026, 1, 1), 'end', 'dayofweek', 2); ## Test 'plus' -- datetime plus duration returns a shifted datetime %!assert_equal (class (datetime (2021, 1, 1) + hours (30)), 'datetime') %!assert_equal (datevec (datetime (2021, 1, 1, 0, 0, 0) + hours (30)), ... %! [2021, 1, 2, 6, 0, 0]) %!assert_equal (datevec (datetime (2021, 1, 1) + days (1)), [2021, 1, 2, 0, 0, 0]) %!assert_equal (datevec (datetime (2021, 1, 1, 0, 0, 0) + minutes (90)), ... %! [2021, 1, 1, 1, 30, 0]) ## Test 'plus' -- addition commutes with the datetime on either side %!assert_equal (datevec (hours (5) + datetime (2021, 1, 1, 0, 0, 0)), ... %! [2021, 1, 1, 5, 0, 0]) %!assert_equal (datevec (calmonths (1) + datetime (2021, 1, 1)), [2021, 2, 1, 0, 0, 0]) %!assert_equal (datevec (caldays (3) + datetime (2021, 1, 1)), [2021, 1, 4, 0, 0, 0]) %!assert_equal (datevec (5 + datetime (2021, 1, 1)), [2021, 1, 6, 0, 0, 0]) %!assert_equal (datevec (5 + datetime (2021, 1, 1)), datevec (datetime (2021, 1, 1) + 5)) ## Test 'plus' -- numeric and logical operands are a number of 24-hour days %!assert_equal (datevec (datetime (2021, 1, 1) + 5), [2021, 1, 6, 0, 0, 0]) %!assert_equal (datevec (datetime (2021, 1, 1, 0, 0, 0) + 1.5), [2021, 1, 2, 12, 0, 0]) %!assert_equal (datevec (datetime (2021, 1, 1) + true), [2021, 1, 2, 0, 0, 0]) %!assert_equal (datevec (datetime (2021, 1, 1) + [1, 2, 3]), ... %! [2021, 1, 2, 0, 0, 0; 2021, 1, 3, 0, 0, 0; 2021, 1, 4, 0, 0, 0]) ## Test 'plus' -- calendarDuration adds whole months with day clamping %!assert_equal (datevec (datetime (2021, 1, 1) + calmonths (1)), [2021, 2, 1, 0, 0, 0]) %!assert_equal (datevec (datetime (2021, 1, 1) + calmonths (13)), [2022, 2, 1, 0, 0, 0]) %!assert_equal (datevec (datetime (2021, 1, 31) + calmonths (1)), [2021, 2, 28, 0, 0, 0]) %!assert_equal (datevec (datetime (2021, 3, 31) + calmonths (1)), [2021, 4, 30, 0, 0, 0]) %!assert_equal (datevec (datetime (2020, 2, 29) + calyears (1)), [2021, 2, 28, 0, 0, 0]) %!assert_equal (datevec (datetime (2021, 1, 1) + caldays (40)), [2021, 2, 10, 0, 0, 0]) ## Test 'plus' -- calendarDuration applies months, then days, then time %!assert_equal (datevec (datetime (2021, 1, 1, 0, 0, 0) ... %! + calendarDuration (0, 1, 15, 6, 0, 0)), [2021, 2, 16, 6, 0, 0]) %!assert_equal (datevec (datetime (2021, 1, 31) + calmonths (1) + caldays (1)), ... %! [2021, 3, 1, 0, 0, 0]) ## Test 'plus' -- duration addition on a zoned array is instant-based (DST) %!assert_equal (datevec (datetime (2021, 3, 14, 0, 0, 0, ... %! 'TimeZone', 'America/New_York') + hours (2)), ... %! [2021, 3, 14, 3, 0, 0]) %!assert_equal (datevec (datetime (2021, 3, 14, 0, 0, 0, ... %! 'TimeZone', 'America/New_York') + caldays (1)), ... %! [2021, 3, 15, 0, 0, 0]) ## Test 'plus' -- adding two datetime arrays is not defined %!error ... %! datetime (2021, 1, 1) + datetime (2021, 1, 2); ## Test 'plus' -- Not-A-Time and infinite elements propagate %!assert_equal (isnat (NaT + hours (1)), true) %!assert_equal (isnat (datetime (2021, 1, 1) + days (NaN)), true) %!assert_equal (isnat (datetime (2021, 1, 1) + calmonths (NaN)), true) %!assert_equal (isinf (year (datetime (2021, 1, 1) + days (Inf))), true) %!assert_equal (sign (year (datetime (2021, 1, 1) + days (Inf))), 1) %!assert_equal (sign (year (datetime (2021, 1, 1) + days (-Inf))), -1) ## Test 'plus' -- empty arrays return empty %!assert_equal (isempty (datetime (2021, 1, []) + 1), true) %!assert_equal (isempty (1 + datetime (2021, 1, [])), true) ## Test 'plus' -- integer, char, string, and cell operands error %!error ... %! datetime (2021, 1, 1) + int8 (5); %!error ... %! datetime (2021, 1, 1) + 'A'; %!error ... %! datetime (2021, 1, 1) + string ("A"); %!error ... %! datetime (2021, 1, 1) + {1}; %!error ... %! int8 (5) + datetime (2021, 1, 1); ################################################################################ ## ** Relational Operations ** ## ################################################################################ ## Available Methods ## ## ## ## 'lt' 'le' 'gt' 'ge' ## ## 'eq' 'ne' ## ## ## ################################################################################ ## Test 'lt' -- earlier is less than later %!assert_equal (datetime (2021, 1, 1) < datetime (2021, 6, 1), true) %!assert_equal (datetime (2021, 6, 1) < datetime (2021, 1, 1), false) %!assert_equal (datetime (2021, 1, 1) < datetime (2021, 1, 1), false) %!assert_equal (datetime (2021, 1, 1, 0, 0, 0) < datetime (2021, 1, 1, 0, 0, 1), true) %!assert_equal (class (datetime (2021, 1, 1) < datetime (2021, 6, 1)), 'logical') ## Test 'le' -- earlier or equal %!assert_equal (datetime (2021, 1, 1) <= datetime (2021, 1, 1), true) %!assert_equal (datetime (2021, 1, 1) <= datetime (2021, 6, 1), true) %!assert_equal (datetime (2021, 6, 1) <= datetime (2021, 1, 1), false) ## Test 'gt' -- later is greater %!assert_equal (datetime (2021, 6, 1) > datetime (2021, 1, 1), true) %!assert_equal (datetime (2021, 1, 1) > datetime (2021, 6, 1), false) %!assert_equal (datetime (2021, 1, 1) > datetime (2021, 1, 1), false) ## Test 'ge' -- later or equal %!assert_equal (datetime (2021, 1, 1) >= datetime (2021, 1, 1), true) %!assert_equal (datetime (2021, 6, 1) >= datetime (2021, 1, 1), true) %!assert_equal (datetime (2021, 1, 1) >= datetime (2021, 6, 1), false) ## Test 'eq' and 'ne' -- equality of the same instant %!assert_equal (datetime (2021, 1, 1) == datetime (2021, 1, 1), true) %!assert_equal (datetime (2021, 1, 1) == datetime (2021, 6, 1), false) %!assert_equal (datetime (2021, 1, 1) != datetime (2021, 6, 1), true) %!assert_equal (datetime (2021, 1, 1) != datetime (2021, 1, 1), false) %!assert_equal (datetime (2021, 1, 1, 12, 0, 0) == datetime (2021, 1, 1, 12, 0, 0), true) ## Test relational -- element-wise over arrays with broadcasting %!assert_equal (datetime (2021, 1, [1, 2, 3]) < datetime (2021, 1, 2), ... %! [true, false, false]) %!assert_equal (datetime (2021, 1, [1, 2, 3]) == datetime (2021, 1, 2), ... %! [false, true, false]) %!assert_equal (datetime (2021, 1, [1, 2, 3]) >= datetime (2021, 1, 2), ... %! [false, true, true]) %!assert_equal (size (datetime (2021, 1, [1, 2; 3, 4]) < datetime (2021, 1, 3)), ... %! [2, 2]) ## Test relational -- Not-A-Time never compares less/greater/equal %!assert_equal (NaT < datetime (2021, 1, 1), false) %!assert_equal (NaT > datetime (2021, 1, 1), false) %!assert_equal (NaT <= datetime (2021, 1, 1), false) %!assert_equal (NaT >= datetime (2021, 1, 1), false) %!assert_equal (NaT == datetime (2021, 1, 1), false) %!assert_equal (NaT == NaT, false) %!assert_equal (NaT != NaT, true) %!assert_equal (NaT != datetime (2021, 1, 1), true) %!assert_equal ([datetime(2021,1,1), NaT] != NaT, [true, true]) ## Test relational -- zoned arrays compare by absolute instant across zones %!assert_equal (datetime (2021, 1, 1, 'TimeZone', 'America/New_York') ... %! == datetime (2021, 1, 1, 'TimeZone', 'Europe/London'), false) %!assert_equal (datetime (2021, 1, 1, 'TimeZone', 'America/New_York') ... %! > datetime (2021, 1, 1, 'TimeZone', 'Europe/London'), true) %!assert_equal (datetime (2021, 1, 1, 12, 0, 0, 'TimeZone', 'UTC') ... %! == datetime (2021, 1, 1, 7, 0, 0, 'TimeZone', 'America/New_York'), true) ## Test relational -- infinite datetimes order correctly %!assert_equal (datetime (2021, 1, 1) - days (-Inf) > datetime (2021, 6, 1), true) %!assert_equal (datetime (2021, 1, 1) - days (Inf) < datetime (2021, 1, 1), true) %!assert_equal (datetime (2021, 1, 1) - days (Inf) == datetime (2021, 1, 1) - days (Inf), true) ## Test relational -- empty arrays return empty %!assert_equal (isempty (datetime (2021, 1, []) < datetime (2021, 1, [])), true) ## Test relational -- comparing a zoned and an unzoned datetime errors %!error ... %! datetime (2021, 1, 1, 'TimeZone', 'UTC') == datetime (2021, 1, 1); %!error ... %! datetime (2021, 1, 1, 'TimeZone', 'UTC') < datetime (2021, 1, 1); ## Test relational -- a non-datetime operand errors %!error ... %! datetime (2021, 1, 1) < 5; %!error ... %! 5 > datetime (2021, 1, 1); %!error ... %! datetime (2021, 1, 1) == 'x'; ## Test relational -- broadcasting holds for all six operators %!assert_equal (size (datetime (2021, 1, [1, 2, 3]) < datetime (2021, 1, [1; 2; 4])), [3, 3]) %!assert_equal (size (datetime (2021, 1, [1, 2, 3]) <= datetime (2021, 1, [1; 2; 4])), [3, 3]) %!assert_equal (size (datetime (2021, 1, [1, 2, 3]) > datetime (2021, 1, [1; 2; 4])), [3, 3]) %!assert_equal (size (datetime (2021, 1, [1, 2, 3]) >= datetime (2021, 1, [1; 2; 4])), [3, 3]) %!assert_equal (size (datetime (2021, 1, [1, 2, 3]) == datetime (2021, 1, [1; 2; 4])), [3, 3]) %!assert_equal (size (datetime (2021, 1, [1, 2, 3]) != datetime (2021, 1, [1; 2; 4])), [3, 3]) %!assert_equal (datetime (2021, 1, [1, 2, 3]) == datetime (2021, 1, [1; 2; 4]), ... %! logical ([1, 0, 0; 0, 1, 0; 0, 0, 0])) %!assert_equal (datetime (2021, 1, [1, 2, 3]) < datetime (2021, 1, [1; 2; 4]), ... %! logical ([0, 0, 0; 1, 0, 0; 1, 1, 1])) %!assert_equal (datetime (2021, 1, 2) <= datetime (2021, 1, [1, 2, 3]), ... %! [false, true, true]) %!assert_equal (datetime (2021, 1, [1, 2; 3, 4]) > datetime (2021, 1, 2), ... %! logical ([0, 0; 1, 1])) ## Test 'isequal' -- equal and unequal datetime arrays %!assert_equal (isequal (datetime (2021, 1, 1), datetime (2021, 1, 1)), true) %!assert_equal (isequal (datetime (2021, 1, 1), datetime (2021, 6, 1)), false) %!assert_equal (isequal (datetime (2021, 1, 1, 12, 0, 0), ... %! datetime (2021, 1, 1, 12, 0, 0)), true) %!assert_equal (isequal (datetime (2021, 1, [1, 2, 3]), datetime (2021, 1, [1, 2, 3])), true) %!assert_equal (class (isequal (datetime (2021, 1, 1), datetime (2021, 1, 1))), 'logical') %!assert_equal (isscalar (isequal (datetime (2021, 1, [1, 2]), datetime (2021, 1, [1, 2]))), true) ## Test 'isequal' -- Not-A-Time is never equal (unlike isequaln) %!assert_equal (isequal (NaT, NaT), false) %!assert_equal (isequal (NaT, datetime (2021, 1, 1)), false) %!assert_equal (isequal ([datetime(2021,1,1), NaT], [datetime(2021,1,1), NaT]), false) ## Test 'isequal' -- multiple arguments are all compared to the first %!assert_equal (isequal (datetime (2021, 1, 1), datetime (2021, 1, 1), ... %! datetime (2021, 1, 1)), true) %!assert_equal (isequal (datetime (2021, 1, 1), datetime (2021, 1, 1), ... %! datetime (2021, 6, 1)), false) ## Test 'isequal' -- size and orientation mismatches return false %!assert_equal (isequal (datetime (2021, 1, [1, 2, 3]), datetime (2021, 1, [1, 2])), false) %!assert_equal (isequal (datetime (2021, 1, [1, 2, 3]), datetime (2021, 1, [1; 2; 3])), false) ## Test 'isequal' -- empties of equal size are equal %!assert_equal (isequal (datetime (2021, 1, []), datetime (2021, 1, [])), true) ## Test 'isequal' -- a non-datetime operand returns false, never errors %!assert_equal (isequal (datetime (2021, 1, 1), 738522), false) %!assert_equal (isequal (datetime (2021, 1, 1), 'x'), false) %!assert_equal (isequal (datetime (2021, 1, 1), {1}), false) ## Test 'isequal' -- time zones compare by instant; a zone mismatch is false %!assert_equal (isequal (datetime (2021, 1, 1, 'TimeZone', 'UTC'), ... %! datetime (2021, 1, 1)), false) %!assert_equal (isequal (datetime (2021, 1, 1, 12, 0, 0, 'TimeZone', 'UTC'), ... %! datetime (2021, 1, 1, 7, 0, 0, 'TimeZone', 'America/New_York')), true) %!assert_equal (isequal (datetime (2021, 1, 1, 'TimeZone', 'UTC'), ... %! datetime (2021, 1, 1, 'TimeZone', 'America/New_York')), false) ## Test 'isequal' -- infinite elements compare equal by sign %!assert_equal (isequal (datetime (2021, 1, 1) - days (Inf), ... %! datetime (2021, 1, 1) - days (Inf)), true) %!assert_equal (isequal (datetime (2021, 1, 1) - days (Inf), ... %! datetime (2021, 1, 1) - days (-Inf)), false) ## Test 'isequaln' -- like isequal but Not-A-Time compares equal %!assert_equal (isequaln (NaT, NaT), true) %!assert_equal (isequaln (NaT, datetime (2021, 1, 1)), false) %!assert_equal (isequaln ([datetime(2021,1,1), NaT], [datetime(2021,1,1), NaT]), true) %!assert_equal (isequaln ([datetime(2021,1,1), NaT], [datetime(2021,1,1), datetime(2021,6,1)]), false) %!assert_equal (isequaln (datetime (2021, 1, 1), datetime (2021, 1, 1)), true) %!assert_equal (isequaln (datetime (2021, 1, 1), datetime (2021, 6, 1)), false) %!assert_equal (class (isequaln (NaT, NaT)), 'logical') ## Test 'isequaln' -- multiple arguments are all compared to the first %!assert_equal (isequaln (NaT, NaT, NaT), true) %!assert_equal (isequaln (datetime (2021, 1, 1), NaT, datetime (2021, 1, 1)), false) ## Test 'isequaln' -- size, type, and time-zone mismatches return false %!assert_equal (isequaln (datetime (2021, 1, [1, 2, 3]), datetime (2021, 1, [1, 2])), false) %!assert_equal (isequaln (datetime (2021, 1, 1), 738522), false) %!assert_equal (isequaln (datetime (2021, 1, 1, 'TimeZone', 'UTC'), datetime (2021, 1, 1)), false) %!assert_equal (isequaln (datetime (2021, 1, 1, 12, 0, 0, 'TimeZone', 'UTC'), ... %! datetime (2021, 1, 1, 7, 0, 0, 'TimeZone', 'America/New_York')), true) ## Test 'isequaln' -- infinite elements are equal but distinct from Not-A-Time %!assert_equal (isequaln (datetime (2021, 1, 1) - days (Inf), ... %! datetime (2021, 1, 1) - days (Inf)), true) %!assert_equal (isequaln (NaT, datetime (2021, 1, 1) - days (Inf)), false) ## Test 'colon' -- issue #2: ranges must not blow the stack %!assert_equal (numel (datetime (2025, 5, 7) : datetime (2025, 5, 8)), 2) %!assert_equal (class (datetime (2025, 5, 7) : datetime (2025, 5, 8)), 'datetime') %!assert_equal (numel (datetime (2025, 5, 7, 0, 0, 0) : hours (1) ... %! : datetime (2025, 5, 8, 0, 0, 0)), 25) ## Test 'colon' -- default step is one calendar day and yields a row vector %!assert_equal (cellstr (datetime (2021, 1, 1) : datetime (2021, 1, 4)), ... %! {'01-Jan-2021', '02-Jan-2021', '03-Jan-2021', '04-Jan-2021'}) %!assert_equal (size (datetime (2021, 1, 1) : datetime (2021, 1, 5)), [1, 5]) %!assert_equal (isscalar (datetime (2021, 1, 1) : datetime (2021, 1, 1)), true) ## Test 'colon' -- duration and numeric steps advance by fixed elapsed time %!assert_equal (numel (datetime (2021, 1, 1, 0, 0, 0) : hours (6) ... %! : datetime (2021, 1, 2, 0, 0, 0)), 5) %!assert_equal (cellstr (datetime (2021, 1, 1) : 2 : datetime (2021, 1, 5)), ... %! {'01-Jan-2021', '03-Jan-2021', '05-Jan-2021'}) %!assert_equal (datevec (datetime (2021, 1, 1, 0, 0, 0) : minutes (30) ... %! : datetime (2021, 1, 1, 1, 0, 0)), ... %! [2021, 1, 1, 0, 0, 0; 2021, 1, 1, 0, 30, 0; 2021, 1, 1, 1, 0, 0]) ## Test 'colon' -- calendarDuration steps clamp the day per element %!assert_equal (cellstr (datetime (2021, 1, 31) : calmonths (1) : datetime (2021, 5, 31)), ... %! {'31-Jan-2021', '28-Feb-2021', '31-Mar-2021', '30-Apr-2021', '31-May-2021'}) %!assert_equal (cellstr (datetime (2021, 1, 31) : calmonths (2) : datetime (2021, 5, 31)), ... %! {'31-Jan-2021', '31-Mar-2021', '31-May-2021'}) %!assert_equal (numel (datetime (2021, 1, 1) : calyears (1) : datetime (2025, 1, 1)), 5) %!assert_equal (cellstr (datetime (2021, 1, 1) : calquarters (1) : datetime (2021, 10, 1)), ... %! {'01-Jan-2021', '01-Apr-2021', '01-Jul-2021', '01-Oct-2021'}) ## Test 'colon' -- default calendar-day step preserves the wall clock over DST %!assert_equal ((datetime (2021, 3, 12, 'TimeZone', 'America/New_York') ... %! : datetime (2021, 3, 16, 'TimeZone', 'America/New_York')).Hour, ... %! [0, 0, 0, 0, 0]) ## Test 'colon' -- a fixed 24-hour step drifts across a DST change %!assert_equal ((datetime (2021, 3, 12, 'TimeZone', 'America/New_York') ... %! : days (1) : datetime (2021, 3, 16, 'TimeZone', 'America/New_York')).Hour, ... %! [0, 0, 0, 1]) ## Test 'colon' -- reversed ranges need a negative step %!assert_equal (isempty (datetime (2021, 1, 5) : datetime (2021, 1, 1)), true) %!assert_equal (size (datetime (2021, 1, 5) : datetime (2021, 1, 1)), [1, 0]) %!assert_equal (isempty (datetime (2021, 1, 5) : caldays (1) : datetime (2021, 1, 1)), true) %!assert_equal (cellstr (datetime (2021, 1, 5) : caldays (-1) : datetime (2021, 1, 3)), ... %! {'05-Jan-2021', '04-Jan-2021', '03-Jan-2021'}) %!assert_equal (numel (datetime (2021, 1, 1, 12, 0, 0) : hours (-6) ... %! : datetime (2021, 1, 1, 0, 0, 0)), 3) ## Test 'colon' -- the range inherits the endpoints' time zone %!assert_equal ((datetime (2021, 1, 1, 'TimeZone', 'UTC') ... %! : datetime (2021, 1, 3, 'TimeZone', 'UTC')).TimeZone, 'UTC') ## Test 'colon' -- error branches %!error ... %! datetime (2021, 1, 1) : 5; %!error ... %! datetime (2021, 1, [1, 2]) : datetime (2021, 1, 5); %!error ... %! datetime (2021, 1, 1) : datetime (2021, 1, 5, 'TimeZone', 'UTC'); %!error ... %! datetime (2021, 1, 1) : NaT; %!error ... %! datetime (2021, 1, 1) : caldays (0) : datetime (2021, 1, 5); %!error ... %! datetime (2021, 1, 1) : {1} : datetime (2021, 1, 5); %!error ... %! datetime (2021, 1, 1) : caldays ([1, 2]) : datetime (2021, 1, 5); ## Test 'sort' -- ascending order is the default and ties are stable %!test %! [b, i] = sort (datetime (2021, 1, [3, 1, 2, 1])); %! assert_equal (b.Day, [1, 1, 2, 3]); %! assert_equal (i, [2, 4, 3, 1]); ## Test 'sort' -- descending order keeps the original order of equal elements %!test %! [b, i] = sort (datetime (2021, 1, [3, 1, 2, 1]), 'descend'); %! assert_equal (b.Day, [3, 2, 1, 1]); %! assert_equal (i, [1, 3, 2, 4]); ## Test 'sort' -- Not-A-Time goes last for ascending, first for descending %!test %! n = datetime ([2021, 2020, NaN, 2022], 1, 1); %! [b, i] = sort (n); %! assert_equal (isnat (b), logical ([0, 0, 0, 1])); %! assert_equal (b.Year(1:3), [2020, 2021, 2022]); %! assert_equal (i, [2, 1, 4, 3]); %! [b, i] = sort (n, 'descend'); %! assert_equal (isnat (b), logical ([1, 0, 0, 0])); %! assert_equal (i, [3, 4, 1, 2]); ## Test 'sort' -- MissingPlacement overrides the direction default %!test %! n = datetime ([2021, 2020, NaN, 2022], 1, 1); %! b = sort (n, 'MissingPlacement', 'first'); %! assert_equal (isnat (b), logical ([1, 0, 0, 0])); %! assert_equal (b.Year(2:4), [2020, 2021, 2022]); %! b = sort (n, 'MissingPlacement', 'last'); %! assert_equal (isnat (b), logical ([0, 0, 0, 1])); %! b = sort (n, 'descend', 'MissingPlacement', 'first'); %! assert_equal (isnat (b), logical ([1, 0, 0, 0])); %! assert_equal (b.Year(2:4), [2022, 2021, 2020]); ## Test 'sort' -- a matrix sorts down each column by default %!test %! [b, i] = sort (datetime (2021, 1, [3, 1; 2, 4])); %! assert_equal (b.Day, [2, 1; 3, 4]); %! assert_equal (i, [2, 1; 1, 2]); ## Test 'sort' -- dimension two sorts along each row %!assert_equal (sort (datetime (2021, 1, [3, 1; 2, 4]), 2).Day, [1, 3; 2, 4]) ## Test 'sort' -- descending sort along the first dimension of a matrix %!test %! [b, i] = sort (datetime (2021, 1, [3, 1; 2, 4]), 1, 'descend'); %! assert_equal (b.Day, [3, 4; 2, 1]); %! assert_equal (i, [1, 2; 2, 1]); ## Test 'sort' -- an empty array is returned unchanged %!assert_equal (size (sort (datetime (2021, 1, []))), [0, 0]) ## Test 'sort' -- a zoned array sorts by absolute instant %!test %! [b, i] = sort (datetime (2021, 1, [3, 1, 2], ... %! 'TimeZone', 'America/New_York'), 'descend'); %! assert_equal (b.Day, [3, 2, 1]); %! assert_equal (i, [1, 3, 2]); ## Test 'sort' -- error branches %!error ... %! sort (datetime (2021, 1, 1), 0); %!error ... %! sort (datetime (2021, 1, 1), 'up'); %!error ... %! sort (datetime (2021, 1, 1), 'MissingPlacement', 'middle'); %!error ... %! sort (datetime (2021, 1, 1), 'MissingPlacement'); %!error ... %! sort (datetime (2021, 1, 1), {1}); ## Test 'issorted' -- ascending (non-decreasing) is the default %!assert_equal (issorted (datetime (2021, 1, [1, 2, 3])), true) %!assert_equal (issorted (datetime (2021, 1, [3, 2, 1])), false) %!assert_equal (issorted (datetime (2021, 1, [1, 2, 2, 3])), true) ## Test 'issorted' -- descending order %!assert_equal (issorted (datetime (2021, 1, [3, 2, 1]), 'descend'), true) ## Test 'issorted' -- strict variants reject equal neighbours %!assert_equal (issorted (datetime (2021, 1, [1, 2, 2, 3]), 'strictascend'), false) %!assert_equal (issorted (datetime (2021, 1, [1, 2, 3]), 'strictascend'), true) %!assert_equal (issorted (datetime (2021, 1, [1, 2, 3]), 'strictmonotonic'), true) ## Test 'issorted' -- monotonic accepts either direction %!assert_equal (issorted (datetime (2021, 1, [1, 2, 3]), 'monotonic'), true) %!assert_equal (issorted (datetime (2021, 1, [3, 2, 1]), 'monotonic'), true) ## Test 'issorted' -- Not-A-Time counts as greater than any value %!assert_equal (issorted (datetime ([2021, 2022, NaN], 1, 1)), true) %!assert_equal (issorted (datetime ([NaN, 2021, 2022], 1, 1)), false) %!assert_equal (issorted (datetime ([2021, 2022, NaN], 1, 1), 'descend'), false) ## Test 'issorted' -- every column (or row) of a matrix must be sorted %!assert_equal (issorted (datetime (2021, 1, [3, 1; 2, 4])), false) %!assert_equal (issorted (datetime (2021, 1, [3, 1; 2, 4]), 2), false) %!assert_equal (issorted (datetime (2021, 1, [1, 3; 2, 4])), true) ## Test 'issorted' -- scalars and empties are trivially sorted %!assert_equal (issorted (datetime (2021, 1, 1)), true) %!assert_equal (issorted (datetime (2021, 1, [])), true) ## Test 'issorted' -- error branches %!error ... %! issorted (datetime (2021, 1, 1), 1.5); %!error ... %! issorted (datetime (2021, 1, 1), 'up'); %!error ... %! issorted (datetime (2021, 1, 1), {1}); ## Test 'sortrows' -- rows sort lexicographically in ascending order %!test %! [b, i] = sortrows (datetime (2021, 1, [3 1; 3 2; 1 5])); %! assert_equal (b.Day, [1 5; 3 1; 3 2]); %! assert_equal (i, [3; 1; 2]); ## Test 'sortrows' -- a bare direction applies to every column %!test %! [b, i] = sortrows (datetime (2021, 1, [3 1; 3 2; 1 5]), 'descend'); %! assert_equal (b.Day, [3 2; 3 1; 1 5]); %! assert_equal (i, [2; 1; 3]); ## Test 'sortrows' -- a column list selects and orders the sort keys %!assert_equal (sortrows (datetime (2021, 1, [3 1; 3 2; 1 5]), 2).Day, ... %! [3 1; 3 2; 1 5]) %!assert_equal (sortrows (datetime (2021, 1, [3 1; 3 2; 1 5]), [2 1]).Day, ... %! [3 1; 3 2; 1 5]) ## Test 'sortrows' -- a negative column sorts that key in descending order %!assert_equal (sortrows (datetime (2021, 1, [3 1; 3 2; 1 5]), [1 -2]).Day, ... %! [1 5; 3 2; 3 1]) ## Test 'sortrows' -- a cell array gives one direction per sort column %!assert_equal (sortrows (datetime (2021, 1, [3 1; 3 2; 1 5]), ... %! {'ascend', 'descend'}).Day, [1 5; 3 2; 3 1]) %!assert_equal (sortrows (datetime (2021, 1, [3 1; 3 2; 1 5]), [1 2], ... %! {'ascend', 'descend'}).Day, [1 5; 3 2; 3 1]) ## Test 'sortrows' -- Not-A-Time sorts as the largest value by default %!test %! N = datetime (2021, 1, [2 1; NaN 3; 2 5; 1 1; 2 1]); %! [b, i] = sortrows (N); %! assert_equal (i, [4; 1; 5; 3; 2]); %! assert_equal (isnat (b)(:, 1), logical ([0; 0; 0; 0; 1])); %! [b, i] = sortrows (N, 'descend'); %! assert_equal (i, [2; 3; 1; 5; 4]); ## Test 'sortrows' -- MissingPlacement overrides the NaT position %!test %! N = datetime (2021, 1, [2 1; NaN 3; 2 5; 1 1; 2 1]); %! bf = sortrows (N, 'MissingPlacement', 'first'); %! assert_equal (isnat (bf)(1, 1), true); %! bl = sortrows (N, 'MissingPlacement', 'last'); %! assert_equal (isnat (bl)(5, 1), true); ## Test 'sortrows' -- a single sort column keeps ties in original order %!test %! N = datetime (2021, 1, [2 1; NaN 3; 2 5; 1 1; 2 1]); %! [b, i] = sortrows (N, 1); %! assert_equal (i, [4; 1; 3; 5; 2]); ## Test 'sortrows' -- a column vector sorts as a single-column matrix %!test %! [b, i] = sortrows (datetime (2021, 1, [3; 1; 2])); %! assert_equal (i, [2; 3; 1]); ## Test 'sortrows' -- error branches %!error ... %! sortrows (datetime (2021, 1, [1 2]), 3); %!error ... %! sortrows (datetime (2021, 1, [1 2]), 'up'); %!error ... %! sortrows (datetime (2021, 1, [1 2]), [1 2], {'ascend'}); %!error ... %! sortrows (datetime (2021, 1, [1 2]), 'ascend', 'descend'); %!error ... %! sortrows (datetime (2021, 1, [1 2]), 'MissingPlacement', 'middle'); ## Test 'issortedrows' -- ascending lexicographic order is the default %!assert_equal (issortedrows (datetime (2021, 1, [3 1; 3 2; 1 5])), false) %!assert_equal (issortedrows (datetime (2021, 1, [1 5; 3 1; 3 2])), true) ## Test 'issortedrows' -- ties are resolved by later columns %!assert_equal (issortedrows (datetime (2021, 1, [1 1; 1 2; 2 0])), true) %!assert_equal (issortedrows (datetime (2021, 1, [1 2; 1 1])), false) ## Test 'issortedrows' -- a column list selects and orients the sort keys %!assert_equal (issortedrows (datetime (2021, 1, [3 1; 3 2; 1 5]), 2), true) %!assert_equal (issortedrows (datetime (2021, 1, [3 1; 3 2; 1 5]), [1 -2]), false) ## Test 'issortedrows' -- descend and monotonic directions %!assert_equal (issortedrows (datetime (2021, 1, [3 2; 3 1; 1 5]), 'descend'), true) %!assert_equal (issortedrows (datetime (2021, 1, [3; 2; 1]), 'monotonic'), true) %!assert_equal (issortedrows (datetime (2021, 1, [1; 2; 3]), 'monotonic'), true) ## Test 'issortedrows' -- strict checks only the first sort column %!assert_equal (issortedrows (datetime (2021, 1, [1 5; 2 5; 3 5]), 'strictascend'), true) %!assert_equal (issortedrows (datetime (2021, 1, [1 5; 2 5; 2 5]), 'strictascend'), false) %!assert_equal (issortedrows (datetime (2021, 1, [1 1; 1 2; 2 0]), 'strictascend'), false) %!assert_equal (issortedrows (datetime (2021, 1, [3 5; 2 5; 1 5]), 'strictdescend'), true) %!assert_equal (issortedrows (datetime (2021, 1, [1; 2; 3]), 'strictmonotonic'), true) ## Test 'issortedrows' -- strict is false when the first column has NaT %!assert_equal (issortedrows (datetime ([2021; 2022; NaN], 1, 1), 'strictascend'), false) %!assert_equal (issortedrows (datetime ([2021; 2022; NaN], 1, 1), 'ascend'), true) ## Test 'issortedrows' -- a cell array gives one direction per column %!assert_equal (issortedrows (datetime (2021, 1, [1 5; 3 2; 3 1]), ... %! {'ascend', 'descend'}), true) ## Test 'issortedrows' -- error branches %!error ... %! issortedrows (datetime (2021, 1, [1 2]), 3); %!error ... %! issortedrows (datetime (2021, 1, [1 2]), 'up'); %!error ... %! issortedrows (datetime (2021, 1, [1 2]), {'ascend'}); %!error ... %! issortedrows (datetime (2021, 1, [1 2]), 'ascend', 'descend'); ## Test 'min'/'max' -- reduce along the first non-singleton dimension %!test %! [m, i] = min (datetime (2021, 1, [3 1 2])); %! assert_equal (m.Day, 1); %! assert_equal (i, 2); %!test %! [m, i] = max (datetime (2021, 1, [3 1 2])); %! assert_equal (m.Day, 3); %! assert_equal (i, 1); ## Test 'min' -- NaT is omitted by default; ties return the first index %!test %! [m, i] = min (datetime (2021, 1, [3 NaN 1 2])); %! assert_equal (m.Day, 1); %! assert_equal (i, 3); %!test %! [m, i] = min (datetime (2021, 1, [2 1 1 3])); %! assert_equal (i, 2); ## Test 'min' -- 'includenan' propagates NaT %!assert_equal (isnat (min (datetime (2021, 1, [3 NaN 1]), [], 'includenan')), true) %!assert_equal (max (datetime (2021, 1, [3 NaN 1]), [], 'omitnan').Day, 3) ## Test 'min' -- a reduction over only NaT yields NaT at index 1 %!test %! [m, i] = min (datetime ([NaN NaN], 1, 1)); %! assert_equal (isnat (m), true); %! assert_equal (i, 1); ## Test 'min' -- matrix reduction along each dimension and over all elements %!assert_equal (min (datetime (2021, 1, [3 1; 2 4])).Day, [2 1]) %!assert_equal (min (datetime (2021, 1, [3 1; 2 4]), [], 2).Day, [1; 2]) %!assert_equal (min (datetime (2021, 1, [3 1; 2 4]), [], 'all').Day, 1) %!test %! [m, i] = min (datetime (2021, 1, [3 1; 2 4])); %! assert_equal (i, [2 1]); ## Test 'min'/'max' -- element-wise comparison of two arrays with broadcasting %!assert_equal (min (datetime (2021, 1, [1 5 3]), datetime (2021, 1, [4 2 3])).Day, [1 2 3]) %!assert_equal (max (datetime (2021, 1, [1 5 3]), datetime (2021, 1, [4 2 3])).Day, [4 5 3]) %!assert_equal (min (datetime (2021, 1, [1 5 3]), datetime (2021, 1, 3)).Day, [1 3 3]) ## Test 'min' -- element-wise NaT handling (omit versus include) %!assert_equal (min (datetime (2021, 1, [1 5 3]), ... %! datetime (2021, 1, [NaN 2 NaN])).Day, [1 2 3]) %!test %! m = min (datetime (2021, 1, [1 5 3]), ... %! datetime (2021, 1, [NaN 2 NaN]), 'includenan'); %! assert_equal (isnat (m), logical ([1 0 1])); %! assert_equal (m.Day(2), 2); ## Test 'min' -- reductions are by absolute instant and keep the time zone %!test %! z = datetime (2021, 1, [3 1 2], 'TimeZone', 'America/New_York'); %! [m, i] = min (z); %! assert_equal (m.Day, 1); %! assert_equal (i, 2); %! assert_equal (m.TimeZone, 'America/New_York'); ## Test 'min' -- an empty array reduces to an empty array %!assert_equal (size (min (datetime (2021, 1, []))), [0 0]) ## Test 'min' -- error branches %!error ... %! min (datetime (2021, 1, 1), 5); %!error ... %! [m, i] = min (datetime (2021, 1, 1), datetime (2021, 1, 2)); %!error ... %! min (datetime (2021, 1, 1), [], 0); %!error ... %! min (datetime (2021, 1, 1), [], 'foo'); %!error ... %! max (datetime (2021, 1, 1, 'TimeZone', 'UTC'), datetime (2021, 1, 2)); pr0m1th3as-datatypes-9c9a8d3/inst/tests/duration.m-tst000066400000000000000000005620141522766574100230170ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the datatypes package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ################################################################################ ## ** Create and convert 'duration' type ** ## ################################################################################ ## Available Methods ## ## ## ## 'duration' 'dispstrings' 'cellstr' 'char' ## ## 'datevec' 'hms' 'years' 'days' ## ## 'hours' 'minutes' 'seconds' 'milliseconds' ## ## ## ################################################################################ %!assert_equal (isscalar (duration ()), true) %!assert_equal (isempty (duration ()), false) %!assert_equal (class (duration), 'duration') %!assert_equal (dispstrings (duration ()), {'00:00:00'}) %!assert_equal (size (duration (zeros (4, 3))), [4, 1]) %!assert_equal (isempty (duration (ones (0, 3))), true) %!assert_equal (seconds (duration ([0, 0, 15])), 15) %!assert_equal (minutes (duration ([0, 50, 30])), 50.5) %!assert_equal (hours (duration ([1, 0, 3600; 5, 60, 0])), [2; 6]) %!assert_equal (seconds (duration ('30:15', 'InputFormat', 'mm:ss')), 1815) %!assert_equal (hours (duration ({'01:00', '02:30'}, 'InputFormat', 'hh:mm')), [1, 2.5]) %!assert_equal (minutes (duration ({'01:00:30'}, 'InputFormat', 'hh:mm:ss')), 60.5) %!assert_equal (minutes (duration ({'01:00:45'}, 'InputFormat', 'hh:mm:ss')), 60.75) %!assert_equal (days (duration ({'24:00:00'}, 'InputFormat', 'hh:mm:ss')), 1) %!assert_equal (days (duration ({'1:12:00:00'}, 'InputFormat', 'dd:hh:mm:ss')), 1.5) %!assert_equal (seconds (duration (0, 0, 15)), 15) %!assert_equal (seconds (duration (0, zeros (4), 15)), ones (4) * 15) %!assert_equal (minutes (duration (0, 50, 30)), 50.5) %!assert_equal (minutes (duration (0, 40:50, 30)), [40:50] + 0.5) %!assert_equal (seconds (duration (0, 0, 0, 15)), 0.015) %!assert_equal (size (duration (ones (2, 4, 3), 0, 0)), [2, 4, 3]) %!assert_equal (size (duration (0, ones (2, 3, 4), 0)), [2, 3, 4]) %!assert_equal (size (duration (zeros (4), zeros (4), 0, 0)), [4, 4]) %!assert_equal (size (duration (zeros (4), 0, 0, zeros (4))), [4, 4]) %!error ... %! duration (0, 0, 0, 'Format', 5) %!error ... %! duration (0, 0, 0, 'Format', {'mm:ss'}) %!error ... %! duration (0, 0, 0, 'Format', string ({'mm:ss', 'hh:mm:ss'})) %!error duration (0, 0, 0, 'Format', 't') %!error duration (0, 0, 0, 'Format', 'd.h.S') %!error duration (0, 0, 0, 'Format', 's.SS') %!error ... %! duration (0, 0, 0, 'Format', 'hh:mm.SS') %!error ... %! duration (0, 0, 0, 'Format', 'hh:mm.SM') %!error ... %! duration (0, 0, 0, 'Format', 'hh:mm:ss.SSSSSSSSSS') %!error ... %! duration (0, 0, 0, 'InputFormat', {'hh:mm'}) %!error ... %! duration (0, 0, 0, 'InputFormat', string ({'hh:mm', 'hh:mm'})) %!error duration (0, 0, 0, 'InputFormat', 'hh:mmm') %!error duration (0, 0, 0, 'InputFormat', string ('hh:mmm')) %!error ... %! duration (0, 0, 0, 'InputFormat', 'hh:mm.SSS') %!error ... %! duration (0, 0, 0, 'InputFormat', string ('hh:mm:ss.SSM')) %!error ... %! duration (0, 0, 0, 'InputFormat', string ('hh:mm:ss.SSSSSSSSSS')) %!error ... %! duration (0, 0, 0, 'InputFormat', 'hh:mm:ss.SSSSSSSSSS') %!error duration (ones (2, 2, 2)) %!error duration (ones (2, 3) * i) %!error duration (ones (2, 4)) %!error duration ({1}) %!error duration (struct ('a', 1)) %!error duration ('a', 0, 0) %!error duration (0, 'a', 0) %!error duration (0, 0, 'a') %!error duration (i, 0, 0) %!error duration (0, i, 0) %!error duration (0, 0, i) %!error ... %! duration (0, [0, 0], [0; 0]) %!error duration ('a', 0, 0, 0) %!error duration (0, 'a', 0, 0) %!error duration (0, 0, 'a', 0) %!error duration (0, 0, 0, 'a') %!error duration (i, 0, 0, 0) %!error duration (0, i, 0, 0) %!error duration (0, 0, i, 0) %!error duration (0, 0, 0, i) %!error ... %! duration (0, [0, 0], [0; 0], 0) %!error duration (1, 2, 3, 4, 5) %!warning ... %! duration ([0, 0, 0], 'InputFormat', 'hh:mm'); %!warning ... %! duration (0, 0, 0, 'InputFormat', 'hh:mm'); %!warning ... %! duration (0, 0, 0, 0, 'InputFormat', 'hh:mm'); %!assert_equal (dispstrings (years (1)), {'1 yr'}) %!assert_equal (dispstrings (years (2)), {'2 yrs'}) %!assert_equal (dispstrings (years ([1, 2, 2.4])), {'1 yr', '2 yrs', '2.4 yrs'}) %!assert_equal (dispstrings (years ([1; 2; 2.4])), {'1 yr'; '2 yrs'; '2.4 yrs'}) %!assert_equal (dispstrings (days (1)), {'1 day'}) %!assert_equal (dispstrings (days (2)), {'2 days'}) %!assert_equal (dispstrings (days ([1, 2, 2.4])), {'1 day', '2 days', '2.4 days'}) %!assert_equal (dispstrings (days ([1; 2; 2.4])), {'1 day'; '2 days'; '2.4 days'}) %!assert_equal (dispstrings (duration (36, 0, 0)), {'36:00:00'}) %!assert_equal (dispstrings (duration (-36, 0, 0)), {'-36:00:00'}) %!assert_equal (dispstrings (duration (36, 0, 0, 'Format', 'y')), {'0.00410686 yrs'}) %!assert_equal (dispstrings (duration (36, 0, 0, 'Format', 'd')), {'1.5 days'}) %!assert_equal (dispstrings (duration (36, 0, 0, 'Format', 'h')), {'36 hr'}) %!assert_equal (dispstrings (duration (36, 0, 0, 'Format', 'm')), {'2160 min'}) %!assert_equal (dispstrings (duration (36, 0, 0, 'Format', 's')), {'129600 sec'}) %!assert_equal (dispstrings (duration (36, 0, 0, 'Format', 'dd:hh:mm:ss.SSSSS')), {'01:12:00:00.00000'}) %!assert_equal (dispstrings (duration (36, 0, 0, 'Format', 'hh:mm:ss.SSSSS')), {'36:00:00.00000'}) %!assert_equal (dispstrings (duration (36, 0, 0, 'Format', 'hh:mm:ss.SSSS')), {'36:00:00.0000'}) %!assert_equal (dispstrings (duration (36, 0, 0, 'Format', 'hh:mm:ss.SSS')), {'36:00:00.000'}) %!assert_equal (dispstrings (duration (36, 0, 0, 'Format', 'hh:mm:ss.SS')), {'36:00:00.00'}) %!assert_equal (dispstrings (duration (36, 0, 0, 'Format', 'hh:mm:ss.S')), {'36:00:00.0'}) %!assert_equal (dispstrings (duration (36, 0, 0, 'Format', 'hh:mm')), {'36:00'}) %!assert_equal (dispstrings (duration (36, 0, 0.025, 'Format', 'hh:mm')), {'36:00'}) %!assert_equal (dispstrings (duration (36, 0, 55.025, 'Format', 'hh:mm')), {'36:00'}) %!assert_equal (dispstrings (duration (36, 0, 0, 'Format', 'mm:ss.SSSSS')), {'2160:00.00000'}) %!assert_equal (dispstrings (duration (36, 0, 0, 'Format', 'mm:ss.SSSSSS')), {'2160:00.000000'}) %!assert_equal (dispstrings (duration (36, 0, 0, 'Format', 'mm:ss.SSSSSSS')), {'2160:00.0000000'}) %!assert_equal (dispstrings (duration (36, 0, 0, 'Format', 'mm:ss.SSSSSSSS')), {'2160:00.00000000'}) %!assert_equal (dispstrings (duration (36, 0, 0, 'Format', 'mm:ss.SSSSSSSSS')), {'2160:00.000000000'}) %!assert_equal (dispstrings (duration (0, 36, 0)), {'00:36:00'}) %!assert_equal (dispstrings (duration (0, 36, 0, 'Format', 'y')), {'6.84477e-05 yrs'}) %!assert_equal (dispstrings (duration (0, 36, 0, 'Format', 'd')), {'0.025 days'}) %!assert_equal (dispstrings (duration (0, 36, 0, 'Format', 'h')), {'0.6 hr'}) %!assert_equal (dispstrings (duration (0, 36, 0, 'Format', 'm')), {'36 min'}) %!assert_equal (dispstrings (duration (0, 36, 0, 'Format', 's')), {'2160 sec'}) %!assert_equal (dispstrings (duration (0, 36, 0.025, 'Format', 'dd:hh:mm:ss.SSSSS')), {'00:36:00.02500'}) %!assert_equal (dispstrings (duration (0, 36, 0.025, 'Format', 'hh:mm:ss.SSSSS')), {'00:36:00.02500'}) %!assert_equal (dispstrings (duration (0, 36, 0.025, 'Format', 'hh:mm:ss.SSSS')), {'00:36:00.0250'}) %!assert_equal (dispstrings (duration (0, 36, 0.025, 'Format', 'hh:mm:ss.SSS')), {'00:36:00.025'}) %!assert_equal (dispstrings (duration (0, 36, 0.025, 'Format', 'hh:mm:ss.SS')), {'00:36:00.02'}) %!assert_equal (dispstrings (duration (0, 36, 0.025, 'Format', 'hh:mm:ss.S')), {'00:36:00.0'}) %!assert_equal (dispstrings (duration (0, 36, 0.025, 'Format', 'hh:mm')), {'00:36'}) %!assert_equal (dispstrings (duration (0, 36, 5.025, 'Format', 'hh:mm')), {'00:36'}) %!assert_equal (dispstrings (duration (0, 36, 0.025, 'Format', 'mm:ss.SSSSS')), {'36:00.02500'}) %!assert_equal (dispstrings (duration (0, 36, 0.025, 'Format', 'mm:ss.SSSSSS')), {'36:00.025000'}) %!assert_equal (dispstrings (duration (0, 36, 0.025, 'Format', 'mm:ss.SSSSSSS')), {'36:00.0250000'}) %!assert_equal (dispstrings (duration (0, 36, 0.025, 'Format', 'mm:ss.SSSSSSSS')), {'36:00.02500000'}) %!assert_equal (dispstrings (duration (0, 36, 0.025, 'Format', 'mm:ss.SSSSSSSSS')), {'36:00.025000000'}) %!test %! D = duration ([1, 2.3, NaN, Inf, -Inf], 0, 0, 'Format', 'y'); %! assert_equal (dispstrings (D), {'0.000114079 yrs', '0.000262383 yrs', 'NaN yrs', 'Inf yrs', '-Inf yrs'}); %!test %! D = duration ([1, 2.3, NaN, Inf, -Inf], 0, 0, 'Format', 'd'); %! assert_equal (dispstrings (D), {'0.0416667 days', '0.0958333 days', 'NaN days', 'Inf days', '-Inf days'}); %!test %! D = duration ([1, 2.3, NaN, Inf, -Inf], 0, 0, 'Format', 'h'); %! assert_equal (dispstrings (D), {'1 hr', '2.3 hr', 'NaN hr', 'Inf hr', '-Inf hr'}); %!test %! D = duration ([1, 2.3, NaN, Inf, -Inf], 0, 0, 'Format', 'm'); %! assert_equal (dispstrings (D), {'60 min', '138 min', 'NaN min', 'Inf min', '-Inf min'}); %!test %! D = duration ([1, 2.3, NaN, Inf, -Inf], 0, 0, 'Format', 's'); %! assert_equal (dispstrings (D), {'3600 sec', '8280 sec', 'NaN sec', 'Inf sec', '-Inf sec'}); %!test %! D = duration ([1, 2.3, NaN, Inf, -Inf], 0, 0, 'Format', 'hh:mm'); %! assert_equal (dispstrings (D), {'01:00', '02:18', 'NaN', 'Inf', '-Inf'}); %!test %! D = duration ([1, 2.3, NaN, Inf, -Inf], 0, 0, 'Format', 'mm:ss'); %! assert_equal (dispstrings (D), {'60:00', '138:00', 'NaN', 'Inf', '-Inf'}); %!test %! D = duration ([1, 2.3, NaN, Inf, -Inf], 0, 0, 'Format', 'hh:mm:ss'); %! assert_equal (dispstrings (D), {'01:00:00', '02:18:00', 'NaN', 'Inf', '-Inf'}); %!test %! D = duration ([1, 2.3, NaN, Inf, -Inf], 0, 0, 'Format', 'hh:mm:ss.SS'); %! assert_equal (dispstrings (D), {'01:00:00.00', '02:18:00.00', 'NaN', 'Inf', '-Inf'}); %!test %! D = duration ([1, 2.3, NaN, Inf, -Inf], 0, 0, 'Format', 'dd:hh:mm:ss'); %! assert_equal (dispstrings (D), {'01:00:00', '02:18:00', 'NaN', 'Inf', '-Inf'}); %!test %! D = duration ([1, 2.3, NaN, Inf, -Inf], 0, 0, 'Format', 'dd:hh:mm:ss.SS'); %! assert_equal (dispstrings (D), {'01:00:00.00', '02:18:00.00', 'NaN', 'Inf', '-Inf'}); %!test %! D = duration ([24.001, 2.3001, NaN, Inf, -Inf], 0, 0, 'Format', 'dd:hh:mm:ss'); %! assert_equal (dispstrings (D), {'01:00:00:03', '02:18:00', 'NaN', 'Inf', '-Inf'}); %!test %! D = duration ([24.001, 2.3001, NaN, Inf, -Inf], 0, 0, 'Format', 'dd:hh:mm:ss.SSS'); %! assert_equal (dispstrings (D), {'01:00:00:03.600', '02:18:00.360', 'NaN', 'Inf', '-Inf'}); %!assert_equal (cellstr (years (1)), {'1 yr'}) %!assert_equal (cellstr (years (1), 'd'), {'365.243 days'}) %!assert_equal (cellstr (years (2)), {'2 yrs'}) %!assert_equal (cellstr (years (2), 'd'), {'730.485 days'}) %!assert_equal (cellstr (days (1)), {'1 day'}) %!assert_equal (cellstr (days (1), 'y'), {'0.00273791 yrs'}) %!assert_equal (cellstr (days (2), 'm'), {'2880 min'}) %!assert_equal (cellstr (duration (36, 0, 0)), {'36:00:00'}) %!assert_equal (cellstr (duration (36, 0, 0), 'y'), {'0.00410686 yrs'}) %!assert_equal (cellstr (duration (36, 0, 0), 'd'), {'1.5 days'}) %!assert_equal (cellstr (duration (36, 0, 0), 'h'), {'36 hr'}) %!assert_equal (cellstr (duration (36, 0, 0), 'm'), {'2160 min'}) %!assert_equal (cellstr (duration (36, 0, 0), 's'), {'129600 sec'}) %!assert_equal (cellstr (duration (36, 0, 0), 'dd:hh:mm:ss.SSSSS'), {'01:12:00:00.00000'}) %!assert_equal (cellstr (duration (36, 0, 0), 'hh:mm:ss.SSSSS'), {'36:00:00.00000'}) %!assert_equal (cellstr (duration (36, 0, 0), 'hh:mm:ss.SSSS'), {'36:00:00.0000'}) %!assert_equal (cellstr (duration (36, 0, 0), 'hh:mm:ss.SSS'), {'36:00:00.000'}) %!assert_equal (cellstr (duration (36, 0, 0), 'hh:mm:ss.SS'), {'36:00:00.00'}) %!assert_equal (cellstr (duration (36, 0, 0), 'hh:mm:ss.S'), {'36:00:00.0'}) %!assert_equal (cellstr (duration (36, 0, 0), 'hh:mm'), {'36:00'}) %!assert_equal (cellstr (duration (36, 0, 0.025), 'hh:mm'), {'36:00'}) %!assert_equal (cellstr (duration (36, 0, 55.025), 'hh:mm'), {'36:00'}) %!assert_equal (cellstr (duration (36, 0, 0), 'mm:ss.SSSSS'), {'2160:00.00000'}) %!assert_equal (cellstr (duration (36, 0, 0), 'mm:ss.SSSSSS'), {'2160:00.000000'}) %!assert_equal (cellstr (duration (36, 0, 0), 'mm:ss.SSSSSSS'), {'2160:00.0000000'}) %!assert_equal (cellstr (duration (36, 0, 0), 'mm:ss.SSSSSSSS'), {'2160:00.00000000'}) %!assert_equal (cellstr (duration (36, 0, 0), 'mm:ss.SSSSSSSSS'), {'2160:00.000000000'}) %!assert_equal (cellstr (duration (0, 36, 0)), {'00:36:00'}) %!assert_equal (cellstr (duration (0, 36, 0), 'y'), {'6.84477e-05 yrs'}) %!assert_equal (cellstr (duration (0, 36, 0), 'd'), {'0.025 days'}) %!assert_equal (cellstr (duration (0, 36, 0), 'h'), {'0.6 hr'}) %!assert_equal (cellstr (duration (0, 36, 0), 'm'), {'36 min'}) %!assert_equal (cellstr (duration (0, 36, 0), 's'), {'2160 sec'}) %!assert_equal (cellstr (duration (0, 36, 0.025), 'dd:hh:mm:ss.SSSSS'), {'00:36:00.02500'}) %!assert_equal (cellstr (duration (0, 36, 0.025), 'hh:mm:ss.SSSSS'), {'00:36:00.02500'}) %!assert_equal (cellstr (duration (0, 36, 0.025), 'hh:mm:ss.SSSS'), {'00:36:00.0250'}) %!assert_equal (cellstr (duration (0, 36, 0.025), 'hh:mm:ss.SSS'), {'00:36:00.025'}) %!assert_equal (cellstr (duration (0, 36, 0.025), 'hh:mm:ss.SS'), {'00:36:00.02'}) %!assert_equal (cellstr (duration (0, 36, 0.025), 'hh:mm:ss.S'), {'00:36:00.0'}) %!assert_equal (cellstr (duration (0, 36, 0.025), 'hh:mm'), {'00:36'}) %!assert_equal (cellstr (duration (0, 36, 5.025), 'hh:mm'), {'00:36'}) %!assert_equal (cellstr (duration (0, 36, 0.025), 'mm:ss.SSSSS'), {'36:00.02500'}) %!assert_equal (cellstr (duration (0, 36, 0.025), 'mm:ss.SSSSSS'), {'36:00.025000'}) %!assert_equal (cellstr (duration (0, 36, 0.025), 'mm:ss.SSSSSSS'), {'36:00.0250000'}) %!assert_equal (cellstr (duration (0, 36, 0.025), 'mm:ss.SSSSSSSS'), {'36:00.02500000'}) %!assert_equal (cellstr (duration (0, 36, 0.025), 'mm:ss.SSSSSSSSS'), {'36:00.025000000'}) %!error ... %! cellstr (days (2), string ({'m', 'y'})) %!error ... %! cellstr (days (2), ['m'; 'y']) %!error ... %! cellstr (days (2), {'m'}) %!error ... %! cellstr (days (2), 2) %!error cellstr (duration (0, 0, 0), 't') %!error cellstr (duration (0, 0, 0), 'd.h.S') %!error cellstr (duration (0, 0, 0), 's.SS') %!error ... %! cellstr (duration (0, 0, 0), 'hh:mm.SS') %!error ... %! cellstr (duration (0, 0, 0), 'hh:mm.SM') %!error ... %! cellstr (duration (0, 0, 0), 'hh:mm:ss.SSSSSSSSSS') %!assert_equal (char (years (1)), '1 yr') %!assert_equal (char (years ([1, 2])), ['1 yr '; '2 yrs']) %!assert_equal (char (years (1), 'd'), '365.243 days') %!assert_equal (char (years (2)), '2 yrs') %!assert_equal (char (years (2), 'd'), '730.485 days') %!assert_equal (char (days (1)), '1 day') %!assert_equal (char (days ([1; 2])), ['1 day '; '2 days']) %!assert_equal (char (days (1), 'y'), '0.00273791 yrs') %!assert_equal (char (days (2), 'm'), '2880 min') %!assert_equal (char (duration (36, 0, 0)), '36:00:00') %!assert_equal (char (duration (36, 0, 0), 'y'), '0.00410686 yrs') %!assert_equal (char (duration (36, 0, 0), 'd'), '1.5 days') %!assert_equal (char (duration (36, 0, 0), 'h'), '36 hr') %!assert_equal (char (duration (36, 0, 0), 'm'), '2160 min') %!assert_equal (char (duration (36, 0, 0), 's'), '129600 sec') %!assert_equal (char (duration (36, 0, 0), 'dd:hh:mm:ss.SSSSS'), '01:12:00:00.00000') %!assert_equal (char (duration (36, 0, 0), 'hh:mm:ss.SSSSS'), '36:00:00.00000') %!assert_equal (char (duration (36, 0, 0), 'hh:mm:ss.SSSS'), '36:00:00.0000') %!assert_equal (char (duration (36, 0, 0), 'hh:mm:ss.SSS'), '36:00:00.000') %!assert_equal (char (duration (36, 0, 0), 'hh:mm:ss.SS'), '36:00:00.00') %!assert_equal (char (duration (36, 0, 0), 'hh:mm:ss.S'), '36:00:00.0') %!assert_equal (char (duration (36, 0, 0), 'hh:mm'), '36:00') %!assert_equal (char (duration (36, 0, 0.025), 'hh:mm'), '36:00') %!assert_equal (char (duration (36, 0, 55.025), 'hh:mm'), '36:00') %!assert_equal (char (duration (36, 0, 0), 'mm:ss.SSSSS'), '2160:00.00000') %!assert_equal (char (duration (36, 0, 0), 'mm:ss.SSSSSS'), '2160:00.000000') %!assert_equal (char (duration (36, 0, 0), 'mm:ss.SSSSSSS'), '2160:00.0000000') %!assert_equal (char (duration (36, 0, 0), 'mm:ss.SSSSSSSS'), '2160:00.00000000') %!assert_equal (char (duration (36, 0, 0), 'mm:ss.SSSSSSSSS'), '2160:00.000000000') %!assert_equal (char (duration (0, 36, 0)), '00:36:00') %!assert_equal (char (duration (0, 36, 0), 'y'), '6.84477e-05 yrs') %!assert_equal (char (duration (0, 36, 0), 'd'), '0.025 days') %!assert_equal (char (duration (0, 36, 0), 'h'), '0.6 hr') %!assert_equal (char (duration (0, 36, 0), 'm'), '36 min') %!assert_equal (char (duration (0, 36, 0), 's'), '2160 sec') %!assert_equal (char (duration (0, 36, 0.025), 'dd:hh:mm:ss.SSSSS'), '00:36:00.02500') %!assert_equal (char (duration (0, 36, 0.025), 'hh:mm:ss.SSSSS'), '00:36:00.02500') %!assert_equal (char (duration (0, 36, 0.025), 'hh:mm:ss.SSSS'), '00:36:00.0250') %!assert_equal (char (duration (0, 36, 0.025), 'hh:mm:ss.SSS'), '00:36:00.025') %!assert_equal (char (duration (0, 36, 0.025), 'hh:mm:ss.SS'), '00:36:00.02') %!assert_equal (char (duration (0, 36, 0.025), 'hh:mm:ss.S'), '00:36:00.0') %!assert_equal (char (duration (0, 36, 0.025), 'hh:mm'), '00:36') %!assert_equal (char (duration (0, 36, 5.025), 'hh:mm'), '00:36') %!assert_equal (char (duration (0, 36, 0.025), 'mm:ss.SSSSS'), '36:00.02500') %!assert_equal (char (duration (0, 36, 0.025), 'mm:ss.SSSSSS'), '36:00.025000') %!assert_equal (char (duration (0, 36, 0.025), 'mm:ss.SSSSSSS'), '36:00.0250000') %!assert_equal (char (duration (0, 36, 0.025), 'mm:ss.SSSSSSSS'), '36:00.02500000') %!assert_equal (char (duration (0, 36, 0.025), 'mm:ss.SSSSSSSSS'), '36:00.025000000') %!error ... %! char (days (2), string ({'m', 'y'})) %!error ... %! char (days (2), ['m'; 'y']) %!error ... %! char (days (2), {'m'}) %!error ... %! char (days (2), 2) %!error char (duration (0, 0, 0), 't') %!error char (duration (0, 0, 0), 'd.h.S') %!error char (duration (0, 0, 0), 's.SS') %!error ... %! char (duration (0, 0, 0), 'hh:mm.SS') %!error ... %! char (duration (0, 0, 0), 'hh:mm.SM') %!error ... %! char (duration (0, 0, 0), 'hh:mm:ss.SSSSSSSSSS') %!test %! [y, mo, d, h, mi, s] = datevec (years (1)); %! assert_equal (y, 1); %! assert_equal (mo, 0); %! assert_equal (d, 0); %! assert_equal (h, 0); %! assert_equal (mi, 0); %! assert_equal (s, 0); %!test %! [y, mo, d, h, mi, s] = datevec (years (1.0258123)); %! assert_equal (y, 1); %! assert_equal (mo, 0); %! assert_equal (d, 9); %! assert_equal (h, 10); %! assert_equal (mi, 15); %! assert_equal (s, 57.5121, 1E-4); %!test %! [y, mo, d, h, mi, s] = datevec (years (1.5)); %! assert_equal (y, 1); %! assert_equal (mo, 0); %! assert_equal (d, 182); %! assert_equal (h, 14); %! assert_equal (mi, 54); %! assert_equal (s, 36, 1e-8); %!test %! [y, mo, d, h, mi, s] = datevec (days (1)); %! assert_equal (y, 0); %! assert_equal (mo, 0); %! assert_equal (d, 1); %! assert_equal (h, 0); %! assert_equal (mi, 0); %! assert_equal (s, 0); %!test %! [y, mo, d, h, mi, s] = datevec (days (1.0258123)); %! assert_equal (y, 0); %! assert_equal (mo, 0); %! assert_equal (d, 1); %! assert_equal (h, 0); %! assert_equal (mi, 37); %! assert_equal (s, 10.1827, 1E-4); %!test %! [y, mo, d, h, mi, s] = datevec (days (1.5)); %! assert_equal (y, 0); %! assert_equal (mo, 0); %! assert_equal (d, 1); %! assert_equal (h, 12); %! assert_equal (mi, 0); %! assert_equal (s, 0, 1e-8); %!assert_equal (datevec (years (1)), [1, 0, 0, 0, 0, 0]) %!assert_equal (datevec (years (1.0258123)), [1, 0, 9, 10, 15, 57.5121], 1e-4) %!assert_equal (datevec (years (1.5)), [1, 0, 182, 14, 54, 36], 1e-8) %!assert_equal (datevec (days (1)), [0, 0, 1, 0, 0, 0]) %!assert_equal (datevec (days (1.0258123)), [0, 0, 1, 0, 37, 10.1827], 1e-4) %!assert_equal (datevec (days (1.5)), [0, 0, 1, 12, 0, 0]) %!test %! [y, mo, d, h, mi, s] = datevec (days (magic (3))); %! assert_equal (y, zeros (3)); %! assert_equal (mo, zeros (3)); %! assert_equal (d, magic (3)); %! assert_equal (h, zeros (3)); %! assert_equal (mi, zeros (3)); %! assert_equal (s, zeros (3)); %!test %! DV = datevec (days (magic (3))); %! out = zeros (9, 6); %! out(:,3) = magic (3)(:); %! assert_equal (DV, out); %!test %! [y, mo, d, h, mi, s] = datevec (days (ones (2, 3, 4))); %! assert_equal (y, zeros (2, 3, 4)); %! assert_equal (mo, zeros (2, 3, 4)); %! assert_equal (d, ones (2, 3, 4)); %! assert_equal (h, zeros (2, 3, 4)); %! assert_equal (mi, zeros (2, 3, 4)); %! assert_equal (s, zeros (2, 3, 4)); %!test %! DV = datevec (days (ones (2, 3, 4))); %! out = zeros (24, 6); %! out(:,3) = ones (24, 1); %! assert_equal (DV, out); %!test %! DV = datevec (years (reshape ([1:24], 2, 3, 4))); %! out = zeros (24, 6); %! out(:,1) = [1:24]'; %! assert_equal (DV, out); %!test %! DV = datevec (days (reshape ([1:24], 2, 3, 4))); %! out = zeros (24, 6); %! out(:,3) = [1:24]'; %! assert_equal (DV, out); %!test %! DV = datevec (hours (reshape ([1:24], 2, 3, 4))); %! out = zeros (24, 6); %! out(:,4) = [1:23,0]'; %! out(24,3) = 1; %! assert_equal (DV, out); %!test %! DV = datevec (minutes (reshape ([1:24], 2, 3, 4))); %! out = zeros (24, 6); %! out(:,5) = [1:24]'; %! assert_equal (DV, out); %!test %! DV = datevec (seconds (reshape ([1:24], 2, 3, 4))); %! out = zeros (24, 6); %! out(:,6) = [1:24]'; %! assert_equal (DV, out, 2e-15); %!test %! DV = datevec (milliseconds (reshape ([1:24], 2, 3, 4))); %! out = zeros (24, 6); %! out(:,6) = 1e-3 * [1:24]'; %! assert_equal (DV, out, 2e-15); %!test %! X = reshape ([1:24], 2, 3, 4); %! X([12,17]) = NaN; %! DV = datevec (days (X)); %! out = zeros (24, 6); %! out(:, 3) = [1:24]'; %! out([12,17],:) = NaN; %! assert_equal (DV, out); %!test %! X = reshape ([1:24], 2, 3, 4); %! X([12,17]) = NaN; %! [y, mo, d, h, mi, s] = datevec (days (X)); %! y_out = mo_out = h_out = mi_out = s_out = zeros (2, 3, 4); %! d_out = X; %! y_out([12,17]) = mo_out([12,17]) = NaN; %! h_out([12,17]) = mi_out([12,17]) = s_out([12,17]) = NaN; %! assert_equal (y, y_out); %! assert_equal (mo, mo_out); %! assert_equal (d, d_out); %! assert_equal (h, h_out); %! assert_equal (mi, mi_out); %! assert_equal (s, s_out); %!assert_equal (hms (hours ([1:5])), [1:5]) %!assert_equal (hms (days ([1:5])), 24 * [1:5]) %!assert_equal (hms (hours (reshape ([1:24], 2, 3, 4))), reshape ([1:24], 2, 3, 4)) %!test %! [h, m, s] = hms (days (0.25 * reshape ([1:24], 2, 3, 4))); %! assert_equal (h, 6 * reshape ([1:24], 2, 3, 4)); %! assert_equal (m, zeros (2, 3, 4)); %! assert_equal (s, zeros (2, 3, 4)); %!test %! [h, m, s] = hms (years (0.25 * reshape ([1:24], 2, 3, 4))); %! h_out = fix (hours (years (0.25 * reshape ([1:24], 2, 3, 4)))); %! assert_equal (h, h_out); %! m_out = fix (minutes (years (0.25 * reshape ([1:24], 2, 3, 4)))) - h_out * 60; %! assert_equal (m, m_out); %! s_out = round (seconds (years (0.25 * reshape ([1:24], 2, 3, 4)))); %! assert_equal (s, s_out - (h_out * 3600 + m_out * 60), 5e-8); %!test %! [h, m, s] = hms (hours ([1:5])); %! assert_equal (m, zeros (1, 5)); %! assert_equal (s, zeros (1, 5)); %!test %! [h, m, s] = hms (minutes ([1:5])); %! assert_equal (h, zeros (1, 5)); %! assert_equal (m, [1:5]); %! assert_equal (s, zeros (1, 5)); %!test %! [h, m, s] = hms (seconds ([1:5])); %! assert_equal (h, zeros (1, 5)); %! assert_equal (m, zeros (1, 5)); %! assert_equal (s, [1:5]); %!error [h, m, s, ms] = hms (days (1)) ################################################################################ ## ** Summary Information ** ## ################################################################################ ## Available Methods ## ## ## ## 'size' 'ndims' 'numel' 'nnz' ## ## 'length' 'keyHash' ## ## ## ################################################################################ %!assert_equal (size (duration), [1, 1]) %!assert_equal (size (days ([])), [0, 0]) %!assert_equal (size (days (ones (2, 0, 3, 4))), [2, 0, 3, 4]) %!assert_equal (size (duration (ones (2, 4), 0, 0)), [2, 4]) %!assert_equal (size (duration (ones (2, 4, 1, 1), 0, 0)), [2, 4]) %!assert_equal (size (duration (ones (2, 4, 1, 0), 0, 0)), [2, 4, 1, 0]) %!assert_equal (size (duration (zeros (6, 3))), [6, 1]) %!assert_equal (ndims (duration), 2) %!assert_equal (ndims (days ([])), 2) %!assert_equal (ndims (days (ones (2, 0, 3, 4))), 4) %!assert_equal (ndims (duration (ones (2, 4), 0, 0)), 2) %!assert_equal (ndims (duration (zeros (6, 3))), 2) %!assert_equal (numel (duration), 1) %!assert_equal (numel (days ([])), 0) %!assert_equal (numel (days (ones (2, 0, 3, 4))), 0) %!assert_equal (numel (duration (ones (2, 4), 0, 0)), 8) %!assert_equal (numel (duration (zeros (6, 3))), 6) %!assert_equal (nnz (days ([])), 0) %!assert_equal (nnz (duration), 0) %!assert_equal (nnz (days (ones (2, 0, 3, 4))), 0) %!assert_equal (nnz (days (zeros (2, 0, 3, 4))), 0) %!assert_equal (nnz (days (zeros (2, 1, 3, 4))), 0) %!assert_equal (nnz (days (ones (2, 1, 3, 4))), 24) %!assert_equal (nnz (days ([1, 4, 0, 9, 0, 8])), 4) %!assert_equal (nnz (days ([1, 4, 0; 9, 0, 8])), 4) %!assert_equal (length (duration (1, 1, 1)), 1) %!assert_equal (length (duration (0, 0, 0)), 1) %!assert_equal (length (duration (0:5, 0, 0)), 6) %!assert_equal (length (duration ([0, 0, 0, 1, 1], 0, 0)), 5) %!assert_equal (length (duration (ones (2, 3, 4), 0, 0)), 4) %!assert_equal (length (duration (ones (2, 10, 4), 0, 0)), 10) %!assert_equal (keyHash (duration), uint64 (8095976253982599603)) %!assert_equal (keyHash (duration (0, 0, 0)), uint64 (8095976253982599603)) %!assert_equal (keyHash (duration ([], [], [])), uint64 (14859204025010592219)) %!assert_equal (keyHash (duration (1, 1, 1)), uint64 (13776124020415645612)) %!test %! key = keyHash (duration ([1, 2; 3, 4], 1, 1)); %! assert_equal (key, uint64 (9389203521563080232)); %!test %! key = keyHash (duration (0, 0, 0, [1, 2, 3])); %! assert_equal (key, uint64 (7422929858764574817)); %!test %! base_key = uint64 (2342124352342344234); %! key = keyHash (duration (0, 0, 0, [1, 2, 3]), base_key); %! assert_equal (key, uint64 (13938388645620099040)); %!error ... %! keyHash (duration (0, 0, 0), uint64 ([1, 2])) %!error ... %! keyHash (duration (0, 0, 0), 2231107818551636405) ################################################################################ ## ** Query Operations ** ## ################################################################################ ## Available Methods ## ## ## ## 'isbetween' 'iscolumn' 'isempty' 'isequal' ## ## 'isequaln' 'isfinite' 'isinf' 'ismatrix' ## ## 'ismember' 'ismissing' 'isnan' 'isregular' ## ## 'isrow' 'isscalar' 'issorted' 'issortedrows' ## ## 'isvector' ## ## ## ################################################################################ %!test %! TF = isbetween (days ([0:5]), days (2), days (3)); %! assert_equal (TF, logical ([0, 0, 1, 1, 0, 0,])); %!test %! TF = isbetween (days ([0:5]), duration ('02:00:00:00'), days (3)); %! assert_equal (TF, logical ([0, 0, 1, 1, 0, 0,])); %!test %! TF = isbetween (days ([0:5]), duration ("1:00:00:00"), days (3)); %! assert_equal (TF, logical ([0, 1, 1, 1, 0, 0,])); %!test %! TF = isbetween (days ([0:5]), duration ("1:00:00:00"), days (4)); %! assert_equal (TF, logical ([0, 1, 1, 1, 1, 0,])); %!test %! TF = isbetween (days ([0:5]), days (2), days (3), 'open'); %! assert_equal (TF, false (1, 6)); %!test %! TF = isbetween (days ([0:5]), duration ('02:00:00:00'), days (3), 'open'); %! assert_equal (TF, false (1, 6)); %!test %! TF = isbetween (days ([0:5]), duration ('01:00:00:00'), days (3), 'open'); %! assert_equal (TF, logical ([0, 0, 1, 0, 0, 0,])); %!test %! TF = isbetween (days ([0:5]), duration ('01:00:00:00'), days (4), 'open'); %! assert_equal (TF, logical ([0, 0, 1, 1, 0, 0,])); %!test %! TF = isbetween (days ([0:5]), days (2), hours (72), 'open'); %! assert_equal (TF, false (1, 6)); %!test %! TF = isbetween (days ([0:5]), days (2), days (3), 'openleft'); %! assert_equal (TF, logical ([0, 0, 0, 1, 0, 0,])); %!test %! TF = isbetween (days ([0:5]), duration ('02:00:00:00'), days (3), 'openleft'); %! assert_equal (TF, logical ([0, 0, 0, 1, 0, 0,])); %!test %! TF = isbetween (days ([0:5]), duration ('01:00:00:00'), days (3), 'openleft'); %! assert_equal (TF, logical ([0, 0, 1, 1, 0, 0,])); %!test %! TF = isbetween (days ([0:5]), duration ('01:00:00:00'), days (4), 'openleft'); %! assert_equal (TF, logical ([0, 0, 1, 1, 1, 0,])); %!test %! TF = isbetween (days ([0:5]), days (2), hours (72), 'openleft'); %! assert_equal (TF, logical ([0, 0, 0, 1, 0, 0,])); %!test %! TF = isbetween (days ([0:5]), days (2), days (3), 'closedleft'); %! assert_equal (TF, logical ([0, 0, 1, 0, 0, 0,])); %!test %! TF = isbetween (days ([0:5]), duration ('02:00:00:00'), days (3), 'closedleft'); %! assert_equal (TF, logical ([0, 0, 1, 0, 0, 0,])); %!test %! TF = isbetween (days ([0:5]), duration ('01:00:00:00'), days (3), 'closedleft'); %! assert_equal (TF, logical ([0, 1, 1, 0, 0, 0,])); %!test %! TF = isbetween (days ([0:5]), duration ('01:00:00:00'), days (4), 'closedleft'); %! assert_equal (TF, logical ([0, 1, 1, 1, 0, 0,])); %!test %! TF = isbetween (days ([0:5]), days (2), hours (72), 'closedleft'); %! assert_equal (TF, logical ([0, 0, 1, 0, 0, 0,])); %!test %! TF = isbetween (days ([0:5]),'1:23:00:00', hours (72)); %! assert_equal (TF, logical ([0, 0, 1, 1, 0, 0,])); %!assert_equal (isbetween (years (1), years (1), years (1)), true) %!error ... %! isbetween (days (1), days (1)) %!error ... %! isbetween (days (1), days (1), days (1), 'open', 'OutputFormat', 'logical') %!error ... %! isbetween (days (1), 1, 2) %!error ... %! isbetween (days (1), days (1), days (1), 'asd') %!assert_equal (iscolumn (days ([1, 2, 3, 4])), false) %!assert_equal (iscolumn (days ([1; 2; 3; 4])), true) %!assert_equal (iscolumn (days ([1, 2; 3, 4])), false) %!assert_equal (iscolumn (days ([])), false) %!assert_equal (iscolumn (days (ones (1, 0))), false) %!assert_equal (iscolumn (days (ones (0, 1))), true) %!assert_equal (iscolumn (days (1)), true) %!assert_equal (iscolumn (days (ones (2, 3, 4))), false) %!assert_equal (isempty (days ([])), true) %!assert_equal (isempty (days (ones (2, 0, 3))), true) %!assert_equal (isempty (days (ones (2, 1, 3))), false) %!assert_equal (isempty (days (1)), false) %!assert_equal (isempty (duration), false) %!assert_equal (isequal (duration (24, 0, 0), days (1)), true) %!assert_equal (isequal (duration (24, 0, 0), '24:00:00'), true) %!assert_equal (isequal (duration (24, 0, 0), '24:00:01'), false) %!assert_equal (isequal (duration (24, 0, 1), {'24:00:01'}), true) %!assert_equal (isequal (duration (24, 0, 1), {'24:00:00'}), false) %!assert_equal (isequal (days ([1, 2]), string ({'24:00:00', '48:00:00'})), true) %!assert_equal (isequal (days ([1, 2]), string ({'24:00:00'; '48:00:00'})), false) %!assert_equal (isequal (days ([1; 2]), string ({'24:00:00'; '48:00:00'})), true) %!assert_equal (isequal (days ([1; 2]), string ({'24:00:00', '48:00:00'})), false) %!assert_equal (isequal (days (ones (2, 3, 4)), days (ones (2, 3, 4))), true) %!assert_equal (isequal (duration (ones (2, 3, 4) * 24, 0, 0), days (ones (2, 3, 4))), true) %!assert_equal (isequal (repmat ({'24:00:00'}, 2, 3, 4), days (ones (2, 3, 4))), true) %!assert_equal (isequal (repmat ({'24:00:05'}, 2, 3, 4), duration (24 * ones (2, 3, 4), 0, 5)), true) %!assert_equal (isequal (days (1), duration (24, 0, 0), '24:00:00'), true) %!assert_equal (isequal (days ([1, NaN]), duration ([24, NaN], 0, 0)), false) %!assert_equal (isequaln (duration (24, 0, 0), days (1)), true) %!assert_equal (isequaln (duration (24, 0, 0), '24:00:00'), true) %!assert_equal (isequaln (duration (24, 0, 0), '24:00:01'), false) %!assert_equal (isequaln (duration (24, 0, 1), {'24:00:01'}), true) %!assert_equal (isequaln (duration (24, 0, 1), {'24:00:00'}), false) %!assert_equal (isequaln (days ([1, 2]), string ({'24:00:00', '48:00:00'})), true) %!assert_equal (isequaln (days ([1, 2]), string ({'24:00:00'; '48:00:00'})), false) %!assert_equal (isequaln (days ([1; 2]), string ({'24:00:00'; '48:00:00'})), true) %!assert_equal (isequaln (days ([1; 2]), string ({'24:00:00', '48:00:00'})), false) %!assert_equal (isequaln (days (ones (2, 3, 4)), days (ones (2, 3, 4))), true) %!assert_equal (isequaln (duration (ones (2, 3, 4) * 24, 0, 0), days (ones (2, 3, 4))), true) %!assert_equal (isequaln (repmat ({'24:00:00'}, 2, 3, 4), days (ones (2, 3, 4))), true) %!assert_equal (isequaln (repmat ({'24:00:05'}, 2, 3, 4), duration (24 * ones (2, 3, 4), 0, 5)), true) %!assert_equal (isequaln (days (1), duration (24, 0, 0), '24:00:00'), true) %!assert_equal (isequaln (days ([1, NaN]), duration ([24, NaN], 0, 0)), true) %!assert_equal (isfinite (days ([1, -Inf, NaN, Inf])), [true, false, false, false]) %!assert_equal (isfinite (days ([1, -Inf; NaN, Inf])), [true, false; false, false]) %!assert_equal (isfinite (days (ones (0, 1))), true (0, 1)) %!assert_equal (isfinite (days (ones (1, 0))), true (1, 0)) %!assert_equal (isfinite (days ([])), true ([])) %!assert_equal (isfinite (duration (ones (2, 3, 4), 0, 0)), true (2, 3, 4)) %!assert_equal (isfinite (duration (Inf (2, 3, 4), 0, 0)), false (2, 3, 4)) %!assert_equal (isfinite (duration (-Inf (2, 3, 4), 0, 0)), false (2, 3, 4)) %!assert_equal (isfinite (duration (NaN (2, 3, 4), 0, 0)), false (2, 3, 4)) %!assert_equal (isinf (days ([1, -Inf, NaN, Inf])), [false, true, false, true]) %!assert_equal (isinf (days ([1, -Inf; NaN, Inf])), [false, true; false, true]) %!assert_equal (isinf (days (ones (0, 1))), true (0, 1)) %!assert_equal (isinf (days (ones (1, 0))), true (1, 0)) %!assert_equal (isinf (days ([])), true ([])) %!assert_equal (isinf (duration (ones (2, 3, 4), 0, 0)), false (2, 3, 4)) %!assert_equal (isinf (duration (Inf (2, 3, 4), 0, 0)), true (2, 3, 4)) %!assert_equal (isinf (duration (-Inf (2, 3, 4), 0, 0)), true (2, 3, 4)) %!assert_equal (isinf (duration (NaN (2, 3, 4), 0, 0)), false (2, 3, 4)) %!assert_equal (ismatrix (duration), true) %!assert_equal (ismatrix (duration ([], [], [])), true) %!assert_equal (ismatrix (duration (ones (2, 4), 0, 0)), true) %!assert_equal (ismatrix (duration (ones (2, 4, 1), 0, 0)), true) %!assert_equal (ismatrix (duration (ones (2, 1, 4), 0, 0)), false) %!assert_equal (ismatrix (duration (ones (2, 3, 4), 0, 0)), false) %!assert_equal (ismatrix (days (ones (0, 1))), true) %!assert_equal (ismatrix (days (ones (1, 0))), true) %!assert_equal (ismember (days (1), days ([1, 2])), true) %!assert_equal (ismember (days ([1, 2]), days (1)), [true, false]) %!assert_equal (ismember (days ([1; 2]), days (1)), [true; false]) %!assert_equal (ismember (days (1), days ([1; 2])), true) %!test %! [TF, index] = ismember (days (1), days (ones (2, 3, 4))); %! assert_equal (TF, true); %! assert_equal (index, 1); %!test %! [TF, index] = ismember (days (1), days (ones (2, 3, 4)), 'legacy'); %! assert_equal (TF, true); %! assert_equal (index, 24); %!test %! x = ones (2, 3, 4); %! x(1) = 2; %! [TF, index] = ismember (days (1), days (x)); %! assert_equal (TF, true); %! assert_equal (index, 2); # This is MATLAB incompatible %!test %! x = ones (2, 3, 4); %! x(1) = 2; %! [TF, index] = ismember (days (1), days (x), 'legacy'); %! assert_equal (TF, true); %! assert_equal (index, 24); %!test %! [TF, index] = ismember (days ([0:4]), days ([1, 2])); %! assert_equal (TF, [false, true, true, false, false]); %! assert_equal (index, [0, 1, 2, 0, 0]); %!test %! [TF, index] = ismember (days ([0:4]), days ([-1, 2])); %! assert_equal (TF, [false, false, true, false, false]); %! assert_equal (index, [0, 0, 2, 0, 0]); %!test %! [TF, index] = ismember (days ([0:4]), [-1, 2]); %! assert_equal (TF, [false, false, true, false, false]); %! assert_equal (index, [0, 0, 2, 0, 0]); %!test %! [TF, index] = ismember (days ([0:4]), {'24:00:00', '18:00:00'}); %! assert_equal (TF, [false, true, false, false, false]); %! assert_equal (index, [0, 1, 0, 0, 0]); %!test %! [TF, index] = ismember (days ([2,3;1,2;-1,2;4,5]), days ([-1, 2]), 'rows'); %! assert_equal (TF, [false; false; true; false]); %! assert_equal (index, [0; 0; 1; 0]); %!error ... %! ismember (days (1), days (ones (2, 3, 4)), 'rows') %!error ... %! ismember (days (1), days (ones (2, 3)), 'rows') %!error ... %! ismember (days (1), days (1), 3) %!error ... %! ismember (days (1), days (1), 'row') %!assert_equal (ismissing (days ([1, 2, 3, NaN, 5])), logical ([0, 0, 0, 1, 0])) %!assert_equal (ismissing (days ([1, 2, 3, NaN, 5]), days (1)), logical ([1, 0, 0, 0, 0])) %!assert_equal (ismissing (days ([1, 2, 3, NaN, 5]), duration (24, 0, 0)), logical ([1, 0, 0, 0, 0])) %!assert_equal (ismissing (days ([1, 2, 3, NaN, 5]), duration ('48:00:00')), logical ([0, 1, 0, 0, 0])) %!assert_equal (ismissing (seconds ([1:5]), duration (0, 0, [1:3])), logical ([1, 1, 1, 0, 0])) %!assert_equal (ismissing (days ([1, 2, 3, NaN, 5]), days ([1, 3])), logical ([1, 0, 1, 0, 0])) %!assert_equal (ismissing (days ([1, 2, 3, NaN, 5]), days ([1, 6])), logical ([1, 0, 0, 0, 0])) %!assert_equal (ismissing (days ([1, 2, 3, NaN, 5]), days ([1, 5, 6])), logical ([1, 0, 0, 0, 1])) %!error ... %! ismissing (days (1), days (1), days (1)) %!error ... %! ismissing (days ([1, 2, 3, NaN, 5]), 5) %!error ... %! ismissing (days ([1, 2, 3, NaN, 5]), '24:00:00') %!error ... %! ismissing (days ([1, 2, 3, NaN, 5]), string ('24:00:00')) %!assert_equal (isnan (minutes ([1:10])), false (1, 10)) %!assert_equal (isnan (days ([1, 2, NaN, 4, NaN])), logical ([0, 0, 1, 0, 1])) %!assert_equal (isnan (duration (NaN (3, 4, 5), 0, 0)), true (3, 4, 5)) %!assert_equal (isnan (duration (zeros (3, 4, 5), 0, 0)), false (3, 4, 5)) %!assert_equal (isrow (days ([1, 2, 3, 4])), true) %!assert_equal (isrow (days ([1; 2; 3; 4])), false) %!assert_equal (isrow (days ([1, 2; 3, 4])), false) %!assert_equal (isrow (days ([])), false) %!assert_equal (isrow (days (ones (1, 0))), true) %!assert_equal (isrow (days (ones (0, 1))), false) %!assert_equal (isrow (days (1)), true) %!assert_equal (isrow (days (ones (2, 3, 4))), false) %!assert_equal (isscalar (days ([])), false) %!assert_equal (isscalar (days (ones (1, 0))), false) %!assert_equal (isscalar (days (ones (0, 1))), false) %!assert_equal (isscalar (days (1)), true) %!assert_equal (isscalar (days (NaN)), true) %!assert_equal (isscalar (days ([1, 2])), false) %!test %! D = days ([1, 2; 1, 3; 2, -4; 2, 1]); %! assert_equal (issorted (sort (D)), true); %! assert_equal (issorted (sort (D), 'ascend'), true); %! assert_equal (issorted (sort (D), 'descend'), false); %! assert_equal (issorted (sort (D), 'monotonic'), true); %! assert_equal (issorted (sort (D), 'strictascend'), false); %! assert_equal (issorted (sort (D), 'strictdescend'), false); %! assert_equal (issorted (sort (D), 'strictmonotonic'), false); %!test %! D = days ([1, 2; 1, 3; 2, -4; 2, 1]); %! assert_equal (issorted (sort (D, 2), 2), true); %! assert_equal (issorted (sort (D, 2), 2, 'ascend'), true); %! assert_equal (issorted (sort (D, 2), 2, 'descend'), false); %! assert_equal (issorted (sort (D, 2), 2, 'monotonic'), true); %! assert_equal (issorted (sort (D, 2), 2, 'strictascend'), true); %! assert_equal (issorted (sort (D, 2), 2, 'strictdescend'), false); %! assert_equal (issorted (sort (D, 2), 2, 'strictmonotonic'), true); %!test %! D = days ([1, 2; 1, 3; 2, -4; 2, 1]); %! assert_equal (issorted (sort (D, 2), 'ascend'), false); %! assert_equal (issorted (sort (D, 2), 'descend'), false); %! assert_equal (issorted (sort (D, 2), 'monotonic'), false); %! assert_equal (issorted (sort (D, 2), 'strictascend'), false); %! assert_equal (issorted (sort (D, 2), 'strictdescend'), false); %! assert_equal (issorted (sort (D, 2), 'strictmonotonic'), false); %!test %! D = days ([1, 2; 1, 3; 2, -4; 2, 1]); %! assert_equal (issorted (sort (D, 'descend'), 'ascend'), false); %! assert_equal (issorted (sort (D, 'descend'), 'descend'), true); %! assert_equal (issorted (sort (D, 'descend'), 'monotonic'), true); %! assert_equal (issorted (sort (D, 'descend'), 'strictascend'), false); %! assert_equal (issorted (sort (D, 'descend'), 'strictdescend'), false); %! assert_equal (issorted (sort (D, 'descend'), 'strictmonotonic'), false); %!test %! D = days ([1, 2; 1, 3; 2, -4; 2, 1]); %! assert_equal (issorted (sort (D, 2, 'descend'), 2, 'ascend'), false); %! assert_equal (issorted (sort (D, 2, 'descend'), 2, 'descend'), true); %! assert_equal (issorted (sort (D, 2, 'descend'), 2, 'monotonic'), true); %! assert_equal (issorted (sort (D, 2, 'descend'), 2, 'strictascend'), false); %! assert_equal (issorted (sort (D, 2, 'descend'), 2, 'strictdescend'), true); %! assert_equal (issorted (sort (D, 2, 'descend'), 2, 'strictmonotonic'), true); %!test %! D = days ([1, 2; 1, 3; 2, -4; 2, 1]); %! assert_equal (issorted (sort (D, 2, 'descend'), 'ascend'), false); %! assert_equal (issorted (sort (D, 2, 'descend'), 'descend'), false); %! assert_equal (issorted (sort (D, 2, 'descend'), 'monotonic'), false); %! assert_equal (issorted (sort (D, 2, 'descend'), 'strictascend'), false); %! assert_equal (issorted (sort (D, 2, 'descend'), 'strictdescend'), false); %! assert_equal (issorted (sort (D, 2, 'descend'), 'strictmonotonic'), false); %!test %! D = days ([1, 2; 1, 3; 2, -4; 2, 1]); %! assert_equal (issorted (sort (D, 'ComparisonMethod', 'real')), true); %! assert_equal (issorted (sort (D, 'ComparisonMethod', 'abs')), false); %! assert_equal (issorted (sort (D, 'ComparisonMethod', 'abs'), ... %! 'ComparisonMethod', 'abs'), true); %!test %! D = days ([1, 2; 1, 3; 2, -4; 2, 1]); %! assert_equal (issorted (sort (D, 2, 'ComparisonMethod', 'real'), 2), true); %! assert_equal (issorted (sort (D, 2, 'ComparisonMethod', 'abs'), 2), false); %! assert_equal (issorted (sort (D, 2, 'ComparisonMethod', 'abs'), 2, ... %! 'ComparisonMethod', 'abs'), true); %!test %! D = days ([1, 2; 2, 3; 3, 4; 4, 5]); %! assert_equal (issorted (sort (D), 'ascend'), true); %! assert_equal (issorted (sort (D), 'descend'), false); %! assert_equal (issorted (sort (D), 'monotonic'), true); %! assert_equal (issorted (sort (D), 'strictascend'), true); %! assert_equal (issorted (sort (D), 'strictdescend'), false); %! assert_equal (issorted (sort (D), 'strictmonotonic'), true); %!test %! D = days ([1, 2; 2, 3; 3, 4; 4, 5]); %! assert_equal (issorted (sort (D, 'descend'), 'ascend'), false); %! assert_equal (issorted (sort (D, 'descend'), 'descend'), true); %! assert_equal (issorted (sort (D, 'descend'), 'monotonic'), true); %! assert_equal (issorted (sort (D, 'descend'), 'strictascend'), false); %! assert_equal (issorted (sort (D, 'descend'), 'strictdescend'), true); %! assert_equal (issorted (sort (D, 'descend'), 'strictmonotonic'), true); %!test %! D = days ([1, 2; 2, 3; 3, 4; 4, NaN]); %! assert_equal (issorted (sort (D)), true); %! assert_equal (issorted (sort (D), 'ascend'), true); %! assert_equal (issorted (sort (D), 'descend'), false); %! assert_equal (issorted (sort (D), 'monotonic'), true); %! assert_equal (issorted (sort (D), 'strictascend'), false); %! assert_equal (issorted (sort (D), 'strictdescend'), false); %! assert_equal (issorted (sort (D), 'strictmonotonic'), false); %!test %! D = days ([1, 2; 2, 3; 3, 4; 4, NaN]); %! assert_equal (issorted (sort (D, 'descend'), 'ascend'), false); %! assert_equal (issorted (sort (D, 'descend'), 'descend'), true); %! assert_equal (issorted (sort (D, 'descend'), 'monotonic'), true); %! assert_equal (issorted (sort (D, 'descend'), 'strictascend'), false); %! assert_equal (issorted (sort (D, 'descend'), 'strictdescend'), false); %! assert_equal (issorted (sort (D, 'descend'), 'strictmonotonic'), false); %!test %! D = days ([1, 2; 2, 3; 3, 4; 4, NaN]); %! assert_equal (issorted (sort (D, 'MissingPlacement', 'auto')), true); %! assert_equal (issorted (sort (D, 'MissingPlacement', 'first')), false); %! assert_equal (issorted (sort (D, 'MissingPlacement', 'first'), ... %! 'MissingPlacement', 'first'), true); %!test %! D = days ([1, 2; 2, 3; 3, 4; 4, NaN]); %! assert_equal (issorted (sort (D, 'descend', 'MissingPlacement', 'auto'), 'descend'), true); %! assert_equal (issorted (sort (D, 'descend', 'MissingPlacement', 'last'), 'descend'), false); %! assert_equal (issorted (sort (D, 'descend', 'MissingPlacement', 'first'), ... %! 'descend', 'MissingPlacement', 'first'), true); %!test %! D = days ([1, 2; 2, 3; 3, 4; 4, NaN]); %! assert_equal (issorted (sort (D, 2, 'MissingPlacement', 'auto'), 2), true); %! assert_equal (issorted (sort (D, 2, 'MissingPlacement', 'first'), 2), false); %! assert_equal (issorted (sort (D, 2, 'MissingPlacement', 'first'), 2, ... %! 'MissingPlacement', 'first'), true); %!test %! D = days ([1, 2; 2, 3; 3, 4; 4, NaN]); %! assert_equal (issorted (sort (D, 2, 'descend', 'MissingPlacement', 'auto'), 2, 'descend'), true); %! assert_equal (issorted (sort (D, 2, 'descend', 'MissingPlacement', 'last'), 2, 'descend'), false); %! assert_equal (issorted (sort (D, 2, 'descend', 'MissingPlacement', 'first'), 2, ... %! 'descend', 'MissingPlacement', 'first'), true); %!test %! D = days ([1, 2; 2, 3; 3, 4; 4, NaN]); %! assert_equal (issorted (sort (D, 2, string ('ascend'), 'MissingPlacement', 'auto'), 2), true); %! assert_equal (issorted (sort (D, 2, string ('ascend'), 'MissingPlacement', 'first'), 2), false); %! assert_equal (issorted (sort (D, 2, string ('ascend'), 'MissingPlacement', 'first'), 2, ... %! 'MissingPlacement', 'first'), true); %!test %! D = days ([1, 2; 2, 3; 3, 4; 4, NaN]); %! assert_equal (issorted (sort (D, 2, string ('descend'), 'MissingPlacement', 'auto'), 2, 'descend'), true); %! assert_equal (issorted (sort (D, 2, string ('descend'), 'MissingPlacement', 'last'), 2, 'descend'), false); %! assert_equal (issorted (sort (D, 2, string ('descend'), 'MissingPlacement', 'first'), 2, ... %! 'descend', 'MissingPlacement', 'first'), true); %!test %! D = days ([1, 2; 2, -3; 3, 4; 4, NaN]); %! assert_equal (issorted (sort (D, 'MissingPlacement', 'auto', ... %! 'ComparisonMethod', 'real')), true); %! assert_equal (issorted (sort (D, 'MissingPlacement', 'auto', ... %! 'ComparisonMethod', 'abs')), false); %! assert_equal (issorted (sort (D, 'MissingPlacement', 'auto', ... %! 'ComparisonMethod', 'abs'), 'ComparisonMethod', 'abs'), true); %! assert_equal (issorted (sort (D, 'MissingPlacement', 'last', ... %! 'ComparisonMethod', 'abs')), false); %! assert_equal (issorted (sort (D, 'MissingPlacement', 'last', ... %! 'ComparisonMethod', 'abs'), 'ComparisonMethod', 'abs'), true); %! assert_equal (issorted (sort (D, 'MissingPlacement', 'first', ... %! 'ComparisonMethod', 'abs'), ... %! 'MissingPlacement', 'first', 'ComparisonMethod', 'abs'), true); %!test %! D = days ([1, 2; 2, -3; 3, 4; 4, NaN]); %! assert_equal (issorted (sort (D, 'descend', 'MissingPlacement', 'auto', ... %! 'ComparisonMethod', 'real'), 'descend'), true); %! assert_equal (issorted (sort (D, 'descend', 'MissingPlacement', 'auto', ... %! 'ComparisonMethod', 'abs'), 'descend'), false); %! assert_equal (issorted (sort (D, 'descend', 'MissingPlacement', 'auto', ... %! 'ComparisonMethod', 'abs'), ... %! 'descend', 'ComparisonMethod', 'abs'), true); %! assert_equal (issorted (sort (D, 'descend', 'MissingPlacement', 'last', ... %! 'ComparisonMethod', 'abs'), 'descend'), false); %! assert_equal (issorted (sort (D, 'descend', 'MissingPlacement', 'last', ... %! 'ComparisonMethod', 'abs'), ... %! 'descend', 'ComparisonMethod', 'abs'), false); %! assert_equal (issorted (sort (D, 'descend', 'MissingPlacement', 'first', ... %! 'ComparisonMethod', 'abs'), 'descend'), false); %! assert_equal (issorted (sort (D, 'descend', 'MissingPlacement', 'first', ... %! 'ComparisonMethod', 'abs'), ... %! 'descend', 'ComparisonMethod', 'abs'), true); %! assert_equal (issorted (sort (D, 'descend', 'MissingPlacement', 'first', ... %! 'ComparisonMethod', 'abs'), 'descend', ... %! 'MissingPlacement', 'first', 'ComparisonMethod', 'abs'), true); %!test %! D = days ([1, 2; 1, 3; 2, -4; 2, 1]); %! assert_equal (issortedrows (sortrows (D)), true); %! assert_equal (issortedrows (sortrows (D, 1)), true); %! assert_equal (issortedrows (sortrows (D, 1), -1), false); %! assert_equal (issortedrows (sortrows (D, 1), 1), true); %! assert_equal (issortedrows (sortrows (D, 2)), false); %! assert_equal (issortedrows (sortrows (D, 2), 2), true); %! assert_equal (issortedrows (sortrows (D, 2), -2), false); %! assert_equal (issortedrows (sortrows (D, -2), 2), false); %! assert_equal (issortedrows (sortrows (D, -2), -2), true); %!test %! D = days ([1, 2; 1, 3; 2, -4; 2, 1]); %! assert_equal (issortedrows (sortrows (D, 'ComparisonMethod', 'abs')), false); %! assert_equal (issortedrows (sortrows (D, 'ComparisonMethod', 'real')), true); %! assert_equal (issortedrows (sortrows (D, 'ComparisonMethod', 'abs'), ... %! 'ComparisonMethod', 'abs'), true); %!test %! D = days ([1, 2; 1, 3; 2, -4; 2, 1]); %! assert_equal (issortedrows (sortrows (D, 2, 'ComparisonMethod', 'abs'), 2), false); %! assert_equal (issortedrows (sortrows (D, 2, 'ComparisonMethod', 'real'), 2), true); %! assert_equal (issortedrows (sortrows (D, 2, 'ComparisonMethod', 'abs'), 2, ... %! 'ComparisonMethod', 'abs'), true); %! assert_equal (issortedrows (sortrows (D, [1, 2], 'ComparisonMethod', 'abs'), ... %! {'ascend', 'ascend'}), false); %! assert_equal (issortedrows (sortrows (D, [1, 2], 'ComparisonMethod', 'abs'), ... %! {'ascend', 'ascend'}, 'ComparisonMethod', 'abs'), true); %! assert_equal (issortedrows (sortrows (D, 2, 'ComparisonMethod', 'real'), 2), true); %! assert_equal (issortedrows (sortrows (D, [1, 2], 'ComparisonMethod', 'real'), ... %! {'ascend', 'ascend'}, 'ComparisonMethod', 'auto'), true); %! assert_equal (issortedrows (sortrows (D, [-1, 2], 'ComparisonMethod', 'abs'), ... %! {'ascend', 'ascend'}, 'ComparisonMethod', 'abs'), false); %! assert_equal (issortedrows (sortrows (D, 2, 'ComparisonMethod', 'real'), -2), false); %! assert_equal (issortedrows (sortrows (D, [1, 2], 'ComparisonMethod', 'real'), ... %! {'ascend', 'descend'}, 'ComparisonMethod', 'auto'), false); %! assert_equal (issortedrows (sortrows (D, [1, -2], 'ComparisonMethod', 'real'), ... %! {'ascend', 'descend'}, 'ComparisonMethod', 'auto'), true); %!test %! D = days ([1, 2; 1, 3; 2, -4; 2, 1]); %! assert_equal (issortedrows (sortrows (D, [1, 2], 'ComparisonMethod', 'abs'), ... %! [1, 2], string ({'ascend', 'ascend'})), false); %! assert_equal (issortedrows (sortrows (D, [1, 2], 'ComparisonMethod', 'abs'), ... %! [1, 2], string ({'ascend', 'ascend'}), ... %! 'ComparisonMethod', 'abs'), true); %! assert_equal (issortedrows (sortrows (D, [1, 2], 'ComparisonMethod', 'abs'), ... %! [1, -2], string ({'ascend', 'ascend'}), ... %! 'ComparisonMethod', 'abs'), true); %! assert_equal (issortedrows (sortrows (D, [1, 2], 'ComparisonMethod', 'real'), ... %! [1, 2], string ({'ascend', 'ascend'}), ... %! 'ComparisonMethod', 'auto'), true); %! assert_equal (issortedrows (sortrows (D, [1, -2], string ({'ascend', 'ascend'}), ... %! 'ComparisonMethod', 'real'), ... %! [1, 2], string ({'ascend', 'ascend'}), ... %! 'ComparisonMethod', 'auto'), true); %!test %! D = days ([1, 2, 2; 1, 3, 1; 2, -4, 2; 2, 1, NaN; -2, NaN, 3; 1, 3, 2]); %! assert_equal (issortedrows (sortrows (D)), true); %!test %! D = days ([1, 2, 2; 1, 3, 1; 2, -4, 2; 2, 1, NaN; -2, NaN, 3; 1, 3, 2]); %! assert_equal (issortedrows (sortrows (D, -2, 'MissingPlacement', 'first')), false); %! assert_equal (issortedrows (sortrows (D, -2, 'MissingPlacement', 'first'), -2), true); %! assert_equal (issortedrows (sortrows (D, -2, 'MissingPlacement', 'last'), -2), false); %! assert_equal (issortedrows (sortrows (D, -2, 'MissingPlacement', 'last'), -2, ... %! 'MissingPlacement', 'last'), true); %!test %! D = days ([1, 2, 2; 1, 3, 1; 2, -4, 2; 2, 1, NaN; -2, NaN, 3; 1, 3, 2]); %! assert_equal (issortedrows (sortrows (D, 2, 'MissingPlacement', 'last')), false); %! assert_equal (issortedrows (sortrows (D, 2, 'MissingPlacement', 'last'), 2), true); %! assert_equal (issortedrows (sortrows (D, 2, 'MissingPlacement', 'first'), 2), false); %! assert_equal (issortedrows (sortrows (D, 2, 'MissingPlacement', 'first'), 2, ... %! 'MissingPlacement', 'first'), true); %!test %! D = days ([1, 2, 2; 1, 3, 1; 2, -4, 2; 2, 1, NaN; -2, NaN, 3; 1, 3, 2]); %! assert_equal (issortedrows (sortrows (D, 'ComparisonMethod', 'abs', ... %! 'MissingPlacement', 'first'), ... %! 'ComparisonMethod', 'abs', ... %! 'MissingPlacement', 'first'), true); %! assert_equal (issortedrows (sortrows (D, 'ComparisonMethod', 'abs', ... %! 'MissingPlacement', 'last'), ... %! 'ComparisonMethod', 'abs', ... %! 'MissingPlacement', 'first'), false); %! assert_equal (issortedrows (sortrows (D, 'ComparisonMethod', 'abs', ... %! 'MissingPlacement', 'last'), ... %! 'ComparisonMethod', 'abs', ... %! 'MissingPlacement', 'auto'), true); %! assert_equal (issortedrows (sortrows (D, 'ComparisonMethod', 'abs', ... %! 'MissingPlacement', 'last'), ... %! 'ComparisonMethod', 'abs'), true); %!test %! D = days ([1, 2, 2; 1, 3, 1; 2, -4, 2; 2, 1, NaN; -2, NaN, 3; 1, 3, 2]); %! assert_equal (issortedrows (sortrows (D, [1, -2, 3]), [1, -2, 3]), true); %! assert_equal (issortedrows (sortrows (D, [1, 2, -3]), [1, -2, 3]), false); %!test %! D = days ([1, 2, 2; 1, 3, 1; 2, -4, 2; 2, 1, NaN; -2, NaN, 3; 1, 3, 2]); %! assert_equal (issortedrows (sortrows (D, string ({'ascend', 'descend', 'ascend'})), ... %! string ({'ascend', 'descend', 'ascend'})), true); %! D = days ([1, 2, 2; 1, 3, 1; 2, -4, 2; 2, 1, NaN; -2, NaN, 3; 1, 3, 2]); %! assert_equal (issortedrows (sortrows (D, string ({'ascend', 'descend', 'ascend'})), ... %! string ({'descend', 'ascend', 'ascend'})), false); %!test %! D = days ([1, 2, 2; 1, 3, 1; 2, -4, 2; 2, 1, NaN; -2, NaN, 3; 1, 3, 2]); %! assert_equal (issortedrows (sortrows (D, [2, 3], string ({'descend', 'ascend'})), ... %! [2, 3], string ({'descend', 'ascend'})), true); %! D = days ([1, 2, 2; 1, 3, 1; 2, -4, 2; 2, 1, NaN; -2, NaN, 3; 1, 3, 2]); %! assert_equal (issortedrows (sortrows (D, [2, 3], string ({'ascend', 'descend'})), ... %! [2, -3]), true); %! assert_equal (issortedrows (sortrows (D, [2, 3], string ({'descend', 'descend'})), ... %! [2, -3], string ({'descend', 'ascend'})), false); %!test %! D = days ([1, 2, 2; 1, 3, 1; 2, -4, 2; 2, 1, NaN; -2, NaN, 3; 1, 3, 2]); %! assert_equal (issortedrows (sortrows (D, [1, -2, 3], 'MissingPlacement', 'first'), ... %! [1, -2, 3], 'MissingPlacement', 'first'), true); %! assert_equal (issortedrows (sortrows (D, [-1, -2, -3], 'MissingPlacement', 'first'), ... %! [-1, -2, -3]), true); %! assert_equal (issortedrows (sortrows (D, [-1, -2, -3], 'MissingPlacement', 'first'), ... %! [1, -2,-3], string ({'descend', 'descend', 'descend'})), true); %!test ## strict issortedrows semantics, verified against MATLAB R2026a %! assert_equal (issortedrows (days ([1, 3; 1, 4]), 'strictascend'), false); %! assert_equal (issortedrows (days ([1, 3; 1, 3]), 'strictascend'), false); %! assert_equal (issortedrows (days ([1, 3; 1, 4]), {'strictascend', 'ascend'}), false); %! assert_equal (issortedrows (days ([1, 3; 2, 3]), {'ascend', 'strictascend'}), true); %! assert_equal (issortedrows (days ([2, 4; 1, 3]), 'monotonic'), true); %! assert_equal (issortedrows (days ([2, 4; 1, 3]), 'descend'), true); %! assert_equal (issortedrows (days ([1, 4; 2, 3]), 2, 'strictascend'), false); %! assert_equal (issortedrows (days ([2, 3; 1, 4]), 2, 'strictascend'), true); %!test ## strict issortedrows with missing values (vs MATLAB R2026a) %! assert_equal (issortedrows (days ([1, 3; NaN, 4]), {'strictascend', 'ascend'}), false); %! assert_equal (issortedrows (days ([1, 3; NaN, 4]), {'ascend', 'strictascend'}), true); %! assert_equal (issortedrows (days ([NaN, 3; 2, 4]), 2, 'strictascend'), true); %! assert_equal (issortedrows (days ([1, NaN; 2, 4]), 'strictascend'), true); %!assert_equal (isvector (days ([1, 2, 3, 4])), true) %!assert_equal (isvector (days ([1; 2; 3; 4])), true) %!assert_equal (isvector (days ([1, 2; 3, 4])), false) %!assert_equal (isvector (days ([])), false) %!assert_equal (isvector (days (ones (1, 0))), true) %!assert_equal (isvector (days (ones (0, 1))), true) %!assert_equal (isvector (days (1)), true) %!assert_equal (isvector (days (ones (2, 3, 4))), false) ################################################################################ ## ** Relational Operations ** ## ################################################################################ ## Available Methods ## ## ## ## 'eq' 'ge' 'gt' 'le' ## ## 'lt' 'ne' ## ## ## ################################################################################ %!assert_equal (duration (1, 0, 0) == '01:00:00', true) %!assert_equal (duration (1, 0, 0) == '01:00:01', false) %!assert_equal (duration (1, 0, 0) == {'01:00:00'}, true) %!assert_equal (duration (1, 0, 1) == {'01:00:00'}, false) %!assert_equal (duration (1, 0, 0) == string ('01:00:00'), true) %!assert_equal (duration (NaN, 0, 0) == string ('01:00:00'), false) %!assert_equal (duration (24, 0, 0) == 1, true) %!assert_equal (duration (NaN, 0, 0) == NaN, false) %!assert_equal (duration (24, 0, 0) == days (1), true) %!assert_equal ('01:00:00' == duration (1, 0, 0), true) %!assert_equal ({'01:00:00'} == duration (1, 0, 0), true) %!assert_equal (string ('01:00:00') == duration (1, 0, 0), true) %!assert_equal (1 == duration (24, 0, 0), true) %!assert_equal (days (1) == duration (24, 0, 0), true) %!assert_equal (days (NaN) == duration (NaN, 0, 0), false) %!assert_equal (duration (1, 0, 0) == hours (1), true) %!assert_equal (duration (0, 1, 0) == minutes (1), true) %!assert_equal (duration (0, 0, 1) == seconds (1), true) %!assert_equal (days ([1, 2]) == 1, [true, false]) %!assert_equal (days ([1; 2]) == 1, [true; false]) %!assert_equal (days ([1, 2]) == [1, 2], [true, true]) %!assert_equal (days ([1, 2]) == [1; 2], logical (eye (2))) %!assert_equal (duration (1, 0, 0) >= '01:00:00', true) %!assert_equal (duration (1, 0, 0) >= '01:00:01', false) %!assert_equal (duration (1, 0, 0) >= {'01:00:00'}, true) %!assert_equal (duration (1, 0, 1) >= {'01:00:00'}, true) %!assert_equal (duration (1, 0, 0) >= string ('01:00:00'), true) %!assert_equal (duration (NaN, 0, 0) >= string ('01:00:00'), false) %!assert_equal (duration (24, 0, 0) >= 1, true) %!assert_equal (duration (NaN, 0, 0) >= NaN, false) %!assert_equal (duration (24, 0, 0) >= days (1), true) %!assert_equal ('01:00:00' >= duration (1, 0, 0), true) %!assert_equal ('01:00:00' >= duration (1, 0, 1), false) %!assert_equal ({'01:00:00'} >= duration (1, 0, 0), true) %!assert_equal (string ('01:00:00') >= duration (1, 0, 0), true) %!assert_equal (string ('01:00:01') >= duration (1, 0, 0), true) %!assert_equal (string ('01:00:00') >= duration (1, 1, 0), false) %!assert_equal (1 >= duration (24, 0, 0), true) %!assert_equal (days (1) >= duration (24, 0, 0), true) %!assert_equal (days (NaN) >= duration (NaN, 0, 0), false) %!assert_equal (duration (1, 0, 0) >= hours (1), true) %!assert_equal (duration (0, 1, 0) >= minutes (1), true) %!assert_equal (duration (0, 0, 1) >= seconds (1), true) %!assert_equal (days ([1, 2]) >= 1, [true, true]) %!assert_equal (days ([1; 2]) >= 1, [true; true]) %!assert_equal (days ([1, 2]) >= [1, 2], [true, true]) %!assert_equal (days ([1, 2]) >= [1; 2], [true, true; false, true]) %!assert_equal (duration (1, 0, 0) > '01:00:00', false) %!assert_equal (duration (1, 0, 0) > '01:00:01', false) %!assert_equal (duration (1, 0, 0) > {'01:00:00'}, false) %!assert_equal (duration (1, 0, 1) > {'01:00:00'}, true) %!assert_equal (duration (1, 0, 0) > string ('01:00:00'), false) %!assert_equal (duration (NaN, 0, 0) > string ('01:00:00'), false) %!assert_equal (duration (24, 0, 0) > 1, false) %!assert_equal (duration (NaN, 0, 0) > NaN, false) %!assert_equal (duration (24, 0, 0) > days (1), false) %!assert_equal (duration (24, 0, 1) > days (1), true) %!assert_equal ('01:00:00' > duration (1, 0, 0), false) %!assert_equal ('01:00:01' > duration (1, 0, 0), true) %!assert_equal ('01:00:00' > duration (1, 0, 1), false) %!assert_equal ({'01:00:00'} > duration (1, 0, 0), false) %!assert_equal (string ('01:00:00') > duration (1, 0, 0), false) %!assert_equal (string ('01:00:01') > duration (1, 0, 0), true) %!assert_equal (string ('01:00:00') > duration (1, 1, 0), false) %!assert_equal (1 > duration (24, 0, 0), false) %!assert_equal (days (1) > duration (24, 0, 0), false) %!assert_equal (days (1.05) > duration (24, 0, 0), true) %!assert_equal (days (NaN) > duration (NaN, 0, 0), false) %!assert_equal (duration (1, 0, 0) > hours (1), false) %!assert_equal (duration (0, 1, 0) > minutes (1), false) %!assert_equal (duration (0, 0, 1) > seconds (1), false) %!assert_equal (days ([1, 2]) > 1, [false, true]) %!assert_equal (days ([1; 2]) > 1, [false; true]) %!assert_equal (days ([1, 2]) > [1, 2], [false, false]) %!assert_equal (days ([1, 2]) > [1; 2], [false, true; false, false]) %!assert_equal (duration (1, 0, 0) <= '01:00:00', true) %!assert_equal (duration (1, 0, 0) <= '01:00:01', true) %!assert_equal (duration (1, 0, 0) <= {'01:00:00'}, true) %!assert_equal (duration (1, 0, 1) <= {'01:00:00'}, false) %!assert_equal (duration (1, 0, 0) <= string ('01:00:00'), true) %!assert_equal (duration (NaN, 0, 0) <= string ('01:00:00'), false) %!assert_equal (duration (24, 0, 0) <= 1, true) %!assert_equal (duration (NaN, 0, 0) <= NaN, false) %!assert_equal (duration (24, 0, 0) <= days (1), true) %!assert_equal ('01:00:00' <= duration (1, 0, 0), true) %!assert_equal ('01:00:00' <= duration (1, 0, 1), true) %!assert_equal ({'01:00:00'} <= duration (1, 0, 0), true) %!assert_equal (string ('01:00:00') <= duration (1, 0, 0), true) %!assert_equal (string ('01:00:01') <= duration (1, 0, 0), false) %!assert_equal (string ('01:00:00') <= duration (1, 1, 0), true) %!assert_equal (1 <= duration (24, 0, 0), true) %!assert_equal (days (1) <= duration (24, 0, 0), true) %!assert_equal (days (NaN) <= duration (NaN, 0, 0), false) %!assert_equal (duration (1, 0, 0) <= hours (1), true) %!assert_equal (duration (0, 1, 0) <= minutes (1), true) %!assert_equal (duration (0, 0, 1) <= seconds (1), true) %!assert_equal (days ([1, 2]) <= 1, [true, false]) %!assert_equal (days ([1; 2]) <= 1, [true; false]) %!assert_equal (days ([1, 2]) <= [1, 2], [true, true]) %!assert_equal (days ([1, 2]) <= [1; 2], [true, false; true, true]) %!assert_equal (duration (1, 0, 0) < '01:00:00', false) %!assert_equal (duration (1, 0, 0) < '01:00:01', true) %!assert_equal (duration (1, 0, 0) < {'01:00:00'}, false) %!assert_equal (duration (1, 0, 1) < {'01:00:00'}, false) %!assert_equal (duration (1, 0, 0) < string ('01:00:00'), false) %!assert_equal (duration (NaN, 0, 0) < string ('01:00:00'), false) %!assert_equal (duration (24, 0, 0) < 1, false) %!assert_equal (duration (NaN, 0, 0) < NaN, false) %!assert_equal (duration (24, 0, 0) < days (1), false) %!assert_equal (duration (24, 0, 1) < days (1), false) %!assert_equal ('01:00:00' < duration (1, 0, 0), false) %!assert_equal ('01:00:01' < duration (1, 0, 0), false) %!assert_equal ('01:00:00' < duration (1, 0, 1), true) %!assert_equal ({'01:00:00'} < duration (1, 0, 0), false) %!assert_equal (string ('01:00:00') < duration (1, 0, 0), false) %!assert_equal (string ('01:00:01') < duration (1, 0, 0), false) %!assert_equal (string ('01:00:00') < duration (1, 1, 0), true) %!assert_equal (1 < duration (24, 0, 0), false) %!assert_equal (days (1) < duration (24, 0, 0), false) %!assert_equal (days (1.05) < duration (24, 0, 0), false) %!assert_equal (days (NaN) < duration (NaN, 0, 0), false) %!assert_equal (duration (1, 0, 0) < hours (1), false) %!assert_equal (duration (0, 1, 0) < minutes (1), false) %!assert_equal (duration (0, 0, 1) < seconds (1), false) %!assert_equal (days ([1, 2]) < 1, [false, false]) %!assert_equal (days ([1; 2]) < 1, [false; false]) %!assert_equal (days ([1, 2]) < [1, 2], [false, false]) %!assert_equal (days ([1, 2]) < [1; 2], [false, false; true, false]) %!assert_equal (duration (1, 0, 0) != '01:00:00', false) %!assert_equal (duration (1, 0, 0) != '01:00:01', true) %!assert_equal (duration (1, 0, 0) != {'01:00:00'}, false) %!assert_equal (duration (1, 0, 1) != {'01:00:00'}, true) %!assert_equal (duration (1, 0, 0) != string ('01:00:00'), false) %!assert_equal (duration (NaN, 0, 0) != string ('01:00:00'), true) %!assert_equal (duration (24, 0, 0) != 1, false) %!assert_equal (duration (NaN, 0, 0) != NaN, true) %!assert_equal (duration (24, 0, 0) != days (1), false) %!assert_equal ('01:00:00' != duration (1, 0, 0), false) %!assert_equal ({'01:00:00'} != duration (1, 0, 0), false) %!assert_equal (string ('01:00:00') != duration (1, 0, 0), false) %!assert_equal (1 != duration (24, 0, 0), false) %!assert_equal (days (1) != duration (24, 0, 0), false) %!assert_equal (days (NaN) != duration (NaN, 0, 0), true) %!assert_equal (duration (1, 0, 0) != hours (1), false) %!assert_equal (duration (0, 1, 0) != minutes (1), false) %!assert_equal (duration (0, 0, 1) != seconds (1), false) %!assert_equal (days ([1, 2]) != 1, [false, true]) %!assert_equal (days ([1; 2]) != 1, [false; true]) %!assert_equal (days ([1, 2]) != [1, 2], [false, false]) %!assert_equal (days ([1, 2]) != [1; 2], [false, true; true, false]) ################################################################################ ## ** Mathematical Operations ** ## ################################################################################ ## Available Methods ## ## ## ## 'abs' 'plus' 'uplus' 'minus' ## ## 'uminus' 'times' 'mtimes' 'ldivide' ## ## 'rdivide' 'colon' 'linspace' 'sign' ## ## 'sum' 'cumsum' 'diff' 'min' ## ## 'max' 'cummin' 'cummax' 'floor' ## ## 'ceil' 'round' ## ## ## ################################################################################ %!assert_equal (days (abs (days ([1, -1, NaN, -Inf, Inf]))), [1, 1, NaN, Inf, Inf]) %!assert_equal (days (abs (days (- ones (2, 3, 4)))), ones (2, 3, 4)) %!assert_equal (days (days ([1, 2; NaN, Inf]) + [2, 3]), [3, 5; NaN, Inf]) %!assert_equal (days (days ([1, 2; NaN, Inf]) + days ([2, 3])), [3, 5; NaN, Inf]) %!assert_equal (days ([2, 3] + days ([1, 2; NaN, Inf])), [3, 5; NaN, Inf]) %!assert_equal (days (days ([2, 3]) + days ([1, 2; NaN, Inf])), [3, 5; NaN, Inf]) %!assert_equal (caldays (days (1) + calendarDuration), 0) %!assert_equal (caldays (days (1) + calendarDuration (0, 0, 1)), 1) %!assert_equal (hours (time (days (1) + calendarDuration)), 24) %!assert_equal (hours (time (days (1) + calendarDuration (0, 0, 1))), 24) %!assert_equal (class (days (1) + calendarDuration), 'calendarDuration') %!error ... %! days (1) + '24:00:00' %!error ... %! days (1) + {'24:00:00'} %!error ... %! days (1) + string('24:00:00') %!assert_equal (days (+ days (-3)), -3) %!assert_equal (days (+ days (magic (3))), magic (3)) %!assert_equal (days (days ([3, 5; NaN, Inf]) - [2, 3]), [1, 2; NaN, Inf]) %!assert_equal (days (days ([3, 5; NaN, Inf]) - days ([2, 3])), [1, 2; NaN, Inf]) %!assert_equal (days ([3, 5] - days ([2, 3; NaN, Inf])), [1, 2; NaN, -Inf]) %!assert_equal (days (days ([3, 5]) - days ([2, 3; NaN, Inf])), [1, 2; NaN, -Inf]) %!assert_equal (caldays (days (1) - calendarDuration), 0) %!assert_equal (caldays (days (1) - calendarDuration (0, 0, 1)), -1) %!assert_equal (hours (time (days (1) - calendarDuration)), 24) %!assert_equal (hours (time (days (1) - calendarDuration (0, 0, 1))), 24) %!assert_equal (class (days (1) - calendarDuration), 'calendarDuration') %!error ... %! days (1) - '24:00:00' %!error ... %! days (1) - {'24:00:00'} %!error ... %! days (1) - string('24:00:00') %!assert_equal (days (- days (-3)), 3) %!assert_equal (days (- days (3)), -3) %!assert_equal (days (- days (magic (3))), - magic (3)) %!assert_equal (days (days ([1:3]) .* [1:3]), [1, 4, 9]) %!assert_equal (days ([1:3] .* days ([1:3])), [1, 4, 9]) %!assert_equal (days (days ([1:3]) .* [1; 2]), [1, 2, 3; 2, 4, 6]) %!assert_equal (days ([1; 2] .* days ([1:3])), [1, 2, 3; 2, 4, 6]) %!assert_equal (class (1 .* duration), 'duration') %!assert_equal (class (duration .* magic (3)), 'duration') %!assert_equal (hours (duration .* magic (3)), zeros (3)) %!error ... %! days (1) .* days (1) %!error ... %! days (1) .* 'asd' %!error ... %! days (1) .* string ({'asd'}) %!error ... %! {'asd'} .* days (1) %!assert_equal (days (days ([1:3]) * [1:3]'), 14) %!assert_equal (days (days ([1:3]') * [1:3]), [1, 2, 3; 2, 4, 6; 3, 6, 9]) %!assert_equal (days ([1:3] * days ([1:3]')), 14) %!assert_equal (days ([1:3]' * days ([1:3])), [1, 2, 3; 2, 4, 6; 3, 6, 9]) %!assert_equal (class (1 * duration), 'duration') %!assert_equal (class (days (magic (3)) * magic (3)), 'duration') %!assert_equal (days (days (magic (3)) * magic (3)), magic (3) * magic (3)) %!assert_equal (days (magic (3) * days (magic (3))), magic (3) * magic (3)) %!error ... %! days (1) * days (1) %!error ... %! days (1) * 'asd' %!error ... %! days (1) * string ({'asd'}) %!error ... %! {'asd'} * days (1) %!assert_equal (days ([1:3]) .\ days ([1:3]), [1, 1, 1]) %!assert_equal (days ([1:3] .\ days ([1:3])), [1, 1, 1]) %!assert_equal (days ([1; 2] .\ days ([1:3])), [1, 2, 3; 0.5, 1, 1.5]) %!assert_equal (class (1 .\ duration), 'duration') %!assert_equal (class (duration .\ duration), 'double') %!error ... %! days (1) .\ 1 %!error ... %! 'asd' .\ days (1) %!assert_equal (days ([1:3]) ./ days ([1:3]), [1, 1, 1]) %!assert_equal (days (days ([1:3]) ./ [1:3]), [1, 1, 1]) %!assert_equal (days (days ([1; 2]) ./ [1:3]), [1, 0.5, 1/3; 2, 1, 2/3]) %!assert_equal (class (duration ./ 1), 'duration') %!assert_equal (class (duration ./ duration), 'double') %!error ... %! 1 ./ days (1) %!error ... %! days (1) ./ 'asd' %!assert_equal (days(1):days(10), days ([1:10])) %!assert_equal (days(1):2:days(10), days ([1:2:10])) %!assert_equal (days(1):days(2):days(10), days ([1:2:10])) %!test %! R = days(1):'72:00:00'; %! assert_equal (R, days ([1:3])) %! assert_equal (R.Format, 'd') %!test %! R = '00:00:00':days(1):'72:00:00'; %! assert_equal (R, days ([0:3])) %! assert_equal (R.Format, 'd') %!test %! R = days(1):'06:00:00':days(10); %! assert_equal (R, days ([1:0.25:10])) %! assert_equal (R.Format, 'd') %!assert_equal (days(1):{'72:00:00'}, days ([1:3])) %!assert_equal (days(1):string('72:00:00'), days ([1:3])) %!assert_equal (string('00:00:00'):days(1):string('72:00:00'), days ([0:3])) %!assert_equal (days(1):string({'06:00:00'}):days(10), days ([1:0.25:10])) %!assert_equal (days(1):{'06:00:00'}:days(10), days ([1:0.25:10])) %!error colon (days (1)) %!error colon (days (1), 2, 3, 4) %!error days([1, 2]):days(2) %!error days(1):days([1, 2]) %!error days(1):days(2):days([1, 2]) %!assert_equal (days (linspace (days (1), days (2))), linspace (1, 2)) %!assert_equal (days (linspace (days (1), days (2), 15)), linspace (1, 2, 15)) %!assert_equal (days (linspace (days (1), 2)), linspace (1, 2)) %!assert_equal (days (linspace (1, days (2))), linspace (1, 2)) %!assert_equal (days (linspace (1, days (2), 15)), linspace (1, 2, 15)) %!assert_equal (days (linspace (days (1), '48:00:00')), linspace (1, 2)) %!assert_equal (days (linspace (days (1), {'48:00:00'})), linspace (1, 2)) %!assert_equal (days (linspace (days (1), string ({'48:00:00'}))), linspace (1, 2)) %!assert_equal (days (linspace (days (1), '48:00:00', 15)), linspace (1, 2, 15)) %!assert_equal (days (linspace (days (1), {'48:00:00'}, 15)), linspace (1, 2, 15)) %!assert_equal (days (linspace (days (1), string ({'48:00:00'}), 15)), linspace (1, 2, 15)) %!assert_equal (days (linspace ('48:00:00', days (1))), linspace (2, 1)) %!assert_equal (days (linspace ({'48:00:00'}, days (1))), linspace (2, 1)) %!assert_equal (days (linspace (string ({'48:00:00'}), days (1))), linspace (2, 1)) %!assert_equal (days (linspace ('48:00:00', days (1), 15)), linspace (2, 1, 15)) %!assert_equal (days (linspace ({'48:00:00'}, days (1), 15)), linspace (2, 1, 15)) %!assert_equal (days (linspace (string ({'48:00:00'}), days (1), 15)), linspace (2, 1, 15)) %!error ... %! linspace (days (1)) %!assert_equal (days (diff (days (magic (3)))), diff (magic (3))) %!assert_equal (days (diff (days (magic (3)), 2)), diff (magic (3), 2)) %!assert_equal (days (diff (days (magic (3)), 1, 1)), diff (magic (3), 1, 1)) %!assert_equal (days (diff (days (magic (3)), 1, 2)), diff (magic (3), 1, 2)) %!assert_equal (sum (days ([1, 2, 3])), days (6)) %!assert_equal (sum (days ([-1; -2; -3])), days (-6)) %!assert_equal (sum (days ([1, 2; 3, 4]), 1), days ([4, 6])) %!assert_equal (sum (days ([1, 2; 3, 4]), 2), days ([3; 7])) %!assert_equal (sum (days ([])), days(0)) %!assert_equal (size (sum (days ([]), 1)), [1, 0]) %!assert_equal (size (sum (days ([]), 2)), [0, 1]) %!assert_equal (size (sum (days ([]), 3)), [0, 0]) %!assert_equal (sum (days (zeros (1, 0))), days (0)) %!assert_equal (sum (days (zeros (1, 0)), 1), days (zeros (1, 0))) %!assert_equal (sum (days (zeros (1, 0)), 2), days (0)) %!assert_equal (sum (days (zeros (0, 1))), days (0)) %!assert_equal (sum (days (zeros (0, 1)), 1), days (0)) %!assert_equal (sum (days (zeros (0, 1)), 2), days (zeros (0, 1))) %!test %! x = repmat (days ([1:20;6:25]), [5, 2, 6, 3]); %! assert_equal (size (sum (x, [3, 2])), [10, 1, 1, 3]); %! assert_equal (size (sum (x, [1, 2])), [1, 1, 6, 3]); %! assert_equal (size (sum (x, [1, 2, 4])), [1, 1, 6]); %! assert_equal (size (sum (x, [1, 4, 3])), [1, 40]); %! assert_equal (size (sum (x, [1, 2, 3, 4])), [1, 1]); %!assert_equal (sum (days (ones (2,2)), 3), days (ones (2,2))) %!assert_equal (sum (days (ones (2,2,2)), 99), days (ones (2,2,2))) %!assert_equal (sum (days (magic (3)), 3), days (magic (3))) %!test %! x = days (ones (3, 4, 5)); %! x(1) = NaN; %! assert_equal (isequaln (sum (x)(:,:,1), days ([NaN, 3, 3, 3])), true); %! assert_equal (isequaln (sum (x, "includenan")(:,:,1), days ([NaN, 3, 3, 3])), true); %! assert_equal (sum (x, "omitnan")(:,:,1), days ([2, 3, 3, 3])); %! assert_equal (sum (x, "omitmissing")(:,:,1), days ([2, 3, 3, 3])); %! assert_equal (isequaln (sum (x, [2 3]), days ([NaN; 20; 20])), true); %! assert_equal (sum (x, [2 3], "omitnan"), days ([19; 20; 20])); %!assert_equal (cumsum (days ([1, 2, 3])), days ([1, 3, 6])) %!assert_equal (cumsum (days ([-1; -2; -3])), days ([-1; -3; -6])) %!assert_equal (cumsum (days ([1, 2; 3, 4]), 1), days ([1, 2; 4, 6])) %!assert_equal (cumsum (days ([1, 2; 3, 4]), 2), days ([1, 3; 3, 7])) %!test %! x = reshape (days ([1:8]), 2, 2, 2); %! y = cumsum (x); %! assert_equal (y(:,:,1), days ([1, 3; 3, 7])); %! assert_equal (y(:,:,2), days ([5, 7; 11, 15])); %! assert_equal (flip (cumsum (flip (x))), cumsum (x, "reverse")); %! y = cumsum (x, 2); %! assert_equal (y(:,:,1), days ([1, 4; 2, 6])); %! assert_equal (y(:,:,2), days ([5, 12; 6, 14])); %! y = cumsum (x, 2, "reverse"); %! assert_equal (y(:,:,1), days ([4, 3; 6, 4])); %! assert_equal (y(:,:,2), days ([12, 7; 14, 8])); %! assert_equal (flip (cumsum (flip (x, 2), 2), 2), cumsum (x, 2, "reverse")); %! y = cumsum (x, [1, 2]); %! assert_equal (y(:,:,1), days ([1; 3; 6; 10])); %! assert_equal (y(:,:,2), days ([5; 11; 18; 26])); %! y = cumsum (x, [1, 3]); %! assert_equal (y(:,1), days ([1; 3; 8; 14])); %! assert_equal (y(:,2), days ([3; 7; 14; 22])); %! y = cumsum (x, [2, 3]); %! assert_equal (y(1,:), days ([1, 4, 9, 16])); %! assert_equal (y(2,:), days ([2, 6, 12, 20])); %! assert_equal (cumsum (x, [1, 2, 3]), cumsum (x, "all")); %!test %! x = reshape (days ([1:8]), 2, 2, 2); %! assert_equal (cumsum (x, 4), x); %! assert_equal (cumsum (x, 2), cumsum (x, [2, 4])); %! assert_equal (cumsum (x, 2, "reverse"), cumsum (x, [2, 4], "reverse")); %! x(3) = NaN; %! assert_equal (isequaln (cumsum (x, 4), x), true); %! y = x; %! y(3) = 0; %! assert_equal (cumsum (x, 4, "omitnan"), y); %! assert_equal (cumsum (x, 2, "omitnan"), cumsum (x, [2, 4], "omitnan")); %! assert_equal (cumsum (x, 2, "reverse", "omitnan"), %! cumsum (x, [2, 4], "reverse", "omitnan")); %!assert_equal (cumsum (days ([NaN, NaN]), "omitnan"), days ([0, 0])) %!assert_equal (min (days ([1, 4, 2, 3])), days (1)) %!assert_equal (min (days ([1; -10; 5; -2])), days (-10)) %!test %! x = reshape (days ([1:8]), [2,2,2]); %! assert_equal (min (x, [], 1), reshape (days ([1, 3, 5, 7]), [1, 2, 2])); %! assert_equal (min (x, [], 2), reshape (days ([1, 2, 5, 6]), [2, 1, 2])); %! [y, i] = min (x, [], 3); %! assert_equal (ndims (y), 2); %! assert_equal (y, days ([1, 3; 2, 4])); %! assert_equal (ndims (i), 2); %! assert_equal (i, [1, 1; 1, 1]); %!test %! x = reshape (days ([1:8]), [2, 2, 2]); %! assert_equal (min (x, [], [1, 3]), days ([1, 3])); %! assert_equal (min (x, [], [2, 3]), days ([1; 2])); %! assert_equal (min (x, [], [1, 2]), reshape (days ([1, 5]), [1, 1, 2])); %! assert_equal (min (x, [], [1, 2, 3]), min (x, [], "all")); %!test %! x = days ([1, 2, 3, 4]); %! y = flip (x); %! assert_equal (min (x, y), days ([1, 2, 2, 1])); %! assert_equal (min (x, 3), days ([1, 2, 3, 3])); %! assert_equal (min (2, x), days ([1, 2, 2, 2])); %!test %! [m, i] = min (days ([1, 2, 3; 4, 3, NaN; 4, 5, 6]), [], 2, "includenan"); %! assert_equal (isequaln (m, days ([1; NaN; 4])), true); %! assert_equal (i, [1; 3; 1]); %! [m,i] = min (days ([1, 2, 3; 4, NaN, NaN; 4, 5, 6]), [], 2, "includenan"); %! assert_equal (isequaln (m, [1; NaN; 4]), true); %! assert_equal (i, [1; 2; 1]); %!test %! x = days (magic (3)); %! x(2, 3) = NaN; %! assert_equal (isequaln (min (x, [], 2, "includenan"), days ([1; NaN; 2])), true); %!assert_equal (size (min (days (0), days (zeros (0, 1)))), [0, 1]) %!assert_equal (size (min (days ([]), days (zeros (0, 1)))), [0, 0]) %!assert_equal (size (min (days (zeros (0, 1)), days ([]))), [0, 0]) %!assert_equal (size (min (days ([]), days (zeros (1, 0)))), [0, 0]) %!assert_equal (size (min (days (zeros (1, 0)), days ([]))), [0, 0]) %!test %! x = repmat (days ([4, 3, 2, 4, 1, 5, 3, 2]), 3, 2, 5, 2); %! [m, i] = min (x, [], [2, 3], 'linear'); %! assert_equal (m, days (ones (3, 1, 1, 2))); %! assert_equal (i(:,:,1,1), [13; 14; 15]); %! assert_equal (i(:,:,1,2), [253; 254; 255]); %!test %! x = repmat (days ([4, 3, 2, 4, 1, 5, 3, 2]), 3, 2, 5, 2); %! [m, i] = min (x, [], [1, 3], 'linear'); %! assert_equal (m, x(1,:,1,:)); %! assert_equal (i(:,:,1,1), [1:3:46]); %! assert_equal (i(:,:,1,2), [241:3:286]); %!assert_equal (cummin (days ([1, 4, 2, 3])), days ([1, 1, 1, 1])) %!assert_equal (cummin (days ([1; -10; 5; -2])), days ([1; -10; -10; -10])) %!test %! x = reshape (days ([1:8]), [2, 2, 2]); %! assert_equal (cummin (x, 1), reshape (days ([1, 1, 3, 3, 5, 5, 7, 7]), [2, 2, 2])); %! assert_equal (cummin (x, 2), reshape (days ([1, 2, 1, 2, 5, 6, 5, 6]), [2, 2, 2])); %! [w, iw] = cummin (x, 3); %! assert_equal (ndims (w), 3); %! assert_equal (w, repmat (days ([1, 3; 2, 4]), [1, 1, 2])); %! assert_equal (ndims (iw), 3); %! assert_equal (iw, ones (2, 2, 2)); %!assert_equal (isequaln (cummin (days ([1; -10; NaN; -2]), 'includenan'), days ([1; -10; NaN; NaN])), true) %!assert_equal (isequaln (cummin (days ([1; -10; NaN; -2]), 'reverse', 'includenan'), days ([NaN(3,1); -2])), true) %!assert_equal (isequaln (cummin (days ([1, -10, NaN, -2]), 'includenan'), days ([1, -10, NaN, NaN])), true) %!assert_equal (isequaln (cummin (days ([1, -10, NaN, -2]), 'reverse', 'includenan'), days ([NaN(1,3), -2])), true) %!shared A %! A = days ([3, 5, NaN, 4, 2; 2, 6, 2, 9, 4; 1, 3, 0, NaN, 1; 5, 3, 4, 2, 0]); %!test %! [m, i] = cummin (A, 'includenan'); %! m_exp = days ([3, 5, NaN, 4, 2; 2, 5, NaN, 4, 2; ... %! 1, 3, NaN, NaN, 1; 1, 3, NaN, NaN, 0]); %! i_exp = [1, 1, 1, 1, 1; 2, 1, 1, 1, 1; 3, 3, 1, 3, 3; 3, 3, 1, 3, 4]; %! assert_equal (isequaln (m, m_exp), true); %! assert_equal (i, i_exp); %!test %! [m, i] = cummin (A, 'includenan', 'reverse'); %! m_exp = days ([1, 3, NaN, NaN, 0; 1, 3, 0, NaN, 0; ... %! 1, 3, 0, NaN, 0; 5, 3, 4, 2, 0]); %! i_exp = [3, 4, 1, 3, 4; 3, 4, 3, 3, 4; 3, 4, 3, 3, 4; 4, 4, 4, 4, 4]; %! assert_equal (isequaln (m, m_exp), true); %! assert_equal (i, i_exp); %!test %! [m, i] = cummin (A, 'reverse'); %! m_exp = days ([1, 3, 0, 2, 0; 1, 3, 0, 2, 0; 1, 3, 0, 2, 0; 5, 3, 4, 2, 0]); %! i_exp = [3, 4, 3, 4, 4; 3, 4, 3, 4, 4; 3, 4, 3, 4, 4; 4, 4, 4, 4, 4]; %! assert_equal (m, m_exp); %! assert_equal (i, i_exp); %!test %! [m, i] = cummin (A); %! m_exp = days ([3, 5, NaN, 4, 2; 2, 5, 2, 4, 2; 1, 3, 0, 4, 1; 1, 3, 0, 2, 0]); %! i_exp = [1, 1, 1, 1, 1; 2, 1, 2, 1, 1; 3, 3, 3, 1, 3; 3, 3, 3, 4, 4]; %! assert_equal (isequaln (m, m_exp), true); %! assert_equal (i, i_exp); %!test %! [m, i] = cummin (A, 2, 'includenan'); %! m_exp = days ([3, 3, NaN, NaN, NaN; 2, 2, 2, 2, 2; 1, 1, 0, NaN, NaN; 5, 3, 3, 2, 0]); %! i_exp = [1, 1, 3, 3, 3; 1, 1, 1, 1, 1; 1, 1, 3, 4, 4; 1, 2, 2, 4, 5]; %! assert_equal (isequaln (m, m_exp), true); %! assert_equal (i, i_exp); %!test %! [m, i] = cummin (A, 2, 'includenan', 'reverse'); %! m_exp = days ([NaN, NaN, NaN, 2, 2; 2, 2, 2, 4, 4; ... %! NaN, NaN, NaN, NaN, 1; 0, 0, 0, 0, 0]); %! i_exp = [3, 3, 3, 5, 5; 3, 3, 3, 5, 5; 4, 4, 4, 4, 5; 5, 5, 5, 5, 5]; %! assert_equal (isequaln (m, m_exp), true); %! assert_equal (i, i_exp); %!test %! [m, i] = cummin (A, 2, 'reverse'); %! m_exp = days ([2, 2, 2, 2, 2; 2, 2, 2, 4, 4; 0, 0, 0, 1, 1; 0, 0, 0, 0, 0]); %! i_exp = [5, 5, 5, 5, 5; 3, 3, 3, 5, 5; 3, 3, 3, 5, 5; 5, 5, 5, 5, 5]; %! assert_equal (m, m_exp); %! assert_equal (i, i_exp); %!test %! [m, i] = cummin (A, 2); %! m_exp = days ([3, 3, 3, 3, 2; 2, 2, 2, 2, 2; 1, 1, 0, 0, 0; 5, 3, 3, 2, 0]); %! i_exp = [1, 1, 1, 1, 5; 1, 1, 1, 1, 1; 1, 1, 3, 3, 3; 1, 2, 2, 4, 5]; %! assert_equal (m, m_exp); %! assert_equal (i, i_exp); %!test %! [~, i_lin] = cummin (A, 'includenan', 'linear'); %! assert_equal (isequaln (cummin (A, 'includenan'), A(i_lin)), true); %!test %! [~, i_lin] = cummin (A, 'includenan', 'reverse', 'linear'); %! assert_equal (isequaln (cummin (A, 'includenan', 'reverse'), A(i_lin)), true); %!test %! [~, i_lin] = cummin (A, 'reverse', 'linear'); %! assert_equal (isequaln (cummin (A, 'reverse'), A(i_lin)), true); %!test %! [~, i_lin] = cummin (A, 'linear'); %! assert_equal (isequaln (cummin (A), A(i_lin)), true); %!test %! [~, i_lin] = cummin (A, 2, 'includenan', 'linear'); %! assert_equal (isequaln (cummin (A, 2, 'includenan'), A(i_lin)), true); %!test %! [~, i_lin] = cummin (A, 2, 'includenan', 'reverse', 'linear'); %! assert_equal (isequaln (cummin (A, 2, 'includenan', 'reverse'), A(i_lin)), true); %!test %! [~, i_lin] = cummin (A, 2, 'reverse', 'linear'); %! assert_equal (isequaln (cummin (A, 2, 'reverse'), A(i_lin)), true); %!test %! [~, i_lin] = cummin (A, 2, 'linear'); %! assert_equal (isequaln (cummin (A, 2), A(i_lin)), true); %!shared x %! x = days (randi ([-10, 10], 3, 4, 5, 2)); %!test %! [m, i] = cummin (x, [2, 3], 'linear'); %! assert_equal (m, x(i)); %!test %! [m, i] = cummin (x, [1, 3], 'linear'); %! assert_equal (m, x(i)); %!test %! [m, i] = cummin (x, [2, 4], 'linear'); %! assert_equal (m, x(i)); %!test %! [m, i] = cummin (x, [1, 4], 'linear'); %! assert_equal (m, x(i)); %!test %! [m, i] = cummin (x, [1, 2, 3], 'linear'); %! assert_equal (m, x(i)); %!test %! [m, i] = cummin (x, [2, 3, 4], 'linear'); %! assert_equal (m, x(i)); %!assert_equal (max (days ([1, 4, 2, 3])), days (4)) %!assert_equal (max (days ([1; -10; 5; -2])), days (5)) %!test %! x = reshape (days ([1:8]), [2,2,2]); %! assert_equal (max (x, [], 1), reshape (days ([2, 4, 6, 8]), [1, 2, 2])); %! assert_equal (max (x, [], 2), reshape (days ([3, 4, 7, 8]), [2, 1, 2])); %! [y, i] = max (x, [], 3); %! assert_equal (ndims (y), 2); %! assert_equal (y, days ([5, 7; 6, 8])); %! assert_equal (ndims (i), 2); %! assert_equal (i, [2, 2; 2, 2]); %!test %! x = reshape (days ([1:8]), [2, 2, 2]); %! assert_equal (max (x, [], [1, 3]), days ([6, 8])); %! assert_equal (max (x, [], [2, 3]), days ([7; 8])); %! assert_equal (max (x, [], [1, 2]), reshape (days ([4, 8]), [1, 1, 2])); %! assert_equal (max (x, [], [1, 2, 3]), max (x, [], "all")); %!test %! x = days ([1, 2, 3, 4]); %! y = flip (x); %! assert_equal (max (x, y), days ([4, 3, 3, 4])); %! assert_equal (max (x, 3), days ([3, 3, 3, 4])); %! assert_equal (max (2, x), days ([2, 2, 3, 4])); %!test %! [m, i] = max (days ([1, 2, 3; 4, 3, NaN; 4, 5, 6]), [], 2, "includenan"); %! assert_equal (isequaln (m, days ([3; NaN; 6])), true); %! assert_equal (i, [3; 3; 3]); %! [m,i] = max (days ([1, 2, 3; 4, NaN, NaN; 4, 5, 6]), [], 2, "includenan"); %! assert_equal (isequaln (m, [3; NaN; 6]), true); %! assert_equal (i, [3; 2; 3]); %!test %! x = days (magic (3)); %! x(2, 3) = NaN; %! assert_equal (isequaln (max (x, [], 2, "includenan"), days ([8; NaN; 9])), true); %!assert_equal (size (max (days (0), days (zeros (0, 1)))), [0, 1]) %!assert_equal (size (max (days ([]), days (zeros (0, 1)))), [0, 0]) %!assert_equal (size (max (days (zeros (0, 1)), days ([]))), [0, 0]) %!assert_equal (size (max (days ([]), days (zeros (1, 0)))), [0, 0]) %!assert_equal (size (max (days (zeros (1, 0)), days ([]))), [0, 0]) %!test %! x = repmat (days ([4, 3, 2, 4, 1, 5, 3, 2]), 3, 2, 5, 2); %! [m, i] = max (x, [], [2, 3], 'linear'); %! assert_equal (m, days (5 * ones (3, 1, 1, 2))); %! assert_equal (i(:,:,1,1), [16; 17; 18]); %! assert_equal (i(:,:,1,2), [256; 257; 258]); %!test %! x = repmat (days ([4, 3, 2, 4, 1, 5, 3, 2]), 3, 2, 5, 2); %! [m, i] = max (x, [], [1, 3], 'linear'); %! assert_equal (m, x(1,:,1,:)); %! assert_equal (i(:,:,1,1), [1:3:46]); %! assert_equal (i(:,:,1,2), [241:3:286]); %!assert_equal (cummax (days ([1, 4, 2, 3])), days ([1, 4, 4, 4])) %!assert_equal (cummax (days ([1; -10; 5; -2])), days ([1; 1; 5; 5])) %!test %! x = reshape (days ([8:-1:1]), [2, 2, 2]); %! assert_equal (cummax (x, 1), reshape (days ([8, 8, 6, 6, 4, 4, 2, 2]), [2, 2, 2])); %! assert_equal (cummax (x, 2), reshape (days ([8, 7, 8, 7, 4, 3, 4, 3]), [2, 2, 2])); %! [w, iw] = cummax (x, 3); %! assert_equal (ndims (w), 3); %! assert_equal (w, repmat (days ([8, 6; 7, 5]), [1, 1, 2])); %! assert_equal (ndims (iw), 3); %! assert_equal (iw, ones (2, 2, 2)); %!assert_equal (isequaln (cummax (days ([1; -10; NaN; -2]), 'includenan'), days ([1; 1; NaN; NaN])), true) %!assert_equal (isequaln (cummax (days ([1; -10; NaN; -2]), 'reverse', 'includenan'), days ([NaN(3,1); -2])), true) %!assert_equal (isequaln (cummax (days ([1, -10, NaN, -2]), 'includenan'), days ([1, 1, NaN, NaN])), true) %!assert_equal (isequaln (cummax (days ([1, -10, NaN, -2]), 'reverse', 'includenan'), days ([NaN(1,3), -2])), true) %!shared A %! A = days ([3, 5, NaN, 4, 2; 2, 6, 2, 9, 4; 1, 3, 0, NaN, 1; 5, 3, 4, 2, 0]); %!test %! [m, i] = cummax (A, 'includenan'); %! m_exp = days ([3, 5, NaN, 4, 2; 3, 6, NaN, 9, 4; ... %! 3, 6, NaN, NaN, 4; 5, 6, NaN, NaN, 4]); %! i_exp = [1, 1, 1, 1, 1; 1, 2, 1, 2, 2; 1, 2, 1, 3, 2; 4, 2, 1, 3, 2]; %! assert_equal (isequaln (m, m_exp), true); %! assert_equal (i, i_exp); %!test %! [m, i] = cummax (A, 'includenan', 'reverse'); %! m_exp = days ([5, 6, NaN, NaN, 4; 5, 6, 4, NaN, 4; ... %! 5, 3, 4, NaN, 1; 5, 3, 4, 2, 0]); %! i_exp = [4, 2, 1, 3, 2; 4, 2, 4, 3, 2; 4, 4, 4, 3, 3; 4, 4, 4, 4, 4]; %! assert_equal (isequaln (m, m_exp), true); %! assert_equal (i, i_exp); %!test %! [m, i] = cummax (A, 'reverse'); %! m_exp = days ([5, 6, 4, 9, 4; 5, 6, 4, 9, 4; 5, 3, 4, 2, 1; 5, 3, 4, 2, 0]); %! i_exp = [4, 2, 4, 2, 2; 4, 2, 4, 2, 2; 4, 4, 4, 4, 3; 4, 4, 4, 4, 4]; %! assert_equal (m, m_exp); %! assert_equal (i, i_exp); %!test %! [m, i] = cummax (A); %! m_exp = days ([3, 5, NaN, 4, 2; 3, 6, 2, 9, 4; 3, 6, 2, 9, 4; 5, 6, 4, 9, 4]); %! i_exp = [1, 1, 1, 1, 1; 1, 2, 2, 2, 2; 1, 2, 2, 2, 2; 4, 2, 4, 2, 2]; %! assert_equal (isequaln (m, m_exp), true); %! assert_equal (i, i_exp); %!test %! [m, i] = cummax (A, 2, 'includenan'); %! m_exp = days ([3, 5, NaN, NaN, NaN; 2, 6, 6, 9, 9; 1, 3, 3, NaN, NaN; 5, 5, 5, 5, 5]); %! i_exp = [1, 2, 3, 3, 3; 1, 2, 2, 4, 4; 1, 2, 2, 4, 4; 1, 1, 1, 1, 1]; %! assert_equal (isequaln (m, m_exp), true); %! assert_equal (i, i_exp); %!test %! [m, i] = cummax (A, 2, 'includenan', 'reverse'); %! m_exp = days ([NaN, NaN, NaN, 4, 2; 9, 9, 9, 9, 4; ... %! NaN, NaN, NaN, NaN, 1; 5, 4, 4, 2, 0]); %! i_exp = [3, 3, 3, 4, 5; 4, 4, 4, 4, 5; 4, 4, 4, 4, 5; 1, 3, 3, 4, 5]; %! assert_equal (isequaln (m, m_exp), true); %! assert_equal (i, i_exp); %!test %! [m, i] = cummax (A, 2, 'reverse'); %! m_exp = days ([5, 5, 4, 4, 2; 9, 9, 9, 9, 4; 3, 3, 1, 1, 1; 5, 4, 4, 2, 0]); %! i_exp = [2, 2, 4, 4, 5; 4, 4, 4, 4, 5; 2, 2, 5, 5, 5; 1, 3, 3, 4, 5]; %! assert_equal (m, m_exp); %! assert_equal (i, i_exp); %!test %! [m, i] = cummax (A, 2); %! m_exp = days ([3, 5, 5, 5, 5; 2, 6, 6, 9, 9; 1, 3, 3, 3, 3; 5, 5, 5, 5, 5]); %! i_exp = [1, 2, 2, 2, 2; 1, 2, 2, 4, 4; 1, 2, 2, 2, 2; 1, 1, 1, 1, 1]; %! assert_equal (m, m_exp); %! assert_equal (i, i_exp); %!test %! [~, i_lin] = cummax (A, 'includenan', 'linear'); %! assert_equal (isequaln (cummax (A, 'includenan'), A(i_lin)), true); %!test %! [~, i_lin] = cummax (A, 'includenan', 'reverse', 'linear'); %! assert_equal (isequaln (cummax (A, 'includenan', 'reverse'), A(i_lin)), true); %!test %! [~, i_lin] = cummax (A, 'reverse', 'linear'); %! assert_equal (isequaln (cummax (A, 'reverse'), A(i_lin)), true); %!test %! [~, i_lin] = cummax (A, 'linear'); %! assert_equal (isequaln (cummax (A), A(i_lin)), true); %!test %! [~, i_lin] = cummax (A, 2, 'includenan', 'linear'); %! assert_equal (isequaln (cummax (A, 2, 'includenan'), A(i_lin)), true); %!test %! [~, i_lin] = cummax (A, 2, 'includenan', 'reverse', 'linear'); %! assert_equal (isequaln (cummax (A, 2, 'includenan', 'reverse'), A(i_lin)), true); %!test %! [~, i_lin] = cummax (A, 2, 'reverse', 'linear'); %! assert_equal (isequaln (cummax (A, 2, 'reverse'), A(i_lin)), true); %!test %! [~, i_lin] = cummax (A, 2, 'linear'); %! assert_equal (isequaln (cummax (A, 2), A(i_lin)), true); %!shared x %! x = days (randi ([-10, 10], 3, 4, 5, 2)); %!test %! [m, i] = cummax (x, [2, 3], 'linear'); %! assert_equal (m, x(i)); %!test %! [m, i] = cummax (x, [1, 3], 'linear'); %! assert_equal (m, x(i)); %!test %! [m, i] = cummax (x, [2, 4], 'linear'); %! assert_equal (m, x(i)); %!test %! [m, i] = cummax (x, [1, 4], 'linear'); %! assert_equal (m, x(i)); %!test %! [m, i] = cummax (x, [1, 2, 3], 'linear'); %! assert_equal (m, x(i)); %!test %! [m, i] = cummax (x, [2, 3, 4], 'linear'); %! assert_equal (m, x(i)); %!test %! D = hours(8) + minutes(29:31) + seconds(1.23); %! D.Format = 'hh:mm:ss.SS'; %! assert_equal (dispstrings (D), {'08:29:01.23', '08:30:01.23', '08:31:01.23'}); %! Y = dispstrings (floor (D)); %! assert_equal (Y, {'08:29:01.00', '08:30:01.00', '08:31:01.00'}); %! Y = dispstrings (floor (D, 'seconds')); %! assert_equal (Y, {'08:29:01.00', '08:30:01.00', '08:31:01.00'}); %! Y = dispstrings (floor (D, 'minutes')); %! assert_equal (Y, {'08:29:00.00', '08:30:00.00', '08:31:00.00'}); %! Y = dispstrings (floor (D, 'hours')); %! assert_equal (Y, {'08:00:00.00', '08:00:00.00', '08:00:00.00'}); %!test %! D = days (3) + hours (6.5); %! assert_equal (floor (D, 'days'), days (3)); %!test %! D = hours(8) + minutes(29:31) + seconds(1.23); %! D.Format = 'hh:mm:ss.SS'; %! assert_equal (dispstrings (D), {'08:29:01.23', '08:30:01.23', '08:31:01.23'}); %! Y = dispstrings (ceil (D)); %! assert_equal (Y, {'08:29:02.00', '08:30:02.00', '08:31:02.00'}); %! Y = dispstrings (ceil (D, 'seconds')); %! assert_equal (Y, {'08:29:02.00', '08:30:02.00', '08:31:02.00'}); %! Y = dispstrings (ceil (D, 'minutes')); %! assert_equal (Y, {'08:30:00.00', '08:31:00.00', '08:32:00.00'}); %! Y = dispstrings (ceil (D, 'hours')); %! assert_equal (Y, {'09:00:00.00', '09:00:00.00', '09:00:00.00'}); %!test %! D = days (3) + hours (6.5); %! assert_equal (ceil (D, 'days'), days (4)); %!test %! D = hours(8) + minutes(29:31) + seconds(1.23); %! D.Format = 'hh:mm:ss.SS'; %! assert_equal (dispstrings (D), {'08:29:01.23', '08:30:01.23', '08:31:01.23'}); %! Y = dispstrings (round (D)); %! assert_equal (Y, {'08:29:01.00', '08:30:01.00', '08:31:01.00'}); %! Y = dispstrings (round (D, 'seconds')); %! assert_equal (Y, {'08:29:01.00', '08:30:01.00', '08:31:01.00'}); %! Y = dispstrings (round (D, 'minutes')); %! assert_equal (Y, {'08:29:00.00', '08:30:00.00', '08:31:00.00'}); %! Y = dispstrings (round (D, 'hours')); %! assert_equal (Y, {'08:00:00.00', '09:00:00.00', '09:00:00.00'}); %!test %! D = days (3) + hours (6.5); %! assert_equal (round (D, 'days'), days (3)); %!assert_equal (sign (days ([-1, 0, 1, NaN])), [-1, 0, 1, NaN]) %!assert_equal (sign (seconds ([-2:2] .* ones (5))), repmat ([-1, -1, 0, 1, 1], 5, 1)) ################################################################################ ## ** Statistical Operations ** ## ################################################################################ ## Available Methods ## ## ## ## 'bounds' 'center' 'histc' 'iqr' ## ## 'kendal' 'kurtosis' 'mad' 'mape' ## ## 'mean' 'median' 'mode' 'prctile' ## ## 'quantile' 'range' 'rmse' 'skewness' ## ## 'spearman' 'statistics' 'std' 'var' ## ## ## ################################################################################ %!test %! [s, l] = bounds (days (1:10)); %! assert_equal ([s, l], days ([1, 10])); %!test %! [s, l] = bounds (days ([10:-1:1]')); %! assert_equal ([s, l], days ([1, 10])); %!assert_equal (bounds (days (magic (3))), days ([3, 1, 2])) %!assert_equal (bounds (days (magic (3)), 2), days ([1; 3; 2])) %!test %! x = days (magic (3)); %! x(2,3) = NaN; %! assert_equal (bounds (x), days ([3, 1, 2])); %! assert_equal (bounds (x, "omitnan"), days ([3, 1, 2])); %! assert_equal (isequaln (bounds (x, "includenan"), days ([3, 1, NaN])), true); %! assert_equal (bounds (x, 2), days ([1; 3; 2])); %! assert_equal (bounds (x, 2, "omitnan"), days ([1; 3; 2])); %! assert_equal (isequaln (bounds (x, 2, "includenan"), days ([1; NaN; 2])), true); %!test %! x = reshape (days (1:27), [3, 3, 3]); %! [s, l] = bounds (x, 3); %! assert_equal (s, x(:,:,1)); %! assert_equal (l, x(:,:,3)); %!test %! [s, l] = bounds (x, [2, 3]); %! assert_equal (s, x(:,1,1)); %! assert_equal (l, x(:,3,3)); %!test %! [s, l] = bounds (x, [1, 3]); %! assert_equal (s, x(1,:,1)); %! assert_equal (l, x(3,:,3)); %!test %! [s, l] = bounds (x, [1, 2, 3]); %! assert_equal (s, days (1)); %! assert_equal (l, days (27)); %!test %! [s, l] = bounds (x, "all"); %! assert_equal (s, days (1)); %! assert_equal (l, days (27)); %!test %! x(3) = NaN; %! [s, l] = bounds (x, "all"); %! assert_equal (s, days (1)); %! assert_equal (l, days (27)); %!test %! [s,l] = bounds (x, "all", "omitnan"); %! assert_equal (s, days (1)); %! assert_equal (l, days (27)); %!test %! [s,l] = bounds (x, "all", "includenan"); %! assert_equal (isnan (s), true); %! assert_equal (isnan (l), true); %!test %! [s,l] = bounds (x, "includenan"); %! assert_equal (isequaln (s, min (x, [], "includenan")), true); %! assert_equal (isequaln (l, max (x, [], "includenan")), true); %!assert_equal (center (days ([1, 2, 3])), days ([-1,0,1])) %!assert_equal (center (days (ones (3, 2, 0, 2))), days (zeros (3, 2, 0,2 ))) %!assert_equal (center (days (magic (3))), days ([3, -4, 1; -2, 0, 2; -1, 4, -3])) %!assert_equal (center (days ([1, 2, 3; 6, 5, 4]), 2), days ([-1, 0, 1; 1, 0, -1])) %!assert_equal (center (days (1), 3), days (0)) %!assert_equal (center (repmat (days ([1, 2, 3]), 5, 1)), days (zeros (5, 3))) %!assert_equal (center (repmat (days ([1, 2, 3]), 5, 1), 2), repmat (days ([-1, 0, 1]), 5, 1)) %!test %! out = center (days ([1, 3, 2; 2, NaN, 1])); %! assert_equal (isequaln (out, days ([-0.5, NaN, 0.5; 0.5, NaN, -0.5])), true); %!test %! out = center (days ([1, 3, 2; 2, NaN, 1]), 'omitnan'); %! assert_equal (isequaln (out, days ([-0.5, 0, 0.5; 0.5, NaN, -0.5])), true); %!test %! out = center (days ([1, 3, 2; 2, NaN, 1]), 2, 'omitnan'); %! assert_equal (isequaln (out, days ([-1, 1, 0; 0.5, NaN, -0.5])), true); %!test %! x = linspace (days (0), days (10), 1001); %! n = histc (x, 0:10); %! assert_equal (n, [repmat(100, 1, 10), 1]); %!test %! x = repmat (linspace (days (0), days (10), 1001), [2, 1, 3]); %! n = histc (x, 0:10, 2); %! assert_equal (n, repmat ([repmat(100, 1, 10), 1], [2, 1, 3])); %!assert_equal (iqr (days (17)), duration) %!assert_equal (iqr (days (17), 1), duration) %!assert_equal (iqr (days (17), 4), duration) %!assert_equal (iqr (days ([1:3])), days (1.5)) %!assert_equal (iqr (days ([1:4])), days (2)) %!assert_equal (iqr (days ([1:5])), days (2.5)) %!assert_equal (iqr (days ([1:10])), days (5)) %!assert_equal (iqr (days ([1:10]')), days (5)) %!assert_equal (iqr (days ([1:10]), 2), days (5)) %!assert_equal (iqr (days ([1:10]), 1), days (zeros (1, 10))) %!assert_equal (iqr (days ([1:10]), 3), days (zeros (1, 10))) %!assert_equal (iqr (days ([1:5; 2:6]), [1, 2]), days (3)) %!assert_equal (iqr (days ([1:5; 2:6]), 'all'), days (3)) %!test %! x = reshape (days (1:6), [1, 2, 3]); %! assert_equal (iqr (x), days (ones (1, 1, 3))); %! assert_equal (iqr (x, 1), days (zeros (1, 2, 3))); %! assert_equal (iqr (x, 2), days (ones (1, 1, 3))); %! assert_equal (iqr (x, 3), days ([3, 3])); %!test %! x = magic (4); x = cat (3,x, 2*x, 3*x); x = cat (4, x, 2*x); %! y = cat (3, 8*[1, 1, 1, 1], 16*[1, 1, 1, 1], 24*[1, 1, 1, 1]); %! x = days (x); y = days (y); %! assert_equal (iqr (x), cat (4, y, 2*y)); %! assert_equal (iqr (x, 1), cat (4, y, 2*y)); %! y = cat (3, 4*[3, 1, 1, 3].', 8*[3, 1, 1, 3].', 12*[3, 1, 1, 3].'); %! y = days (y); %! assert_equal (iqr (x, 2), cat (4, y, 2*y)); %! y = [24, 3, 4.5, 19.5; 7.5, 16.5, 15, 12; 13.5, 10.5, 9, 18; 6, 21, 22.5, 1.5]; %! y = days (y); %! assert_equal (iqr (x, 3), cat (4, y, 2*y)); %! y = [16, 2, 3, 13; 5, 11, 10, 8; 9, 7, 6, 12; 4, 14, 15, 1]; %! y = days (y); %! assert_equal (iqr (x, 4), cat (3, y, 2*y, 3*y)); %! assert_equal (iqr (x, 5), days (zeros (size (x)))); %!assert_equal (iqr (days (17), [1, 8]), duration) %!assert_equal (iqr (days ([1, 2, 5; 2, 5, 6]), [1, 2]), days (3)) %!test %! x = days (cat (3, [1, 2, 5; 2, 5, 6], [1, 2, 5; 2, 5, 6])); %! assert_equal (iqr (x, [1, 2]), days (cat (3, 3, 3))); %! assert_equal (iqr (x, [1, 2]'), days (cat (3, 3, 3))); %!test %! x = [-1; 0; 0; 0; 1]; %! y = days ([x, 2*x]); %! assert_equal (kurtosis (y), [2.5, 2.5], sqrt (eps)); %!assert_equal (kurtosis (days ([-3, 0, 1])), kurtosis (days ([-1, 0, 3]))) %!assert_equal (kurtosis (days (ones (3, 5))), NaN (1, 5)) %!assert_equal (kurtosis (days (1), [], 3), NaN) %!test %! clear x; %! x(:,:,1) = [0.5377, 0.3188, 3.5784; 1.8339, -1.3077, 2.7694; ... %! -2.2588, -0.4336, -1.3499; 0.8622, 0.3426, 3.0349]; %! x(:,:,2) = [0.7254, -0.1241, 0.6715; -0.0631, 1.4897, -1.2075; ... %! 0.7147 1.4090 0.7172; -0.2050, 1.4172, 1.6302]; %! y = kurtosis (days (x)); %! assert_equal (class (y), 'double'); %! assert_equal (y(:,:,1), [2.1350, 1.7060, 2.2789], 1e-4); %! assert_equal (y(:,:,2), [1.0542, 2.3278, 2.0996], 1e-4); %! y = kurtosis (days (x), 1, 2); %! assert_equal (y(:,:,1), [1.5; 1.5; 1.5; 1.5], 8 * eps); %! assert_equal (y(:,:,2), [1.5; 1.5; 1.5; 1.5], 8 * eps); %! y = kurtosis (days (x), 1, 3); %! assert_equal (y, ones (4, 3), 8 * eps); %!test %! x(:,:,1) = [0.5377, 0.3188, 3.5784; 1.8339, -1.3077, 2.7694; ... %! -2.2588, -0.4336, -1.3499; 0.8622, 0.3426, 3.0349]; %! x(:,:,2) = [0.7254, -0.1241, 0.6715; -0.0631, 1.4897, -1.2075; ... %! 0.7147 1.4090 0.7172; -0.2050, 1.4172, 1.6302]; %! y = kurtosis (days (x), 1, "all"); %! assert_equal (y, 2.8029, 1e-4); %! y = kurtosis (days (x), 1, [1, 2]); %! assert_equal (y(:,:,1), 1.9345, 1e-4); %! assert_equal (y(:,:,2), 2.5877, 1e-4); %! y = kurtosis (days (x), 1, [2, 3]); %! assert_equal (y, [3.8457; 1.4306; 1.7094; 2.3378], 1e-4); %!assert_equal (mad (days (123)), duration) %!assert_equal (isnan (mad (days (Inf))), true) %!assert_equal (mad (days ([3, Inf])), days (Inf)) %!assert_equal (mad (days ([0, 0, 1, 2, 100])), days (31.76)) %!assert_equal (mad (days ([0, 0, 1, 2, 100]')), days (31.76)) %!assert_equal (mad (days ([0, 0, 1, 2, 100]), 1), days (1)) %!assert_equal (mad (days ([0, 0, 1, 2, 100]'), 1), days (1)) %!assert_equal (mad (days (magic (4))), days ([4, 4, 4, 4])) %!assert_equal (mad (days (magic (4)), [], 2), days ([6; 2; 2; 6])) %!assert_equal (mad (days (magic (4)), 1), days ([2.5, 3.5, 3.5, 2.5])) %!assert_equal (mad (days (magic (4)), 1, 2), days ([5.5; 1.5; 1.5; 5.5])) %!assert_equal (mad (days (magic (4)), 0, 3), days (zeros (4))) %!assert_equal (mad (days (magic (4)), 1, 3), days (zeros (4))) %!assert_equal (mad (days (cat (3, magic (4), magic (4)))), days (4 * ones (1, 4, 2))) %!assert_equal (mad (days (magic (4)), 0, "all"), days (4)) %!assert_equal (mad (days (magic (4)), 1, "all"), days (4)) %!assert_equal (mad (days (magic (4)), 0, [1 2]), days (4)) %!assert_equal (mad (days (magic (4)), 0, [1 3]), mad (days (magic (4)), 0, 1)) %!assert_equal (mad (days (magic (4)), 0, [1 2 3]), days (4)) %!assert_equal (mad (days (magic (4)), 1, [1 2]), days (4)) %!assert_equal (mad (days (magic (4)), 1, [1 3]), mad (days (magic (4)), 1, 1)) %!assert_equal (mad (days (magic (4)), 1, [1 2 3]), days (4)) %!assert_equal (mad (days (magic (4)), 0, [3 4 99]), days (zeros (4))) %!assert_equal (mad (days (magic (4)), 1, [3 4 99]), days (zeros (4))) %!assert_equal (isequaln (mad (days (NaN)), days (NaN)), true) %!assert_equal (isequaln (mad (days (NaN (2))), days (NaN (1, 2))), true) %!assert_equal (mad (days ([1, 2; 3, NaN])), days ([1, 0])) %!assert_equal (isequaln (mad (days ([1, 2; 3, NaN]), [], 1), days ([1, 0])), true) %!assert_equal (isequaln (mad (days ([1, NaN; 3, NaN]), [], 1), days ([1, NaN])), true) %!assert_equal (mad (days ([1, NaN; 3, NaN]), [], 2), days ([0; 0])) %!test %! F = days ([2, 11, 6]); %! A = days ([3, 10, 8]); %! assert_equal (mape (F, A), 22.7778, 1e-4); %! assert_equal (mape (F, A, 1), [33.3333, 10.0000, 25.0000], 1e-4); %! assert_equal (mape (F, A, 2), 22.7778, 1e-4); %! assert_equal (mape (F, A, 3), [33.3333, 10.0000, 25.0000], 1e-4); %! assert_equal (mape (F, A, 'all'), 22.7778, 1e-4); %!test %! F = days ([3; 7; 4; NaN]); %! A = days ([4; 0; 6; 4]); %! assert_equal (mape (F, A), NaN); %! assert_equal (mape (F, A, 'omitnan'), Inf); %! assert_equal (mape (F, A, 'omitzero'), NaN); %! assert_equal (mape (F, A, 'omitnan', 'omitzero'), 29.1667, 1e-4); %! assert_equal (mape (F, A, 1, 'omitnan', 'omitzero'), 29.1667, 1e-4); %! E = mape (F, A, 2, 'omitnan', 'omitzero'); %! assert_equal (E, [25; NaN; 33.3333; NaN], 1e-4); %!test %! F = days ([17, 21; 1, 5; 13, 17]); %! A = days ([18, 19; 5, 4; 15, 13]); %! assert_equal (mape (F, A), [32.9630, 22.0985], 1e-4); %! assert_equal (mape (F, A, 1), [32.9630, 22.0985], 1e-4); %! assert_equal (mape (F, A, 2), [8.0409; 52.5000; 22.0513], 1e-4); %! assert_equal (mape (F, A, 'all'), 27.5307, 1e-4); %! assert_equal (mape (F, A, [1, 2]), 27.5307, 1e-4); %!test %! F = days ([3, 5; -1, 2]); %! F(:,:,2) = days ([4, 2; 7, -4]); %! A = days ([6, 5; 0, 3]); %! E = mape (F, A, [1, 2]); %! assert_equal (E(:,:,1), Inf); %! assert_equal (E(:,:,2), Inf); %! E = mape (F, A, [1, 2], 'omitzero'); %! assert_equal (E(:,:,1), 27.7778, 1e-4); %! assert_equal (E(:,:,2), 108.8889, 1e-4); %! assert_equal (mape (F, A, [1, 3]), [Inf, 81.6667], 1e-4); %! assert_equal (mape (F, A, [1, 3], 'omitzero'), [41.6667, 81.6667], 1e-4); %! assert_equal (mape (F, A, [2, 3]), [35.8333; Inf], 1e-4); %! assert_equal (mape (F, A, [2, 3], 'omitzero'), [35.8333; 133.3333], 1e-4); %!assert_equal (mape ([1:4], zeros (1, 4)), Inf) %!assert_equal (mape ([1:4], zeros (1, 4), "omitzero"), NaN) %!test %! F = days ([2, 11, 6]); %! A = days ([3, 10, 8]); %! W = [1, 2, 1]; %! assert_equal (mape (F, A, 'Weights', W), 19.5833, 1e-4); %! assert_equal (mape (F, A, 1, 'Weights', W), [33.3333, 10, 25], 1e-4); %! assert_equal (mape (F, A, 2, 'Weights', W), 19.5833, 1e-4); %! assert_equal (mape (F, A, 3, 'Weights', W), [33.3333, 10, 25], 1e-4); %!test %! F = days ([3; 7; 4; NaN]); %! A = days ([4; 0; 6; 4]); %! W = [1, 2, 1, 2]; %! assert_equal (mape (F, A, 'Weights', W), NaN); %! assert_equal (mape (F, A, 'omitnan', 'Weights', W), Inf); %! assert_equal (mape (F, A, 'omitzero', 'Weights', W), NaN); %! assert_equal (mape (F, A, 'omitnan', 'omitzero', 'Weights', W), 29.1667, 1e-4); %! assert_equal (mape (F, A, 1, 'omitnan', 'omitzero', 'Weights', W), 29.1667, 1e-4); %!test %! F = days ([3; 7; 4; NaN]); %! A = days ([4; 0; 6; 4]); %! W = [1; 2; 1; 2]; %! E = mape (F, A, 2, 'omitnan', 'omitzero', 'Weights', W); %! assert_equal (E, [25; NaN; 33.3333; NaN], 1e-4); %! E = mape (F, A, 3, 'omitnan', 'omitzero', 'Weights', W); %! assert_equal (E, [25; NaN; 33.3333; NaN], 1e-4); %!test %! F = days ([3, 5; -1, 2]); %! F(:,:,2) = days ([4, 2; 7, -4]); %! A = days ([6, 5; 0, 3]); %! W = [1, 1; 1, 1]; %! Ew = mape (F, A, [1, 2], 'omitzero', 'Weights', W); %! E = mape (F, A, [1, 2], 'omitzero'); %! assert_equal (Ew, E); %! Ew = mape (F, A, [1, 3], 'omitzero', 'Weights', W); %! E = mape (F, A, [1, 3], 'omitzero'); %! assert_equal (Ew, E); %! Ew = mape (F, A, [2, 3], 'omitzero', 'Weights', W); %! E = mape (F, A, [2, 3], 'omitzero'); %! assert_equal (Ew, E); %!test %! F = days ([3, 5; -1, 2]); %! F(:,:,2) = days ([4, NaN; 7, -4]); %! A = days ([6, 5; 2, 3]); %! W = [1, 1; 1, 1]; %! Ew = mape (F, A, [1, 2], 'omitnan', 'Weights', W); %! E = mape (F, A, [1, 2], 'omitnan'); %! assert_equal (Ew, E); %! Ew = mape (F, A, [1, 3], 'omitnan','Weights', W); %! E = mape (F, A, [1, 3], 'omitnan'); %! assert_equal (Ew, E); %! Ew = mape (F, A, [2, 3], 'omitnan','Weights', W); %! E = mape (F, A, [2, 3], 'omitnan'); %! assert_equal (Ew, E); %!error ... %! mape (days ([1, 2, 3]), 1:3) %!error ... %! mape (1:3, days ([1, 2, 3])) %!assert_equal (mean (days (magic (3)), 1), days ([5, 5, 5])) %!assert_equal (mean (days (magic (3)), 2), days ([5; 5; 5])) %!test %! x = days ([-10:10]); %! y = [x;x+5;x-5]; %! assert_equal (mean (x), days (0)); %! assert_equal (mean (y, 2), days ([0, 5, -5]')); %! assert_equal (mean (y, 'all'), days (0)); %! y(2,4) = NaN; %! assert_equal (all (mean (y', 'omitnan') - days ([0, 5.35, -5]) < 1e-14), true); %! z = y + 20; %! assert_equal (isequaln (mean (z, "all"), days (NaN)), true); %! assert_equal (isequaln (mean (z, "all", "includenan"), days (NaN)), true); %! assert_equal (days (mean (z, "all", "omitnan")), 20.03225806451613, 4e-14); %!test %! x = repmat ([1:20;6:25], [5 2 6 3]); %! assert_equal (size (mean (x, [3 2])), [10 1 1 3]); %! assert_equal (size (mean (x, [1 2])), [1 1 6 3]); %! assert_equal (size (mean (x, [1 2 4])), [1 1 6]); %! assert_equal (size (mean (x, [1 4 3])), [1 40]); %! assert_equal (size (mean (x, [1 2 3 4])), [1 1]); ## Test exceeding dimensions %!assert_equal (mean (days (ones (2, 2)), 3), days (ones (2, 2))) %!assert_equal (mean (days (ones (2, 2, 2)), 99), days (ones (2, 2, 2))) %!assert_equal (mean (days (magic (3)), 3), days (magic (3))) %!assert_equal (mean (days (magic (3)), [1, 3]), days ([5, 5, 5])) %!assert_equal (mean (days (magic (3)), [1, 99]), days ([5, 5, 5])) %!test %! x = days ([1, 1; 7, 9; 1, 9; 1, 9; 6, 2]); %! w = [1; 2; 1; 2; 3]; %! assert_equal (days (mean (x, 'Weights', w)), [4, 5.7778], 1e-4); %! assert_equal (days (mean (x, 'Weights', w')), [4, 5.7778], 1e-4); %! assert_equal (days (mean (x, 1, 'Weights', w)), [4, 5.7778], 1e-4); %! assert_equal (days (mean (x, 1, 'Weights', w')), [4, 5.7778], 1e-4); %! assert_equal (days (mean (x', 2, 'Weights', w)), [4; 5.7778], 1e-4); %! assert_equal (days (mean (x', 2, 'Weights', w')), [4; 5.7778], 1e-4); %!test %! x = days ([1, 1; 7, 9; 1, 9; 1, 9; 6, 2]); %! x = reshape (x', [1, 2, 5]); %! w = [1, 2, 1, 2, 3]; %! assert_equal (size (x)([1, 3]), size (w)); %! assert_equal (days (mean (x, [1, 3], 'Weights', w)), [4, 5.7778], 1e-4); %!test %! x = days ([1, 1; 7, 9; 1, 9; 1, 9; 6, 2]); %! x = reshape (x', [1, 2, 5]); %! w = ones (1, 2, 5); %! assert_equal (size (x), size (w)); %! assert_equal (days (mean (x, 'all', 'Weights', w)), days (mean (x, 'all')), 1e-4); %! assert_equal (days (mean (x, [1, 2, 3], 'Weights', w)), days (mean (x, [1, 2, 3])), 1e-4); %!assert_equal (median (days (1)), days (1)) %!assert_equal (median (days ([1, 2, 3])), days (2)) %!assert_equal (median (days ([1, 2, 3]')), days (2)) %!assert_equal (median (days (cat (3, 3, 1, 2))), days (2)) %!assert_equal (median (days ([3, 1, 2])), days (2)) %!assert_equal (median (days ([2, 4, 6, 8])), days (5)) %!assert_equal (median (days ([8, 2, 6, 4])), days (5)) %!assert_equal (median (days ([1, 2]), 3), days ([1, 2])) %!test %! x = days ([1, 2, 3, 4, 5, 6]); %! x2 = x'; %! y = days ([1, 2, 3, 4, 5, 6, 7]); %! y2 = y'; %! %! assert_equal (median (x) == median (x2) && median (x) == days (3.5), true); %! assert_equal (median (y) == median (y2) && median (y) == days (4), true); %! assert_equal (median ([x2, 2 * x2]), days ([3.5, 7])); %! assert_equal (median ([y2, 3 * y2]), days ([4, 12])); %!test %! x = repmat (days ([2 2.1 2.2 2 NaN; 3 1 2 NaN 5; 1 1.1 1.4 5 3]), [1, 1, 4]); %! y = repmat (days ([2 1.1 2 NaN NaN]), [1, 1, 4]); %! assert_equal (isequaln (median (x), y), true); %! assert_equal (isequaln (median (x, 1), y), true); %! y = repmat (days ([2, 1.1, 2, 3.5, 4]), [1, 1, 4]); %! assert_equal (median (x, "omitnan"), y); %! assert_equal (median (x, 1, "omitnan"), y); %! y = repmat (days ([2.05; 2.5; 1.4]), [1, 1, 4]); %! assert_equal (median (x, 2, "omitnan"), y); %! y = repmat (days ([NaN; NaN; 1.4]), [1, 1, 4]); %! assert_equal (isequaln (median (x, 2), y), true); %! assert_equal (isequaln (median (x, "all"), NaN), true); %! assert_equal (isequaln (median (x, "all", "omitnan"), 2), true); %!assert_equal (median (cat (3, 3, 1, NaN, 2), "omitnan"), 2) %!assert_equal (median (cat (3, 3, 1, NaN, 2), 3, "omitnan"), 2) %!test %! [m, f, c] = mode (days (toeplitz (1:5))); %! assert_equal (m, days ([1, 2, 2, 2, 1])); %! assert_equal (f, [1, 2, 2, 2, 1]); %! assert_equal (c, {days([1;2;3;4;5]), days(2), days([2;3]), days(2), days([1;2;3;4;5])}); %!test %! [m, f, c] = mode (days (toeplitz (1:5)), 2); %! assert_equal (m, days ([1; 2; 2; 2;1 ])); %! assert_equal (f, [1; 2; 2; 2; 1]); %! assert_equal (c, {days([1;2;3;4;5]); days(2); days([2;3]); days(2); days([1;2;3;4;5])}); %!assert_equal (mode (days ([2,3,1,2,3,4]), 1), days ([2,3,1,2,3,4])) %!assert_equal (mode (days ([2,3,1,2,3,4]), 2), days (2)) %!assert_equal (mode (days ([2,3,1,2,3,4])), days (2)) %!assert_equal (mode (days ([2;3;1;2;3;4]), 1), days (2)) %!assert_equal (mode (days ([2;3;1;2;3;4]), 2), days ([2;3;1;2;3;4])) %!assert_equal (mode (days ([2;3;1;2;3;4])), days (2)) %!shared %!shared x %! x(:,:,1) = toeplitz (1:3); %! x(:,:,2) = circshift (toeplitz (1:3), 1); %! x(:,:,3) = circshift (toeplitz (1:3), 2); %! x = days (x); %!test %! [m, f, c] = mode (x, 1); %! assert_equal (reshape (m, [3, 3]), days ([1, 1, 1; 2, 2, 2; 1, 1, 1])); %! assert_equal (reshape (f, [3, 3]), [1, 1, 1; 2, 2, 2; 1, 1, 1]); %! c = reshape (c, [3, 3]); %! assert_equal (c{1}, days ([1; 2; 3])); %! assert_equal (c{2}, days (2)); %! assert_equal (c{3}, days ([1; 2; 3])); %!test %! [m, f, c] = mode (x, 2); %! assert_equal (reshape (m, [3, 3]), days ([1, 1, 2; 2, 1, 1; 1, 2, 1])); %! assert_equal (reshape (f, [3, 3]), [1, 1, 2; 2, 1, 1; 1, 2, 1]); %! c = reshape (c, [3, 3]); %! assert_equal (c{1}, days ([1; 2; 3])); %! assert_equal (c{2}, days (2)); %! assert_equal (c{3}, days ([1; 2; 3])); %!test %! [m, f, c] = mode (x, 3); %! assert_equal (reshape (m, [3, 3]), days ([1, 2, 1; 1, 2, 1; 1, 2, 1])); %! assert_equal (reshape (f, [3, 3]), [1, 2, 1; 1, 2, 1; 1, 2, 1]); %! c = reshape (c, [3, 3]); %! assert_equal (c{1}, days ([1; 2; 3])); %! assert_equal (c{2}, days ([1; 2; 3])); %! assert_equal (c{3}, days ([1; 2; 3])); %!shared %!test %! pct = 50; %! q = prctile (days (1:4), pct); %! qa = days (2.5); %! assert_equal (q, qa); %! q = prctile (days (1:4), pct, 1); %! qa = days ([1, 2, 3, 4]); %! assert_equal (q, qa); %! q = prctile (days (1:4), pct, 2); %! qa = days (2.5); %! assert_equal (q, qa); %!test %! pct = [50 75]; %! q = prctile (days (1:4), pct); %! qa = days ([2.5, 3.5]); %! assert_equal (q, qa); %! q = prctile (days (1:4), pct, 1); %! qa = days ([1, 2, 3, 4; 1, 2, 3, 4]); %! assert_equal (q, qa); %! q = prctile (days (1:4), pct, 2); %! qa = days ([2.5, 3.5]); %! assert_equal (q, qa); %!test %! pct = 50; %! x = [0.1126, 0.1148, 0.0521, 0.2364, 0.1393 %! 0.1718, 0.7273, 0.2041, 0.4531, 0.1585 %! 0.2795, 0.7978, 0.3296, 0.5567, 0.7307 %! 0.4288, 0.8753, 0.6477, 0.6287, 0.8165 %! 0.9331, 0.9312, 0.9635, 0.7796, 0.8461]; %! tol = 0.0001; %! q = days (prctile (days (x), pct, 1)); %! qa = [0.2795, 0.7978, 0.3296, 0.5567, 0.7307]; %! assert_equal (q, qa, tol); %! q = days (prctile (days (x), pct, 2)); %! qa = [0.1148; 0.2041; 0.5567; 0.6477; 0.9312]; %! assert_equal (q, qa, tol); %!test %! pct = 50; %! tol = 0.0001; %! x = [0.1126, 0.1148, 0.0521, 0.2364, 0.1393 %! 0.1718, 0.7273, 0.2041, 0.4531, 0.1585 %! 0.2795, 0.7978, 0.3296, 0.5567, 0.7307 %! 0.4288, 0.8753, 0.6477, 0.6287, 0.8165 %! 0.9331, 0.9312, 0.9635, 0.7796, 0.8461]; %! x(5,5) = Inf; %! q = days (prctile (days (x), pct, 1)); %! qa = [0.2795, 0.7978, 0.3296, 0.5567, 0.7307]; %! assert_equal (q, qa, tol); %! x(5,5) = -Inf; %! q = days (prctile (days (x), pct, 1)); %! qa = [0.2795, 0.7978, 0.3296, 0.5567, 0.1585]; %! assert_equal (q, qa, tol); %! x(1,1) = Inf; %! q = days (prctile (days (x), pct, 1)); %! qa = [0.4288, 0.7978, 0.3296, 0.5567, 0.1585]; %! assert_equal (q, qa, tol); %!test %! pct = 50; %! tol = 0.0001; %! x = [0.1126, 0.1148, 0.0521, 0.2364, 0.1393 %! 0.1718, 0.7273, 0.2041, 0.4531, 0.1585 %! 0.2795, 0.7978, 0.3296, 0.5567, 0.7307 %! 0.4288, 0.8753, 0.6477, 0.6287, 0.8165 %! 0.9331, 0.9312, 0.9635, 0.7796, 0.8461]; %! x(3,3) = Inf; %! q = days (prctile (days (x), pct, 1)); %! qa = [0.2795, 0.7978, 0.6477, 0.5567, 0.7307]; %! assert_equal (q, qa, tol); %! q = days (prctile (days (x), pct, 2)); %! qa = [0.1148; 0.2041; 0.7307; 0.6477; 0.9312]; %! assert_equal (q, qa, tol); %!test %! pct = 50; %! tol = 0.0001; %! x = [0.1126, 0.1148, 0.0521, 0.2364, 0.1393 %! 0.1718, 0.7273, 0.2041, 0.4531, 0.1585 %! 0.2795, 0.7978, 0.3296, 0.5567, 0.7307 %! 0.4288, 0.8753, 0.6477, 0.6287, 0.8165 %! 0.9331, 0.9312, 0.9635, 0.7796, 0.8461]; %! x(5,5) = NaN; %! q = days (prctile (days (x), pct, 2)); %! qa = [0.1148; 0.2041; 0.5567; 0.6477; 0.9322]; %! assert_equal (q, qa, tol); %! x(1,1) = NaN; %! q = days (prctile (days (x), pct, 2)); %! qa = [0.1270; 0.2041; 0.5567; 0.6477; 0.9322]; %! assert_equal (q, qa, tol); %! x(3,3) = NaN; %! q = days (prctile (days (x), pct, 2)); %! qa = [0.1270; 0.2041; 0.6437; 0.6477; 0.9322]; %! assert_equal (q, qa, tol); %!assert_equal (prctile (days ([1:10]), 1, 3), days ([1:10])) %!test %! p = 0.50; %! q = quantile (days (1:4), p); %! qa = days (2.5); %! assert_equal (q, qa); %! q = quantile (days (1:4), p, 1); %! qa = days ([1, 2, 3, 4]); %! assert_equal (q, qa); %! q = quantile (days (1:4), p, 2); %! qa = days (2.5); %! assert_equal (q, qa); %!test %! p = [0.50 0.75]; %! q = quantile (days (1:4), p); %! qa = days ([2.5 3.5]); %! assert_equal (q, qa); %! q = quantile (days (1:4), p, 1); %! qa = days ([1, 2, 3, 4; 1, 2, 3, 4]); %! assert_equal (q, qa); %! q = quantile (days (1:4), p, 2); %! qa = days ([2.5 3.5]); %! assert_equal (q, qa); %!test %! p = 0.5; %! x = days (sort (rand (11))); %! q = quantile (x, p); %! assert_equal (q, x(6,:)); %! x = x.'; %! q = quantile (x, p, 2); %! assert_equal (q, x(:,6)); %!test %! sx = [2, 3, 4]; %! x = days (rand (sx)); %! dim = 2; %! p = 0.5; %! yobs = quantile (x, p, dim); %! yexp = median (x, dim); %! assert_equal (yobs, yexp); %!assert_equal (range (days (1:10)), days (9)) %!assert_equal (range (days (magic (3))), days ([5, 8, 5])) %!assert_equal (range (days (magic (3)), 2), days ([7; 4; 7])) %!assert_equal (range (days (2)), duration) %!test %! x = days (magic (3)); %! x(2,3) = NaN; %! assert_equal (range (x), days ([5, 8, 4])); %! assert_equal (range (x, "omitnan"), days ([5, 8, 4])); %! assert_equal (isequaln (range (x, "includenan"), days ([5, 8, NaN])), true); %! assert_equal (range (x, 2), days ([7; 2; 7])); %! assert_equal (range (x, 2, "omitnan"), days ([7; 2; 7])); %! assert_equal (isequaln (range (x, 2, "includenan"), days ([7; NaN; 7])), true); %!test %! x = reshape (days (1:27), [3, 3, 3]); %! assert_equal (range (x, 3), x(:,:,3) - x(:,:,1)); %!test %! x = reshape (days (1:27), [3, 3, 3]); %! assert_equal (range (x, [2, 3]), x(:,3,3) - x(:,1,1)); %!test %! x = reshape (days (1:27), [3, 3, 3]); %! assert_equal (range (x, [1, 3]), x(3,:,3) - x(1,:,1)); %!test %! x = reshape (days (1:27), [3, 3, 3]); %! assert_equal (range (x, [1, 2, 3]), days (26)); %!test %! x = reshape (days (1:27), [3, 3, 3]); %! assert_equal (range (x, "all"), days (26)); %!test %! x = reshape (days (1:27), [3, 3, 3]); %! x(3) = NaN; %! assert_equal (range (x, "all"), days (26)); %!test %! x = reshape (days (1:27), [3, 3, 3]); %! x(3) = NaN; %! assert_equal (range (x, "all", "omitnan"), days (26)); %!test %! x = reshape (days (1:27), [3, 3, 3]); %! x(3) = NaN; %! assert_equal (isequaln (range (x, "all", "includenan"), days (NaN)), true); %!test %! x = reshape (days (1:27), [3, 3, 3]); %! x(3) = NaN; %! s = min (x, [], "includenan"); %! l = max (x, [], "includenan"); %! assert_equal (isequaln (range (x, "includenan"), l - s), true); %!test %! x = reshape (days (1:27), [3, 3, 3]); %! x(3) = NaN; %! y = range (x, 3, "includenan"); %! assert_equal (isequaln (y(3), days (NaN)), true); %!test %! F = days ([2, 11, 6]); %! A = days ([3, 10, 8]); %! assert_equal (days (rmse (F, A)), sqrt (2), eps); %! assert_equal (days (rmse (F, A, 1)), [1, 1, 2], eps); %! assert_equal (days (rmse (F, A, 2)), sqrt (2), eps); %! assert_equal (days (rmse (F, A, 3)), [1, 1, 2], eps); %! assert_equal (days (rmse (F, A, 'all')), sqrt (2), eps); %!test %! F = days ([3; 7; 4; NaN]); %! A = days ([4; 0; 6; 4]); %! assert_equal (days (rmse (F, A)), NaN); %! assert_equal (days (rmse (F, A, 'omitnan')), 4.2426, 1e-4); %! assert_equal (days (rmse (F, A, 1, 'omitnan')), 4.2426, 1e-4); %! assert_equal (days (rmse (F, A, 2, 'omitnan')), [1; 7; 2; NaN]); %!test %! F = days ([17, 21; 1, 5; 13, 17]); %! A = days ([18, 19; 5, 4; 15, 13]); %! assert_equal (days (rmse (F, A)), [2.6458, 2.6458], 1e-4); %! assert_equal (days (rmse (F, A, 1)), [2.6458, 2.6458], 1e-4); %! assert_equal (days (rmse (F, A, 2)), [1.5811; 2.9155; 3.1623], 1e-4); %! assert_equal (days (rmse (F, A, 'all')), 2.6458, 1e-4); %! assert_equal (days (rmse (F, A, [1, 2])), 2.6458, 1e-4); %!test %! F = days ([3, 5; -1, 2]); %! F(:,:,2) = days ([4, 2; 7, -4]); %! A = days ([6, 5; NaN, 3]); %! E = days (rmse (F, A, [1, 2])); %! assert_equal (E(:,:,1), NaN); %! assert_equal (E(:,:,2), NaN); %! E = days (rmse (F, A, [1, 2], 'omitnan')); %! assert_equal (E(:,:,1), 1.8257, 1e-4); %! assert_equal (E(:,:,2), 4.5461, 1e-4); %! assert_equal (days (rmse (F, A, [1, 3])), [NaN, 3.8406], 1e-4); %! assert_equal (days (rmse (F, A, [1, 3], 'omitnan')), [2.5495, 3.8406], 1e-4); %! assert_equal (days (rmse (F, A, [2, 3])), [2.3452; NaN], 1e-4); %! assert_equal (days (rmse (F, A, [2, 3], 'omitnan')), [2.3452; 5], 1e-4); %!test %! F = days ([2, 11, 6]); %! A = days ([3, 10, 8]); %! W = [1, 2, 1]; %! assert_equal (days (rmse (F, A, 'Weights', W)), 1.3229, 1e-4); %! assert_equal (days (rmse (F, A, 1, 'Weights', W)), [1, 1, 2]); %! assert_equal (days (rmse (F, A, 2, 'Weights', W)), 1.3229, 1e-4); %! assert_equal (days (rmse (F, A, 3, 'Weights', W)), [1, 1, 2]); %!test %! F = days ([3; 7; 4; NaN]); %! A = days ([4; 0; 6; 4]); %! W = [1, 2, 1, 2]; %! assert_equal (days (rmse (F, A, 'Weights', W)), NaN); %! assert_equal (days (rmse (F, A, 'omitnan', 'Weights', W)), 5.0744, 1e-4); %! assert_equal (days (rmse (F, A, 1, 'omitnan', 'Weights', W)), 5.0744, 1e-4); %!test %! F = days ([3; 7; 4; NaN]); %! A = days ([4; 0; 6; 4]); %! W = [1; 2; 1; 2]; %! assert_equal (days (rmse (F, A, 2, 'Weights', W)), [1; 7; 2; NaN], 1e-4); %! assert_equal (days (rmse (F, A, 2, 'omitnan', 'Weights', W)), [1; 7; 2; NaN], 1e-4); %! assert_equal (days (rmse (F, A, 3, 'Weights', W)), [1; 7; 2; NaN], 1e-4); %! assert_equal (days (rmse (F, A, 3, 'omitnan', 'Weights', W)), [1; 7; 2; NaN], 1e-4); %!test %! F = days ([3, 4; 7, 5; 4, 3; NaN, 8]); %! A = days ([4; 0; 6; 4]); %! W = [3; 1; 2; 2]; %! E = days (rmse (F, A, 1, 'omitnan', 'Weights', W)); %! assert_equal (E, [3.1623, 3.0619], 1e-4); %!test %! F = days ([3, 4; 7, 5; 4, 3; NaN, 8]); %! A = days ([4; 0; 6; 4]); %! W = [3, 0.4; 1, 0.2; 2, 0.2; 2, 0.2]; %! assert_equal (days (rmse (F, A, 1, 'Weights', W)), [NaN, 3.1623], 1e-4); %! assert_equal (days (rmse (F, A, 1, 'omitnan', 'Weights', W)), [3.1623, 3.1623], 1e-4); %! assert_equal (days (rmse (F, A, 2, 'Weights', W)), [0.9393; 6.7082; 2.1106; NaN], 1e-4); %! E = rmse (F, A, 2, 'omitnan', 'Weights', W); %! assert_equal (days (E), [0.9393; 6.7082; 2.1106; 4], 1e-4); %!test %! F = days ([3, 5; -1, 2]); %! F(:,:,2) = days ([4, NaN; 7, -4]); %! A = days ([6, 5; 2, 3]); %! W = [1, 1; 1, 1]; %! Ew = rmse (F, A, [1, 2], 'omitnan', 'Weights', W); %! E = rmse (F, A, [1, 2], 'omitnan'); %! assert_equal (Ew, E); %! Ew = rmse (F, A, [1, 3], 'omitnan','Weights', W); %! E = rmse (F, A, [1, 3], 'omitnan'); %! assert_equal (Ew, E); %! Ew = rmse (F, A, [2, 3], 'omitnan','Weights', W); %! E = rmse (F, A, [2, 3], 'omitnan'); %! assert_equal (Ew, E); %!test %! F = days ([3, 5; -1, 2]); %! F(:,:,2) = days ([4, 2; 7, -4]); %! A = days ([6, 5; 0, 3]); %! W = [1, 2; 1, 1]; %! F1 = reshape (F, 1, 4, 2); %! A1 = reshape (A, 1, 4); %! W1 = reshape (W, 1, 4); %! E1w = days (rmse (F1, A1, 2, 'Weights', W1)); %! assert_equal (E1w(:,:,1), 1.4832, 1e-4); %! assert_equal (E1w(:,:,2), 4.8990, 1e-4); %! E1 = days (rmse (F1, A1, 2)); %! assert_equal (E1(:,:,1), 1.6583, 1e-4); %! assert_equal (E1(:,:,2), 5.2678, 1e-4); %! assert_equal (rmse (F, A, [1, 2], 'Weights', W), days (E1w)); %! assert_equal (rmse (F, A, [1, 2]), days (E1)); %!test %! F = days ([3, 5; -1, 2]); %! F(:,:,2) = days ([4, 2; 7, -4]); %! A = days ([6, 5; 0, 3]); %! W = [0.5, 2; 1, 3]; %! F1 = reshape (F, 2, 4, 1); %! A1 = reshape (repmat (A, 1, 1, 2), 2, 4, 1); %! W1 = [0.5, 1, 2, 3; 0.5, 1, 2, 3]; %! E1w = rmse (F1, A1, 2, 'Weights', W1); %! assert_equal (days (E1w), [2.4651; 6.1582], 1e-4); %! E1 = rmse (F1, A1, 2); %! assert_equal (days (E1), [2.3452; 5], 1e-4); %! assert_equal (rmse (F, A, [2, 3], 'Weights', W), E1w); %! assert_equal (rmse (F, A, [2, 3]), E1); %!error ... %! rmse (days ([1, 2, 3]), 1:3) %!error ... %! rmse (1:3, days ([1, 2, 3])) %!assert_equal (skewness (days ([-1, 0, 1])), 0) %!assert_equal (skewness (days ([-2, 0, 1])) < 0, true) %!assert_equal (skewness (days ([-1, 0, 2])) > 0, true) %!assert_equal (skewness (days ([-3, 0, 1])) == -1 * skewness ([-1, 0, 3]), true) %!assert_equal (skewness (days (ones (3, 5))), NaN (1, 5)) %!assert_equal (skewness (days (1), [], 3), NaN) %!test %! x = [0; 0; 0; 1]; %! y = days ([x, 2*x]); %! assert_equal (skewness (y), 1.154700538379251 * [1 1], 5*eps); %!assert_equal (skewness (days ([1:5, 10; 1:5, 10]), 0, 2), 1.439590274527954 * [1; 1], eps) %!assert_equal (skewness (days ([1:5, 10; 1:5, 10]), 1, 2), 1.051328089232020 * [1; 1], 2*eps) %!assert_equal (skewness (days ([1:5, 10; 1:5, 10]), [], 2), 1.051328089232020 * [1; 1], 2*eps) %!test %! x(:,:,1) = [0.5377, 0.3188, 3.5784; 1.8339, -1.3077, 2.7694; ... %! -2.2588, -0.4336, -1.3499; 0.8622, 0.3426, 3.0349]; %! x(:,:,2) = [0.7254, -0.1241, 0.6715; -0.0631, 1.4897, -1.2075; ... %! 0.7147 1.4090 0.7172; -0.2050, 1.4172, 1.6302]; %! y = skewness (days (x)); %! assert_equal (y(:,:,1), [-0.8084, -0.5578, -1.0772], 1e-4); %! assert_equal (y(:,:,2), [-0.0403, -1.1472, -0.6632], 1e-4); %! y = skewness (days (x), 1, 2); %! assert_equal (y(:,:,1), [0.6956; -0.5575; 0.0049; 0.6033], 1e-4); %! assert_equal (y(:,:,2), [-0.6969; 0.1828; 0.7071; -0.6714], 1e-4); %! y = skewness (days (x), 1, 3); %! assert_equal (y, zeros (4, 3), 8 * eps); %!test %! x(:,:,1) = [0.5377, 0.3188, 3.5784; 1.8339, -1.3077, 2.7694; ... %! -2.2588, -0.4336, -1.3499; 0.8622, 0.3426, 3.0349]; %! x(:,:,2) = [0.7254, -0.1241, 0.6715; -0.0631, 1.4897, -1.2075; ... %! 0.7147 1.4090 0.7172; -0.2050, 1.4172, 1.6302]; %! y = skewness (days (x), 1, "all"); %! assert_equal (y, 0.0916, 1e-4); %! y = skewness (days (x), 1, [1, 2]); %! assert_equal (y(:,:,1), 0.1070, 1e-4); %! assert_equal (y(:,:,2), -0.6263, 1e-4); %! y = skewness (days (x), 1, [1, 3]); %! assert_equal (y, [-1.0755, -0.3108, -0.2209], 1e-4); %!test %! x = days (ones (10, 2)); %! y = days ([1, 3]); %! assert_equal (std (x), days ([0, 0])); %! assert_equal (days (std (y)), sqrt (2), sqrt (eps)); %! assert_equal (std (x, 0, 2), days (zeros (10, 1))); %!assert_equal (std (days (ones (3, 1, 2)), 0, 2), days (zeros (3, 1, 2))) %!assert_equal (days (std (days ([1, 2]), 0)), sqrt (2)/2, 5*eps) %!assert_equal (days (std (days ([1, 2]), 1)), 0.5, 5*eps) %!assert_equal (std (days (1)), duration) %!assert_equal (std (days ([1, 2, 3]), [], 3), days ([0, 0, 0])) %!test %! x = days ([-10:10]); %! y = [x;x+5;x-5]; %! assert_equal (std (x), days (sqrt (38.5))); %! assert_equal (std (y, [], 2), days (sqrt ([38.5; 38.5; 38.5]))); %! assert_equal (std (y, 0, 2), days (sqrt ([38.5; 38.5; 38.5]))); %! assert_equal (days (std (y, 1, 2)), sqrt (ones (3,1) * 36.66666666666666), 1e-14); %! assert_equal (days (std (y, "all")), sqrt (54.19354838709678), 1e-14); %! y(2,4) = NaN; %! assert_equal (isequaln (std (y, "all"), days (NaN)), true); %! assert_equal (isequaln (std (y, "all", "includenan"), days (NaN)), true); %! assert_equal (days (std (y, "all", "omitnan")), sqrt (55.01533580116342), 1e-14); %! assert_equal (isequaln (std (y, 0, 2, "includenan"), days (sqrt ([38.5; NaN; 38.5]))), true); %! assert_equal (isequaln (std (y, [], 2), days (sqrt ([38.5; NaN; 38.5]))), true); %! assert_equal (days (std (y, [], 2, "omitnan")), sqrt ([38.5; 37.81842105263158; 38.5]), 1e-14); %!assert_equal (days (std (days ([1, NaN, 3]), [1 2 3], "omitnan")), sqrt (0.75), eps) %!assert_equal (days (std (days ([1, 2, 3]), [1 NaN 3], "omitnan")), sqrt (0.75), eps) %!assert_equal (days (std (days (magic (3)), [1 NaN 3], "omitnan")), sqrt ([3, 12, 3]), eps) %!assert_equal (days (std (days ([1, NaN, 3]), [1 2 3], "omitnan", "all")), sqrt (0.75), eps) %!assert_equal (days (std (days ([1, NaN, 3]), [1 2 3], "all", "omitnan")), sqrt (0.75), eps) %!assert_equal (days (std (days ([1, 2, 3]), [1 NaN 3], "omitnan", "all")), sqrt (0.75), eps) %!assert_equal (days (std (days ([1, NaN, 3]), [1 2 3], 2, "omitnan")), sqrt (0.75), eps) %!assert_equal (days (std (days ([1, 2, 3]), [1 NaN 3], 2, "omitnan")), sqrt (0.75), eps) %!assert_equal (days (std (days (magic (3)), [1 NaN 3], 1, "omitnan")), sqrt ([3, 12, 3]), eps) %!assert_equal (days (std (days (magic (3)), [1 NaN 3], 2, "omitnan")), sqrt ([0.75;3;0.75]), eps) %!assert_equal (days (std (days ([4, 4; 4, 6; 6, 6]), [1 3], 2, 'omitnan')), sqrt ([0;0.75;0]), eps) %!assert_equal (days (std (days ([4, NaN; 4, 6; 6, 6]), [1, 2, 3], 1, 'omitnan')), sqrt ([1 0])) %!assert_equal (days (std (days ([4, NaN; 4, 6; 6, 6]), [1, 3], 2, 'omitnan')), sqrt ([0;0.75;0]), eps) %!assert_equal (days (std (days (3*reshape(1:18, [3, 3, 2])), [1, 2, 3], 1, 'omitnan')), ... %! sqrt (ones (1, 3, 2) * 5)) %!assert_equal (days (std (reshape (days (1:18), [3, 3, 2]), [1, 2, 3], 2, 'omitnan')), ... %! sqrt (5 * ones(3, 1, 2))) %!assert_equal (days (std (3 * reshape (days (1:18), [3, 3, 2]), ones (3,3,2), [1 2], 'omitnan')), ... %! sqrt (60 * ones (1, 1, 2))) %!assert_equal (days (std (3*reshape(days (1:18), [3, 3, 2]), ones (3,3,2), [1 4], 'omitnan')), ... %! sqrt (6 * ones (1, 3, 2))) %!assert_equal (days (std (6 * reshape (days (1:18), [3, 3, 2]), ones (3,3,2), [1:3], 'omitnan')), sqrt (969)) %!test %! x = reshape (days (1:18), [3, 3, 2]); %! x([2, 14]) = NaN; %! w = ones (3,3,2); %! assert_equal (std (16 * x, w, [1:3], 'omitnan'), days (sqrt (6519))); ################################################################################ ## ** Sort, Filter, and Set Operations ** ## ################################################################################ ## Available Methods ## ## ## ## 'sort' 'sortrows' 'unique' 'interp1' ## ## 'intersect' 'setdiff' 'setxor' 'union' ## ## ## ################################################################################ %!test %! D = days ([1, 2; 1, 3; 2, -4; 2, 1]); %! [B, I] = sort (D); %! assert_equal (days (B), [1, -4; 1, 1; 2, 2; 2, 3]); %! assert_equal (I, [1, 3; 2, 4; 3, 1; 4, 2]); %!test %! D = days ([1, 2; 1, 3; 2, -4; 2, 1]); %! [B, I] = sort (D, 1); %! assert_equal (days (B), [1, -4; 1, 1; 2, 2; 2, 3]); %! assert_equal (I, [1, 3; 2, 4; 3, 1; 4, 2]); %!test %! D = days ([1, 2; 1, 3; 2, -4; 2, 1]); %! [B, I] = sort (D, 2); %! assert_equal (days (B), [1, 2; 1, 3; -4, 2; 1, 2]); %! assert_equal (I, [1, 2; 1, 2; 2, 1; 2, 1]); %!test %! D = days ([1, 2; 1, 3; 2, -4; 2, 1]); %! [B, I] = sort (D, 1, 'ComparisonMethod', 'abs'); %! assert_equal (days (B), [1, 1; 1, 2; 2, 3; 2, -4]); %! assert_equal (I, [1, 4; 2, 1; 3, 2; 4, 3]); %!test %! D = days ([1, 2; 1, 3; 2, -4; 2, 1]); %! [B, I] = sort (D, 2, 'ComparisonMethod', 'abs'); %! assert_equal (days (B), [1, 2; 1, 3; 2, -4; 1, 2]); %! assert_equal (I, [1, 2; 1, 2; 1, 2; 2, 1]); %!test %! D = days ([1, 2; 1, 3; 2, -4; 2, 1]); %! [B, I] = sort (D, 'descend'); %! assert_equal (days (B), [2, 3; 2, 2; 1, 1; 1, -4]); %! assert_equal (I, [3, 2; 4, 1; 1, 4; 2, 3]); %!test %! D = days ([1, 2; 1, 3; 2, -4; 2, 1]); %! [B, I] = sort (D, 1, 'descend'); %! assert_equal (days (B), [2, 3; 2, 2; 1, 1; 1, -4]); %! assert_equal (I, [3, 2; 4, 1; 1, 4; 2, 3]); %!test %! D = days ([1, 2; 1, 3; 2, -4; 2, 1]); %! [B, I] = sort (D, 2, 'descend'); %! assert_equal (days (B), [2, 1; 3, 1; 2, -4; 2, 1]); %! assert_equal (I, [2, 1; 2, 1; 1, 2; 1, 2]); %!test %! D = days ([1, 2; 1, 3; 2, -4; 2, 1]); %! [B, I] = sort (D, 1, 'descend', 'ComparisonMethod', 'abs'); %! assert_equal (days (B), [2, -4; 2, 3; 1, 2; 1, 1]); %! assert_equal (I, [3, 3; 4, 2; 1, 1; 2, 4]); %!test %! D = days ([1, 2; 1, 3; 2, -4; 2, 1]); %! [B, I] = sort (D, 2, 'descend', 'ComparisonMethod', 'abs'); %! assert_equal (days (B), [2, 1; 3, 1; -4, 2; 2, 1]); %! assert_equal (I, [2, 1; 2, 1; 2, 1; 1, 2]); %!test %! D = days ([1, 2, 2; 1, 3, 1; 2, -4, 2; 2, 1, NaN; -2, NaN, 3; 1, 3, 2]); %! [B, I] = sort (D); %! X = [-2, -4, 1; 1, 1, 2; 1, 2, 2; 1, 3, 2; 2, 3, 3; 2, NaN, NaN]; %! assert_equal (days (B), X); %! assert_equal (I, [5, 3, 2; 1, 4, 1; 2, 1, 3; 6, 2, 6; 3, 6, 5; 4, 5, 4]); %!test %! D = days ([1, 2, 2; 1, 3, 1; 2, -4, 2; 2, 1, NaN; -2, NaN, 3; 1, 3, 2]); %! [B, I] = sort (D, 1); %! X = [-2, -4, 1; 1, 1, 2; 1, 2, 2; 1, 3, 2; 2, 3, 3; 2, NaN, NaN]; %! assert_equal (days (B), X); %! assert_equal (I, [5, 3, 2; 1, 4, 1; 2, 1, 3; 6, 2, 6; 3, 6, 5; 4, 5, 4]); %!test %! D = days ([1, 2, 2; 1, 3, 1; 2, -4, 2; 2, 1, NaN; -2, NaN, 3; 1, 3, 2]); %! [B, I] = sort (D, 2); %! X = [1, 2, 2; 1, 1, 3; -4, 2, 2; 1, 2, NaN; -2, 3, NaN; 1, 2, 3]; %! assert_equal (days (B), X); %! assert_equal (I, [1, 2, 3; 1, 3, 2; 2, 1, 3; 2, 1, 3; 1, 3, 2; 1, 3, 2]); %!test %! D = days ([1, 2, 2; 1, 3, 1; 2, -4, 2; 2, 1, NaN; -2, NaN, 3; 1, 3, 2]); %! [B, I] = sort (D, 1, 'ComparisonMethod', 'abs'); %! X = [1, 1, 1; 1, 2, 2; 1, 3, 2; 2, 3, 2; 2, -4, 3; -2, NaN, NaN]; %! assert_equal (days (B), X); %! assert_equal (I, [1, 4, 2; 2, 1, 1; 6, 2, 3; 3, 6, 6; 4, 3, 5; 5, 5, 4]); %!test %! D = days ([1, 2, 2; 1, 3, 1; 2, -4, 2; 2, 1, NaN; -2, NaN, 3; 1, 3, 2]); %! [B, I] = sort (D, 2, 'ComparisonMethod', 'abs'); %! X = [1, 2, 2; 1, 1, 3; 2, 2, -4; 1, 2, NaN; -2, 3, NaN; 1, 2, 3]; %! assert_equal (days (B), X); %! assert_equal (I, [1, 2, 3; 1, 3, 2; 1, 3, 2; 2, 1, 3; 1, 3, 2; 1, 3, 2]); %!test %! D = days ([1, 2, 2; 1, 3, 1; 2, -4, 2; 2, 1, NaN; -2, NaN, 3; 1, 3, 2]); %! [B, I] = sort (D, 1, 'MissingPlacement', 'first'); %! X = [-2, NaN, NaN; 1, -4, 1; 1, 1, 2; 1, 2, 2; 2, 3, 2; 2, 3, 3]; %! assert_equal (days (B), X); %! assert_equal (I, [5, 5, 4; 1, 3, 2; 2, 4, 1; 6, 1, 3; 3, 2, 6; 4, 6, 5]); %!test %! D = days ([1, 2, 2; 1, 3, 1; 2, -4, 2; 2, 1, NaN; -2, NaN, 3; 1, 3, 2]); %! [B, I] = sort (D, 2, 'MissingPlacement', 'first'); %! X = [1, 2, 2; 1, 1, 3; -4, 2, 2; NaN, 1, 2; NaN, -2, 3; 1, 2, 3]; %! assert_equal (days (B), X); %! assert_equal (I, [1, 2, 3; 1, 3, 2; 2, 1, 3; 3, 2, 1; 2, 1, 3; 1, 3, 2]); %!test %! D = days ([1, 2, 2; 1, 3, 1; 2, -4, 2; 2, 1, NaN; -2, NaN, 3; 1, 3, 2]); %! [B, I] = sort (D, 1, 'MissingPlacement', 'last'); %! X = [-2, -4, 1; 1, 1, 2; 1, 2, 2; 1, 3, 2; 2, 3, 3; 2, NaN, NaN]; %! assert_equal (days (B), X); %! assert_equal (I, [5, 3, 2; 1, 4, 1; 2, 1, 3; 6, 2, 6; 3, 6, 5; 4, 5, 4]); %!test %! D = days ([1, 2, 2; 1, 3, 1; 2, -4, 2; 2, 1, NaN; -2, NaN, 3; 1, 3, 2]); %! [B, I] = sort (D, 2, 'MissingPlacement', 'last'); %! X = [1, 2, 2; 1, 1, 3; -4, 2, 2; 1, 2, NaN; -2, 3, NaN; 1, 2, 3]; %! assert_equal (days (B), X); %! assert_equal (I, [1, 2, 3; 1, 3, 2; 2, 1, 3; 2, 1, 3; 1, 3, 2; 1, 3, 2]); %!test %! D = days ([1, 2, 2; 1, 3, 1; 2, -4, 2; 2, 1, NaN; -2, NaN, 3; 1, 3, 2]); %! [B, I] = sort (D, 'descend'); %! X = [2, NaN, NaN; 2, 3, 3; 1, 3, 2; 1, 2, 2; 1, 1, 2; -2, -4, 1]; %! assert_equal (days (B), X); %! assert_equal (I, [3, 5, 4; 4, 2, 5; 1, 6, 1; 2, 1, 3; 6, 4, 6; 5, 3, 2]); %!test %! D = days ([1, 2, 2; 1, 3, 1; 2, -4, 2; 2, 1, NaN; -2, NaN, 3; 1, 3, 2]); %! [B, I] = sort (D, 1, 'descend'); %! X = [2, NaN, NaN; 2, 3, 3; 1, 3, 2; 1, 2, 2; 1, 1, 2; -2, -4, 1]; %! assert_equal (days (B), X); %! assert_equal (I, [3, 5, 4; 4, 2, 5; 1, 6, 1; 2, 1, 3; 6, 4, 6; 5, 3, 2]); %!test %! D = days ([1, 2, 2; 1, 3, 1; 2, -4, 2; 2, 1, NaN; -2, NaN, 3; 1, 3, 2]); %! [B, I] = sort (D, 2, 'descend'); %! X = [2, 2, 1; 3, 1, 1; 2, 2, -4; NaN, 2, 1; NaN, 3, -2; 3, 2, 1]; %! assert_equal (days (B), X); %! assert_equal (I, [2, 3, 1; 2, 1, 3; 1, 3, 2; 3, 1, 2; 2, 3, 1; 2, 3, 1]); %!test %! D = days ([1, 2, 2; 1, 3, 1; 2, -4, 2; 2, 1, NaN; -2, NaN, 3; 1, 3, 2]); %! [B, I] = sort (D, 1, 'descend', 'ComparisonMethod', 'abs'); %! X = [2, NaN, NaN; 2, -4, 3; -2, 3, 2; 1, 3, 2; 1, 2, 2; 1, 1, 1]; %! assert_equal (days (B), X); %! assert_equal (I, [3, 5, 4; 4, 3, 5; 5, 2, 1; 1, 6, 3; 2, 1, 6; 6, 4, 2]); %!test %! D = days ([1, 2, 2; 1, 3, 1; 2, -4, 2; 2, 1, NaN; -2, NaN, 3; 1, 3, 2]); %! [B, I] = sort (D, 2, 'descend', 'ComparisonMethod', 'abs'); %! X = [2, 2, 1; 3, 1, 1; -4, 2, 2; NaN, 2, 1; NaN, 3, -2; 3, 2, 1]; %! assert_equal (days (B), X); %! assert_equal (I, [2, 3, 1; 2, 1, 3; 2, 1, 3; 3, 1, 2; 2, 3, 1; 2, 3, 1]); %!test %! D = days ([1, 2, 2; 1, 3, 1; 2, -4, 2; 2, 1, NaN; -2, NaN, 3; 1, 3, 2]); %! [B, I] = sort (D, 1, 'descend', 'MissingPlacement', 'first'); %! X = [2, NaN, NaN; 2, 3, 3; 1, 3, 2; 1, 2, 2; 1, 1, 2; -2, -4, 1]; %! assert_equal (days (B), X); %! assert_equal (I, [3, 5, 4; 4, 2, 5; 1, 6, 1; 2, 1, 3; 6, 4, 6; 5, 3, 2]); %!test %! D = days ([1, 2, 2; 1, 3, 1; 2, -4, 2; 2, 1, NaN; -2, NaN, 3; 1, 3, 2]); %! [B, I] = sort (D, 2, 'descend', 'MissingPlacement', 'first'); %! X = [2, 2, 1; 3, 1, 1; 2, 2, -4; NaN, 2, 1; NaN, 3, -2; 3, 2, 1]; %! assert_equal (days (B), X); %! assert_equal (I, [2, 3, 1; 2, 1, 3; 1, 3, 2; 3, 1, 2; 2, 3, 1; 2, 3, 1]); %!test %! D = days ([1, 2, 2; 1, 3, 1; 2, -4, 2; 2, 1, NaN; -2, NaN, 3; 1, 3, 2]); %! [B, I] = sort (D, 1, 'descend', 'MissingPlacement', 'last'); %! X = [2, 3, 3; 2, 3, 2; 1, 2, 2; 1, 1, 2; 1, -4, 1; -2, NaN, NaN]; %! assert_equal (days (B), X); %! assert_equal (I, [3, 2, 5; 4, 6, 1; 1, 1, 3; 2, 4, 6; 6, 3, 2; 5, 5, 4]); %!test %! D = days ([1, 2, 2; 1, 3, 1; 2, -4, 2; 2, 1, NaN; -2, NaN, 3; 1, 3, 2]); %! [B, I] = sort (D, 2, 'descend', 'MissingPlacement', 'last'); %! X = [2, 2, 1; 3, 1, 1; 2, 2, -4; 2, 1, NaN; 3, -2, NaN; 3, 2, 1]; %! assert_equal (days (B), X); %! assert_equal (I, [2, 3, 1; 2, 1, 3; 1, 3, 2; 1, 2, 3; 3, 1, 2; 2, 3, 1]); %!test %! D = days ([1, 2, 2; 1, 3, 1; 2, -4, 2; 2, 1, NaN; -2, NaN, 3; 1, 3, 2]); %! [B, I] = sort (D, 1, 'MissingPlacement', 'first', 'ComparisonMethod', 'abs'); %! X = [1, NaN, NaN; 1, 1, 1; 1, 2, 2; 2, 3, 2; 2, 3, 2; -2, -4, 3]; %! assert_equal (days (B), X); %! assert_equal (I, [1, 5, 4; 2, 4, 2; 6, 1, 1; 3, 2, 3; 4, 6, 6; 5, 3, 5]); %!test %! D = days ([1, 2, 2; 1, 3, 1; 2, -4, 2; 2, 1, NaN; -2, NaN, 3; 1, 3, 2]); %! [B, I] = sort (D, 2, 'MissingPlacement', 'first', 'ComparisonMethod', 'abs'); %! X = [1, 2, 2; 1, 1, 3; 2, 2, -4; NaN, 1, 2; NaN, -2, 3; 1, 2, 3]; %! assert_equal (days (B), X); %! assert_equal (I, [1, 2, 3; 1, 3, 2; 1, 3, 2; 3, 2, 1; 2, 1, 3; 1, 3, 2]); %!test %! D = days ([1, 2, 2; 1, 3, 1; 2, -4, 2; 2, 1, NaN; -2, NaN, 3; 1, 3, 2]); %! [B, I] = sort (D, 1, 'MissingPlacement', 'last', 'ComparisonMethod', 'abs'); %! X = [1, 1, 1; 1, 2, 2; 1, 3, 2; 2, 3, 2; 2, -4, 3; -2, NaN, NaN]; %! assert_equal (days (B), X); %! assert_equal (I, [1, 4, 2; 2, 1, 1; 6, 2, 3; 3, 6, 6; 4, 3, 5; 5, 5, 4]); %!test %! D = days ([1, 2, 2; 1, 3, 1; 2, -4, 2; 2, 1, NaN; -2, NaN, 3; 1, 3, 2]); %! [B, I] = sort (D, 2, 'MissingPlacement', 'last', 'ComparisonMethod', 'abs'); %! X = [1, 2, 2; 1, 1, 3; 2, 2, -4; 1, 2, NaN; -2, 3, NaN; 1, 2, 3]; %! assert_equal (days (B), X); %! assert_equal (I, [1, 2, 3; 1, 3, 2; 1, 3, 2; 2, 1, 3; 1, 3, 2; 1, 3, 2]); %!test %! D = days ([1, 2, 2; 1, 3, 1; 2, -4, 2; 2, 1, NaN; -2, NaN, 3; 1, 3, 2]); %! [B, I] = sort (D, 1, 'descend', 'MissingPlacement', 'first', 'ComparisonMethod', 'abs'); %! X = [2, NaN, NaN; 2, -4, 3; -2, 3, 2; 1, 3, 2; 1, 2, 2; 1, 1, 1]; %! assert_equal (days (B), X); %! assert_equal (I, [3, 5, 4; 4, 3, 5; 5, 2, 1; 1, 6, 3; 2, 1, 6; 6, 4, 2]); %!test %! D = days ([1, 2, 2; 1, 3, 1; 2, -4, 2; 2, 1, NaN; -2, NaN, 3; 1, 3, 2]); %! [B, I] = sort (D, 2, 'descend', 'MissingPlacement', 'first', 'ComparisonMethod', 'abs'); %! X = [2, 2, 1; 3, 1, 1; -4, 2, 2; NaN, 2, 1; NaN, 3, -2; 3, 2, 1]; %! assert_equal (days (B), X); %! assert_equal (I, [2, 3, 1; 2, 1, 3; 2, 1, 3; 3, 1, 2; 2, 3, 1; 2, 3, 1]); %!test %! D = days ([1, 2, 2; 1, 3, 1; 2, -4, 2; 2, 1, NaN; -2, NaN, 3; 1, 3, 2]); %! [B, I] = sort (D, 1, 'descend', 'MissingPlacement', 'last', 'ComparisonMethod', 'abs'); %! X = [2, -4, 3; 2, 3, 2; -2, 3, 2; 1, 2, 2; 1, 1, 1; 1, NaN, NaN]; %! assert_equal (days (B), X); %! assert_equal (I, [3, 3, 5; 4, 2, 1; 5, 6, 3; 1, 1, 6; 2, 4, 2; 6, 5, 4]); %!test %! D = days ([1, 2, 2; 1, 3, 1; 2, -4, 2; 2, 1, NaN; -2, NaN, 3; 1, 3, 2]); %! [B, I] = sort (D, 2, 'descend', 'MissingPlacement', 'last', 'ComparisonMethod', 'abs'); %! X = [2, 2, 1; 3, 1, 1; -4, 2, 2; 2, 1, NaN; 3, -2, NaN; 3, 2, 1]; %! assert_equal (days (B), X); %! assert_equal (I, [2, 3, 1; 2, 1, 3; 2, 1, 3; 1, 2, 3; 3, 1, 2; 2, 3, 1]); %!test %! D = days ([1, 2; 1, 3; 2, -4; 2, 1]); %! D = sortrows (D); %! assert_equal (days (D), [1, 2; 1, 3; 2, -4; 2, 1]); %!test %! D = days ([1, 2; 1, 3; 2, -4; 2, 1]); %! D = sortrows (D, 'ComparisonMethod', 'abs'); %! assert_equal (days (D), [1, 2; 1, 3; 2, 1; 2, -4]); %!test %! D = days ([1, 2; 1, 3; 2, -4; 2, 1]); %! D = sortrows (D, 1, 'ComparisonMethod', 'abs'); %! assert_equal (days (D), [1, 2; 1, 3; 2, -4; 2, 1]); %!test %! D = days ([1, 2; 1, 3; 2, -4; 2, 1]); %! D = sortrows (D, 2, 'ComparisonMethod', 'abs'); %! assert_equal (days (D), [2, 1; 1, 2; 1, 3; 2, -4]); %!test %! D = days ([1, 2; 1, 3; 2, -4; 2, 1]); %! D = sortrows (D, -1, 'ComparisonMethod', 'abs'); %! assert_equal (days (D), [2, -4; 2, 1; 1, 2; 1, 3]); %!test %! D = days ([1, 2; 1, 3; 2, -4; 2, 1]); %! D = sortrows (D, -2, 'ComparisonMethod', 'abs'); %! assert_equal (days (D), [2, -4; 1, 3; 1, 2; 2, 1]); %!test %! D = days ([1, 2, 2; 1, 3, 1; 2, -4, 2; 2, 1, NaN; -2, NaN, 3; 1, 3, 2]); %! D = sortrows (D); %! X = [-2, NaN, 3; 1, 2, 2; 1, 3, 1; 1, 3, 2; 2, -4, 2; 2, 1, NaN]; %! assert_equal (days (D), X); %!test %! D = days ([1, 2, 2; 1, 3, 1; 2, -4, 2; 2, 1, NaN; -2, NaN, 3; 1, 3, 2]); %! D = sortrows (D, -2, 'MissingPlacement', 'first'); %! X = [-2, NaN, 3; 1, 3, 1; 1, 3, 2; 1, 2, 2; 2, 1, NaN; 2, -4, 2]; %! assert_equal (days (D), X); %!test %! D = days ([1, 2, 2; 1, 3, 1; 2, -4, 2; 2, 1, NaN; -2, NaN, 3; 1, 3, 2]); %! D = sortrows (D, -2, 'MissingPlacement', 'last'); %! X = [1, 3, 1; 1, 3, 2; 1, 2, 2; 2, 1, NaN; 2, -4, 2; -2, NaN, 3]; %! assert_equal (days (D), X); %!test %! D = days ([1, 2, 2; 1, 3, 1; 2, -4, 2; 2, 1, NaN; -2, NaN, 3; 1, 3, 2]); %! D = sortrows (D, 2, 'MissingPlacement', 'first'); %! X = [-2, NaN, 3; 2, -4, 2; 2, 1, NaN; 1, 2, 2; 1, 3, 1; 1, 3, 2]; %! assert_equal (days (D), X); %!test %! D = days ([1, 2, 2; 1, 3, 1; 2, -4, 2; 2, 1, NaN; -2, NaN, 3; 1, 3, 2]); %! D = sortrows (D, 2, 'MissingPlacement', 'last'); %! X = [2, -4, 2; 2, 1, NaN; 1, 2, 2; 1, 3, 1; 1, 3, 2; -2, NaN, 3]; %! assert_equal (days (D), X); %!test %! D = days ([1, 2, 2; 1, 3, 1; 2, -4, 2; 2, 1, NaN; -2, NaN, 3; 1, 3, 2]); %! D = sortrows (D, 'ComparisonMethod', 'abs', 'MissingPlacement', 'first'); %! X = [1, 2, 2; 1, 3, 1; 1, 3, 2; -2, NaN, 3; 2, 1, NaN; 2, -4, 2]; %! assert_equal (days (D), X); %!test %! D = days ([1, 2, 2; 1, 3, 1; 2, -4, 2; 2, 1, NaN; -2, NaN, 3; 1, 3, 2]); %! D = sortrows (D, 'ComparisonMethod', 'abs', 'MissingPlacement', 'last'); %! X = [1, 2, 2; 1, 3, 1; 1, 3, 2; 2, 1, NaN; 2, -4, 2; -2, NaN, 3]; %! assert_equal (days (D), X); %!test %! D = days ([1, 2, 2; 1, 3, 1; 2, -4, 2; 2, 1, NaN; -2, NaN, 3; 1, 3, 2]); %! D = sortrows (D, [1, -2, 3]); %! X = [-2, NaN, 3; 1, 3, 1; 1, 3, 2; 1, 2, 2; 2, 1, NaN; 2, -4, 2]; %! assert_equal (days (D), X); %!test %! D = days ([1, 2, 2; 1, 3, 1; 2, -4, 2; 2, 1, NaN; -2, NaN, 3; 1, 3, 2]); %! D = sortrows (D, [-1, -2, 3]); %! X = [2, 1, NaN; 2, -4, 2; 1, 3, 1; 1, 3, 2; 1, 2, 2; -2, NaN, 3]; %! assert_equal (days (D), X); %!test %! D = days ([1, 2, 2; 1, 3, 1; 2, -4, 2; 2, 1, NaN; -2, NaN, 3; 1, 3, 2]); %! D = sortrows (D, [1, -2, -3]); %! X = [-2, NaN, 3; 1, 3, 2; 1, 3, 1; 1, 2, 2; 2, 1, NaN; 2, -4, 2]; %! assert_equal (days (D), X); %!test %! D = days ([1, 2, 2; 1, 3, 1; 2, -4, 2; 2, 1, NaN; -2, NaN, 3; 1, 3, 2]); %! D = sortrows (D, [1, -2], {'ascend', 'descend'}); %! X = [-2, NaN, 3; 1, 3, 1; 1, 3, 2; 1, 2, 2; 2, 1, NaN; 2, -4, 2]; %! assert_equal (days (D), X); %!test %! D = days ([1, 2, 2; 1, 3, 1; 2, -4, 2; 2, 1, NaN; -2, NaN, 3; 1, 3, 2]); %! D = sortrows (D, [1, -2], {'descend', 'ascend'}); %! X = [2, -4, 2; 2, 1, NaN; 1, 2, 2; 1, 3, 1; 1, 3, 2; -2, NaN, 3]; %! assert_equal (days (D), X); %!test %! D = days ([1, 2, 2; 1, 3, 1; 2, -4, 2; 2, 1, NaN; -2, NaN, 3; 1, 3, 2]); %! D = sortrows (D, [-1, -2, 3], {'ascend', 'ascend', 'descend'}); %! X = [-2, NaN, 3; 1, 2, 2; 1, 3, 2; 1, 3, 1; 2, -4, 2; 2, 1, NaN]; %! assert_equal (days (D), X); %!test %! D = days ([1, 2, 2; 1, 3, 1; 2, -4, 2; 2, 1, NaN; -2, NaN, 3; 1, 3, 2]); %! D = sortrows (D, [1, -2, -3], string ({'ascend', 'ascend', 'descend'})); %! X = [-2, NaN, 3; 1, 2, 2; 1, 3, 2; 1, 3, 1; 2, -4, 2; 2, 1, NaN]; %! assert_equal (days (D), X); %!test %! D = days ([1, 2, 2; 1, 3, 1; 2, -4, 2; 2, 1, NaN; -2, NaN, 3; 1, 3, 2]); %! D = sortrows (D, string ({'ascend', 'ascend', 'descend'})); %! X = [-2, NaN, 3; 1, 2, 2; 1, 3, 2; 1, 3, 1; 2, -4, 2; 2, 1, NaN]; %! assert_equal (days (D), X); %!test %! D = days ([1, 2, 2; 1, 3, 1; 2, -4, 2; 2, 1, NaN; -2, NaN, 3; 1, 3, 2]); %! D = sortrows (D, string ({'ascend', 'ascend', 'descend'})); %! X = [-2, NaN, 3; 1, 2, 2; 1, 3, 2; 1, 3, 1; 2, -4, 2; 2, 1, NaN]; %! assert_equal (days (D), X); %!test %! D = days ([1, 2, 2; 1, 3, 1; 2, -4, 2; 2, 1, NaN; -2, NaN, 3; 1, 3, 2]); %! D = sortrows (D, [1, 2, -3]); %! X = [-2, NaN, 3; 1, 2, 2; 1, 3, 2; 1, 3, 1; 2, -4, 2; 2, 1, NaN]; %! assert_equal (days (D), X); %!test %! D = days ([1, 2, 2; 1, 3, 1; 2, -4, 2; 2, 1, NaN; -2, NaN, 3; 1, 3, 2]); %! D = sortrows (D, [1, -2, 3], 'MissingPlacement', 'first'); %! X = [-2, NaN, 3; 1, 3, 1; 1, 3, 2; 1, 2, 2; 2, 1, NaN; 2, -4, 2]; %! assert_equal (days (D), X); %!test %! D = days ([1, 2, 2; 1, 3, 1; 2, -4, 2; 2, 1, NaN; -2, NaN, 3; 1, 3, 2]); %! D = sortrows (D, [-1, -2, 3], 'MissingPlacement', 'first'); %! X = [2, 1, NaN; 2, -4, 2; 1, 3, 1; 1, 3, 2; 1, 2, 2; -2, NaN, 3]; %! assert_equal (days (D), X); %!test %! D = days ([1, 2, 2; 1, 3, 1; 2, -4, 2; 2, 1, NaN; -2, NaN, 3; 1, 3, 2]); %! D = sortrows (D, [1, -2, -3], 'MissingPlacement', 'first'); %! X = [-2, NaN, 3; 1, 3, 2; 1, 3, 1; 1, 2, 2; 2, 1, NaN; 2, -4, 2]; %! assert_equal (days (D), X); %!test %! D = days ([1, 2, 2; 1, 3, 1; 2, -4, 2; 2, 1, NaN; -2, NaN, 3; 1, 3, 2]); %! D = sortrows (D, [1, -2, 3], 'MissingPlacement', 'last'); %! X = [-2, NaN, 3; 1, 3, 1; 1, 3, 2; 1, 2, 2; 2, 1, NaN; 2, -4, 2]; %! assert_equal (days (D), X); %!test %! D = days ([1, 2, 2; 1, 3, 1; 2, -4, 2; 2, 1, NaN; -2, NaN, 3; 1, 3, 2]); %! D = sortrows (D, [-1, -2, 3], 'MissingPlacement', 'last'); %! X = [2, 1, NaN; 2, -4, 2; 1, 3, 1; 1, 3, 2; 1, 2, 2; -2, NaN, 3]; %! assert_equal (days (D), X); %!test %! D = days ([1, 2, 2; 1, 3, 1; 2, -4, 2; 2, 1, NaN; -2, NaN, 3; 1, 3, 2]); %! D = sortrows (D, [1, -2, -3], 'MissingPlacement', 'last'); %! X = [-2, NaN, 3; 1, 3, 2; 1, 3, 1; 1, 2, 2; 2, 1, NaN; 2, -4, 2]; %! assert_equal (days (D), X); %!test %! D = days ([1, 2, 2; 1, 3, 1; 2, -4, 2; 2, 1, NaN; -2, NaN, 3; 1, 3, 2]); %! D = sortrows (D, [1, -2, 3], 'MissingPlacement', 'first', 'ComparisonMethod', 'abs'); %! X = [1, 3, 1; 1, 3, 2; 1, 2, 2; -2, NaN, 3; 2, -4, 2; 2, 1, NaN]; %! assert_equal (days (D), X); %!test %! D = days ([1, 2, 2; 1, 3, 1; 2, -4, 2; 2, 1, NaN; -2, NaN, 3; 1, 3, 2]); %! D = sortrows (D, [-1, -2, 3], 'MissingPlacement', 'first', 'ComparisonMethod', 'abs'); %! X = [-2, NaN, 3; 2, -4, 2; 2, 1, NaN; 1, 3, 1; 1, 3, 2; 1, 2, 2]; %! assert_equal (days (D), X); %!test %! D = days ([1, 2, 2; 1, 3, 1; 2, -4, 2; 2, 1, NaN; -2, NaN, 3; 1, 3, 2]); %! D = sortrows (D, [1, -2, -3], 'MissingPlacement', 'first', 'ComparisonMethod', 'abs'); %! X = [1, 3, 2; 1, 3, 1; 1, 2, 2; -2, NaN, 3; 2, -4, 2; 2, 1, NaN]; %! assert_equal (days (D), X); %!test %! D = days ([1, 2, 2; 1, 3, 1; 2, -4, 2; 2, 1, NaN; -2, NaN, 3; 1, 3, 2]); %! D = sortrows (D, [1, -2, 3], 'MissingPlacement', 'last', 'ComparisonMethod', 'abs'); %! X = [1, 3, 1; 1, 3, 2; 1, 2, 2; 2, -4, 2; 2, 1, NaN; -2, NaN, 3]; %! assert_equal (days (D), X); %!test %! D = days ([1, 2, 2; 1, 3, 1; 2, -4, 2; 2, 1, NaN; -2, NaN, 3; 1, 3, 2]); %! D = sortrows (D, [-1, -2, 3], 'MissingPlacement', 'last', 'ComparisonMethod', 'abs'); %! X = [2, -4, 2; 2, 1, NaN; -2, NaN, 3; 1, 3, 1; 1, 3, 2; 1, 2, 2]; %! assert_equal (days (D), X); %!test %! D = days ([1, 2, 2; 1, 3, 1; 2, -4, 2; 2, 1, NaN; -2, NaN, 3; 1, 3, 2]); %! D = sortrows (D, [1, -2, -3], 'MissingPlacement', 'last', 'ComparisonMethod', 'abs'); %! X = [1, 3, 2; 1, 3, 1; 1, 2, 2; 2, -4, 2; 2, 1, NaN; -2, NaN, 3]; %! assert_equal (days (D), X); %!test %! D = days ([1, 2, 2; 1, 3, 1; 2, -4, 2; 2, 1, NaN; -2, NaN, 3; 1, 3, 2]); %! D = sortrows (D, [1, -2], {'ascend', 'descend'}, 'MissingPlacement', 'first'); %! X = [-2, NaN, 3; 1, 3, 1; 1, 3, 2; 1, 2, 2; 2, 1, NaN; 2, -4, 2]; %! assert_equal (days (D), X); %!test %! D = days ([1, 2, 2; 1, 3, 1; 2, -4, 2; 2, 1, NaN; -2, NaN, 3; 1, 3, 2]); %! D = sortrows (D, [1, -2], {'descend', 'ascend'}, 'MissingPlacement', 'last'); %! X = [2, -4, 2; 2, 1, NaN; 1, 2, 2; 1, 3, 1; 1, 3, 2; -2, NaN, 3]; %! assert_equal (days (D), X); %!test %! D = days ([1, 2, 2; 1, 3, 1; 2, -4, 2; 2, 1, NaN; -2, NaN, 3; 1, 3, 2]); %! D = sortrows (D, [-1, -2, 3], {'ascend', 'ascend', 'descend'}, ... %! 'ComparisonMethod', 'abs', 'MissingPlacement', 'first'); %! X = [1, 2, 2; 1, 3, 2; 1, 3, 1; -2, NaN, 3; 2, 1, NaN; 2, -4, 2]; %! assert_equal (days (D), X); %!test %! D = days ([1, 2, 2; 1, 3, 1; 2, -4, 2; 2, 1, NaN; -2, NaN, 3; 1, 3, 2]); %! D = sortrows (D, [1, -2, -3], {'ascend', 'ascend', 'descend'}, ... %! 'ComparisonMethod', 'abs', 'MissingPlacement', 'first'); %! X = [1, 2, 2; 1, 3, 2; 1, 3, 1; -2, NaN, 3; 2, 1, NaN; 2, -4, 2]; %! assert_equal (days (D), X); %!test %! D = days ([1, 2, 2; 1, 3, 1; 2, -4, 2; 2, 1, NaN; -2, NaN, 3; 1, 3, 2]); %! D = sortrows (D, [-1, -2, 3], {'ascend', 'ascend', 'descend'}, ... %! 'ComparisonMethod', 'abs', 'MissingPlacement', 'last'); %! X = [1, 2, 2; 1, 3, 2; 1, 3, 1; 2, 1, NaN; 2, -4, 2; -2, NaN, 3]; %! assert_equal (days (D), X); %!test %! D = days ([1, 2, 2; 1, 3, 1; 2, -4, 2; 2, 1, NaN; -2, NaN, 3; 1, 3, 2]); %! D = sortrows (D, [1, -2, -3], {'ascend', 'ascend', 'descend'}, ... %! 'ComparisonMethod', 'abs', 'MissingPlacement', 'last'); %! X = [1, 2, 2; 1, 3, 2; 1, 3, 1; 2, 1, NaN; 2, -4, 2; -2, NaN, 3]; %! assert_equal (days (D), X); %!test %! D = days ([1, 2, 2; 1, 3, 1; 2, -4, 2; 2, 1, NaN; -2, NaN, 3; 1, 3, 2]); %! D = sortrows (D, {'ascend', 'ascend', 'descend'}, 'ComparisonMethod', 'abs'); %! X = [1, 2, 2; 1, 3, 2; 1, 3, 1; 2, 1, NaN; 2, -4, 2; -2, NaN, 3]; %! assert_equal (days (D), X); %!test %! D = days ([1, 2, 2; 1, 3, 1; 2, -4, 2; 2, 1, NaN; -2, NaN, 3; 1, 3, 2]); %! D = sortrows (D, [1, 2, -3], 'ComparisonMethod', 'abs'); %! X = [1, 2, 2; 1, 3, 2; 1, 3, 1; 2, 1, NaN; 2, -4, 2; -2, NaN, 3]; %! assert_equal (days (D), X); %!error ... %! sortrows (days (ones (2, 3, 4))) %!error ... %! sortrows (days (2), 'MissingPlacement', 2) %!error ... %! sortrows (days (2), 'ComparisonMethod', 2) %!error ... %! sortrows (days (1), 1, 'ascend', 2) %!error ... %! sortrows (days (1), 1.5) %!error ... %! sortrows (days (1), ones (2)) %!error ... %! sortrows (days (1), 2) %!error ... %! sortrows (days (1), {'a'}) %!error ... %! sortrows (days (1), 1, {'ascend', 'descend'}) %!error ... %! sortrows (days (1), {1}) %!error ... %! sortrows (days (1), {'ascend'}, 'ascend') %!error ... %! sortrows (days (1), {'ascend'}, 1) %!error ... %! sortrows (days (1), 1, 1) %!error ... %! sortrows (days (1), 1, {'a'}) %!error ... %! sortrows (days (1), 1, {'ascend', 'descend'}) %!assert_equal (unique (days ([1, 2, 2, 3, 4, 4])), days ([1:4])) %!assert_equal (unique (days ([1, 2, 2, 3, 4, 4]')), days ([1:4]')) %!test %! D = days ([1, 2, 2, 3, 4, 4]); %! [c, i, j] = unique (D); %! assert_equal (c, D(i)); %! assert_equal (c(j), D); %!assert_equal (unique (days ([1, 4, 2, 2, 3]), 'sorted'), days (1:4)) %!test %! D = days ([1, 4, 2, 2, 3]); %! [c, i, j] = unique (D, 'sorted'); %! assert_equal (c, days (1:4)); %! assert_equal (c, D(i)); %! assert_equal (c(j), D); %!test %! D = days ([1, 4, 2, 2, 3]); %! [c, i, j] = unique (D, 'stable'); %! assert_equal (c, days ([1, 4, 2, 3])); %! assert_equal (c, D(i)); %! assert_equal (c(j), D); %!test %! D = days ([1, 4, 2, 2, 3]); %! [c, i, j] = unique (D, 'sorted', 'last'); %! assert_equal (i, [1; 4; 5; 2]); %! assert_equal (c, D(i)); %! assert_equal (c(j), D); %!test %! D = days ([1, 4, 2, 2, 3]); %! [c, i, j] = unique (D, 'stable', 'last'); %! assert_equal (i, [1; 2; 4; 5]); %! assert_equal (c, D(i)); %! assert_equal (c(j), D); %!test %! D = days ([3, 1, 3; 3, 3, 1; 1, 3, 3; 3, 2, 3; 2, 3, 3; ... %! 1, 1, 3; 1, 2, 3; 2, 3, 2; 3, 3, 2; 3, 3, 1]); %! [c, i, j] = unique (D(:,[1:2]), 'rows'); %! assert_equal (c, days ([1, 1; 1, 2; 1, 3; 2, 3; 3, 1; 3, 2; 3, 3])); %! assert_equal (i, [6; 7; 3; 5; 1; 4; 2]); %! assert_equal (j, [5; 7; 3; 6; 4; 1; 2; 4; 7; 7]); %! assert_equal (D(i,[1:2]), days ([1, 1; 1, 2; 1, 3; 2, 3; 3, 1; 3, 2; 3, 3])); %!test %! [B, ia, ib] = unique (days ([1, 2; 2, 3; 1, 2]), 'rows', 'sorted', 'first'); %! assert_equal (B, days ([1, 2; 2, 3])); %! assert_equal (ia, [1; 2]); %! assert_equal (ib, [1; 2; 1]); %!test %! [B, ia, ib] = unique (days ([1, 2; 2, 3; 1, 2]), 'rows', 'sorted', 'last'); %! assert_equal (B, days ([1, 2; 2, 3])); %! assert_equal (ia, [3; 2]); %! assert_equal (ib, [1; 2; 1]); %!test %! [B, ia, ib] = unique (days ([1, 2; 2, 3; 1, 2]), 'rows', 'stable', 'first'); %! assert_equal (B, days ([1, 2; 2, 3])); %! assert_equal (ia, [1; 2]); %! assert_equal (ib, [1; 2; 1]); %!test %! [B, ia, ib] = unique (days ([1, 2; 2, 3; 1, 2]), 'rows', 'stable', 'last'); %! assert_equal (B, days ([2, 3; 1, 2])); %! assert_equal (ia, [2; 3]); %! assert_equal (ib, [2; 1; 2]); %!error ... %! unique (days ([1:3]), 'legacy') %!assert_equal (interp1 (days ([1:5]), 1:5), days (1:5)) %!assert_equal (interp1 (days ([1:5]), 1:6, 'linear', days(1)), days ([1:5,1])) %!assert_equal (interp1 (1:5, days ([1:5])), [1:5]) %!assert_equal (interp1 (1:5, days ([1:6]), 'linear', 1), [1:5,1]) %!assert_equal (interp1 (days ([1:5]), 1:5, days ([1.5, 2.5, 3.5, 4.5])), ... %! [1.5, 2.5, 3.5, 4.5]) %!assert_equal (interp1 (1:5, days ([1:5]), [1.5, 2.5, 3.5, 4.5]), ... %! days ([1.5, 2.5, 3.5, 4.5])) %!assert_equal (interp1 (days ([1:5]), days ([1:5]), days ([1.5, 2.5, 3.5, 4.5])), ... %! days ([1.5, 2.5, 3.5, 4.5])) %!assert_equal (isequaln (interp1 (1:5, days ([1:5]), [-1.5, 2.5, 3.5, 6.5]), ... %! days ([NaN, 2.5, 3.5, NaN])), true) %!assert_equal (class (interp1 (1:5, days ([1:5]), 'pp')), 'struct') %!assert_equal (isequaln (interp1 (1:5, days ([1:5]), [-1.5, 2.5, 3.5, 6.5], 'linear'), ... %! days ([NaN, 2.5, 3.5, NaN])), true) %!assert_equal (interp1 (1:5, days ([1:5]), [-1.5, 2.5, 3.5, 6.5], 'linear', days (2)), ... %! days ([2, 2.5, 3.5, 2])) %!assert_equal (interp1 (1:5, days ([1:5]), [-1.5, 2.5, 3.5, 6.5], 'linear', 'extrap'), ... %! days ([-1.5, 2.5, 3.5, 6.5])) %!test %! YI = interp1 ([1:5], hours (1:5), [1.5, 2.5, 3.5, 4.5]); %! assert_equal (round (YI), round (hours ([1.5, 2.5, 3.5, 4.5]))); %! assert_equal (YI.Format, 'h'); %!test %! YI = interp1 (days ([1:5]), hours (1:5), days ([1.5, 2.5, 3.5, 4.5])); %! assert_equal (round (YI), round (hours ([1.5, 2.5, 3.5, 4.5]))); %! assert_equal (YI.Format, 'h'); %!test %! YI = interp1 (hours ([1:5]), days (1:5), hours ([1.5, 2.5, 3.5, 4.5])); %! assert_equal (round (YI), round (days ([1.5, 2.5, 3.5, 4.5]))); %! assert_equal (YI.Format, 'd'); %!error ... %! interp1 (1:5, days ([1:5]), 'linear', days (2)) %!error ... %! interp1 (days ([1:5]), 1:5, 'linear', 2) %!error ... %! interp1 (days ([1:5]), 1:5, [-1.5, 2.5, 3.5, 6.5]) %!error ... %! interp1 (1:5, days ([1:5]), days ([-1.5, 2.5, 3.5, 6.5])) %!error ... %! interp1 (1:5, days ([1:5]), [-1.5, 2.5, 3.5, 6.5], 'linear', 2) %!error ... %! interp1 (days ([1:5]), 1:5, days ([-1.5, 2.5, 3.5, 6.5]), 'linear', days (2)) %!error ... %! interp1 (days ([1:5]), "asd", days ([1.5, 2.5, 3.5, 4.5])) %!error ... %! interp1 (days ([1:5]), {1:5}, days ([1.5, 2.5, 3.5, 4.5])) %!test %! A = days ([7 1 7 7 4]); %! B = days ([7 0 4 4 0]); %! [C, ixA, ixB] = intersect (A, B); %! assert_equal (C, days ([4, 7])); %! assert_equal (ixA, [5; 1]); %! assert_equal (ixB, [3; 1]); %!test %! A = days ([7 1 7 7 4]); %! B = days ([7 0 4 4 0]); %! [C, ixA, ixB] = intersect (A, B, 'sorted'); %! assert_equal (C, days ([4, 7])); %! assert_equal (ixA, [5; 1]); %! assert_equal (ixB, [3; 1]); %!test %! A = days ([7 1 7 7 4]); %! B = days ([7 0 4 4 0]); %! [C, ixA, ixB] = intersect (A, B, 'stable'); %! assert_equal (C, days ([7, 4])); %! assert_equal (ixA, [1; 5]); %! assert_equal (ixB, [1; 3]); %!test %! A = days ([2, 2, 2; 0, 0, 1; 1, 2, 3; 1, 1, 1]); %! B = days ([1, 2, 3; 2, 2, 2; 2, 2, 0]); %! [C, ixA, ixB] = intersect (A, B, 'rows'); %! assert_equal (C, days ([1, 2, 3; 2, 2, 2])); %! assert_equal (ixA, [3; 1]); %! assert_equal (ixB, [1; 2]); %!test %! A = days ([2, 2, 2; 0, 0, 1; 1, 2, 3; 1, 1, 1]); %! B = days ([1, 2, 3; 2, 2, 2; 2, 2, 0]); %! [C, ixA, ixB] = intersect (A, B, 'sorted', 'rows'); %! assert_equal (C, days ([1, 2, 3; 2, 2, 2])); %! assert_equal (ixA, [3; 1]); %! assert_equal (ixB, [1; 2]); %!test %! A = days ([2, 2, 2; 0, 0, 1; 1, 2, 3; 1, 1, 1]); %! B = days ([1, 2, 3; 2, 2, 2; 2, 2, 0]); %! [C, ixA, ixB] = intersect (A, B, 'stable', 'rows'); %! assert_equal (C, days ([2, 2, 2; 1, 2, 3])); %! assert_equal (ixA, [1; 3]); %! assert_equal (ixB, [2; 1]); %!assert_equal (intersect (days ([5, NaN, NaN]), days ([5, NaN, NaN])), days (5)) %!assert_equal (intersect (days ([1, 2]), '48:00:00'), days (2)) %!assert_equal (intersect (days ([1, 2]), {'48:00:00'}), days (2)) %!assert_equal (intersect (days ([1, 2]), string ({'48:00:00'})), days (2)) %!assert_equal (intersect (days ([1, 2]), 2), days (2)) %!assert_equal (intersect ('48:00:00', days ([1, 2])), days (2)) %!assert_equal (intersect ({'48:00:00'}, days ([1, 2])), days (2)) %!assert_equal (intersect (string ({'48:00:00'}), days ([1, 2])), days (2)) %!assert_equal (intersect (2, days ([1, 2])), days (2)) %!assert_equal (intersect (days ([2, 3]), 1:3), days ([2, 3])) %!error ... %! intersect (days ([1, 2]), days (2), 'legacy') %!error ... %! intersect ({1}, days ([1, 2])) %!error ... %! intersect (days ([1, 2]), caldays) %!error ... %! intersect (days ([1, 2]), datetime) %!test %! A = days ([3, 6, 2, 1, 5, 1, 1]); %! B = days ([2, 4, 6]); %! [C, index] = setdiff (A, B); %! assert_equal (C, days ([1, 3, 5])); %! assert_equal (index, [4; 1; 5]); %!test %! A = days ([3, 6, 2, 1, 5, 1, 1]); %! B = days ([2, 4, 6]); %! [C, index] = setdiff (A, B, 'sorted'); %! assert_equal (C, days ([1, 3, 5])); %! assert_equal (index, [4; 1; 5]); %!test %! A = days ([3, 6, 2, 1, 5, 1, 1]); %! B = days ([2, 4, 6]); %! [C, index] = setdiff (A, B, 'stable'); %! assert_equal (C, days ([3, 1, 5])); %! assert_equal (index, [1; 4; 5]); %!test %! A = days ([7, 9, 7; 0, 0, 0; 7, 9, 7; 5, 5, 5; 1, 4, 5]); %! B = days ([0, 0, 0; 5, 5, 5]); %! [C, index] = setdiff (A, B, 'rows'); %! assert_equal (C, days ([1, 4, 5; 7, 9, 7])); %! assert_equal (index, [5; 1]); %!test %! A = days ([7, 9, 7; 0, 0, 0; 7, 9, 7; 5, 5, 5; 1, 4, 5]); %! B = days ([0, 0, 0; 5, 5, 5]); %! [C, index] = setdiff (A, B, 'sorted', 'rows'); %! assert_equal (C, days ([1, 4, 5; 7, 9, 7])); %! assert_equal (index, [5; 1]); %!test %! A = days ([7, 9, 7; 0, 0, 0; 7, 9, 7; 5, 5, 5; 1, 4, 5]); %! B = days ([0, 0, 0; 5, 5, 5]); %! [C, index] = setdiff (A, B, 'stable', 'rows'); %! assert_equal (C, days ([7, 9, 7; 1, 4, 5])); %! assert_equal (index, [1; 5]); %!test %! C = setdiff (days ([5, NaN, NaN]), days ([5, NaN, NaN])); %! assert_equal (isequaln (C, days ([NaN, NaN])), true); %!assert_equal (setdiff (days ([1, 2]), days (2)), days (1)) %!assert_equal (setdiff (days ([1, 2]), '48:00:00'), days (1)) %!assert_equal (setdiff (days ([1, 2]), string ({'48:00:00'})), days (1)) %!assert_equal (size (setdiff (days ([1, 2]), string ({'48:00:00', '24:00:00'}))), [1, 0]) %!assert_equal (class (setdiff (days ([1, 2]), string ({'48:00:00', '24:00:00'}))), 'duration') %!assert_equal (setdiff (days ([1, 2, 3]), string ({'48:00:00', '24:00:00'})), days (3)) %!assert_equal (setdiff (string ({'24:00:00', '48:00:00'}), days (2)), days (1)) %!assert_equal (size (setdiff (string ({'24:00:00'}), days ([1, 2]))), [1, 0]) %!assert_equal (class (setdiff (string ({'24:00:00'}), days ([1, 2]))), 'duration') %!assert_equal (size (setdiff ('24:00:00', days ([1, 2]))), [1, 0]) %!assert_equal (class (setdiff ('24:00:00', days ([1, 2]))), 'duration') %!assert_equal (setdiff ([1, 2, 3], days (2)), days ([1, 3])) %!assert_equal (size (setdiff (days ([2, 3]), 1:3)), [1, 0]) %!error ... %! setdiff (days ([1, 2]), days (2), 'legacy') %!error ... %! setdiff ({1}, days ([1, 2])) %!error ... %! setdiff (days ([1, 2]), caldays) %!error ... %! setdiff (days ([1, 2]), datetime) %!test %! A = days ([5, 1, 3, 3, 3]); %! B = days ([4, 1, 2]); %! [C, ixA, ixB] = setxor (A, B); %! assert_equal (C, days ([2, 3, 4, 5])); %! assert_equal (ixA, [3; 1]); %! assert_equal (ixB, [3; 1]); %!test %! A = days ([5, 1, 3, 3, 3]); %! B = days ([4, 1, 2]); %! [C, ixA, ixB] = setxor (A, B, 'sorted'); %! assert_equal (C, days ([2, 3, 4, 5])); %! assert_equal (ixA, [3; 1]); %! assert_equal (ixB, [3; 1]); %!test %! A = days ([5, 1, 3, 3, 3]); %! B = days ([4, 1, 2]); %! [C, ixA, ixB] = setxor (A, B, 'stable'); %! assert_equal (C, days ([5, 3, 4, 2])); %! assert_equal (ixA, [1; 3]); %! assert_equal (ixB, [1; 3]); %!test %! A = days ([7, 8, 9; 7, 7, 1; 7, 7, 1; 1, 2, 3; 4, 5, 6]); %! B = days ([1, 2, 3; 4, 5, 6; 7, 7, 2]); %! [C, ixA, ixB] = setxor (A, B, 'rows'); %! assert_equal (C, days ([7, 7, 1; 7, 7, 2; 7, 8, 9])); %! assert_equal (ixA, [2; 1]); %! assert_equal (ixB, [3]); %!test %! A = days ([7, 8, 9; 7, 7, 1; 7, 7, 1; 1, 2, 3; 4, 5, 6]); %! B = days ([1, 2, 3; 4, 5, 6; 7, 7, 2]); %! [C, ixA, ixB] = setxor (A, B, 'sorted', 'rows'); %! assert_equal (C, days ([7, 7, 1; 7, 7, 2; 7, 8, 9])); %! assert_equal (ixA, [2; 1]); %! assert_equal (ixB, [3]); %!test %! A = days ([7, 8, 9; 7, 7, 1; 7, 7, 1; 1, 2, 3; 4, 5, 6]); %! B = days ([1, 2, 3; 4, 5, 6; 7, 7, 2]); %! [C, ixA, ixB] = setxor (A, B, 'stable', 'rows'); %! assert_equal (C, days ([7, 8 ,9; 7, 7, 1; 7, 7, 2])); %! assert_equal (ixA, [1; 2]); %! assert_equal (ixB, [3]); %!test %! C = setxor (days ([5, NaN, NaN]), days ([5, NaN, NaN])); %! assert_equal (isequaln (C, days ([NaN, NaN, NaN, NaN])), true); %!assert_equal (setxor (days ([1, 2]), days (2)), days (1)) %!assert_equal (setxor (days ([1, 2]), '48:00:00'), days (1)) %!assert_equal (setxor (days ([1, 2]), string ('48:00:00')), days (1)) %!assert_equal (setxor (days ([1, 2]), string ('24:00:00')), days (2)) %!assert_equal (size (setxor (days ([1, 2]), string ({'48:00:00', '24:00:00'}))), [1, 0]) %!assert_equal (class (setxor (days ([1, 2]), string ({'48:00:00', '24:00:00'}))), 'duration') %!assert_equal (setxor (days ([1, 2, 3]), string ({'48:00:00', '24:00:00'})), days (3)) %!assert_equal (setxor (days ([1, 2, 3]), {'48:00:00', '24:00:00'}), days (3)) %!assert_equal (setxor ({'24:00:00', '48:00:00'}, days (2)), days (1)) %!assert_equal (setxor (string ({'24:00:00', '48:00:00'}), days (2)), days (1)) %!assert_equal (setxor (string ('24:00:00'), days ([1, 2])), days (2)) %!assert_equal (setxor ('24:00:00', days ([1, 2])), days (2)) %!assert_equal (setxor ([1, 2, 3], days (2)), days ([1, 3])) %!assert_equal (setxor (days ([2, 3]), 1:3), days (1)) %!error ... %! setxor (days ([1, 2]), days (2), 'legacy') %!error ... %! setxor ({1}, days ([1, 2])) %!error ... %! setxor (days ([1, 2]), caldays) %!error ... %! setxor (days ([1, 2]), datetime) %!test %! A = days ([5, 7, 1]); %! B = days ([3, 1, 1]); %! [C, ixA, ixB] = union (A, B); %! assert_equal (C, days ([1, 3, 5, 7])); %! assert_equal (ixA, [3; 1; 2]); %! assert_equal (ixB, 1); %!test %! A = days ([5, 7, 1]); %! B = days ([3, 1, 1]); %! [C, ixA, ixB] = union (A, B, 'sorted'); %! assert_equal (C, days ([1, 3, 5, 7])); %! assert_equal (ixA, [3; 1; 2]); %! assert_equal (ixB, 1); %!test %! A = days ([5, 7, 1]); %! B = days ([3, 1, 1]); %! [C, ixA, ixB] = union (A, B, 'stable'); %! assert_equal (C, days ([5, 7, 1, 3])); %! assert_equal (ixA, [1; 2; 3]); %! assert_equal (ixB, 1); %!test %! A = days ([2, 2, 2; 0, 0, 1]); %! B = days ([1, 2, 3; 2, 2, 2; 2, 2, 2]); %! [C, ixA, ixB] = union (A, B, 'rows'); %! assert_equal (C, days ([0, 0, 1; 1, 2, 3; 2, 2, 2])); %! assert_equal (ixA, [2; 1]); %! assert_equal (ixB, 1); %!test %! A = days ([2, 2, 2; 0, 0, 1]); %! B = days ([1, 2, 3; 2, 2, 2; 2, 2, 2]); %! [C, ixA, ixB] = union (A, B, 'sorted', 'rows'); %! assert_equal (C, days ([0, 0, 1; 1, 2, 3; 2, 2, 2])); %! assert_equal (ixA, [2; 1]); %! assert_equal (ixB, 1); %!test %! A = days ([2, 2, 2; 0, 0, 1]); %! B = days ([1, 2, 3; 2, 2, 2; 2, 2, 2]); %! [C, ixA, ixB] = union (A, B, 'stable', 'rows'); %! assert_equal (C, days ([2, 2, 2; 0, 0, 1; 1, 2, 3])); %! assert_equal (ixA, [1; 2]); %! assert_equal (ixB, 1); %!test %! C = union (days ([5, NaN, 1]), days ([4, NaN, NaN])); %! assert_equal (isequaln (C, days ([1, 4, 5, NaN, NaN, NaN])), true); %!assert_equal (union (days ([1, 3]), days (2)), days ([1, 2, 3])) %!assert_equal (union (days ([1, 3]), '48:00:00'), days ([1, 2, 3])) %!assert_equal (union (days ([1, 4]), string ('48:00:00')), days ([1, 2, 4])) %!assert_equal (union (days ([1, 4]), {'48:00:00'}), days ([1, 2, 4])) %!assert_equal (union (days ([1, 1]), string ({'48:00:00', '24:00:00'})), days ([1, 2])) %!assert_equal (union (days ([1, 1]), {'48:00:00', '24:00:00'}), days ([1, 2])) %!assert_equal (union (days ([1, 3]), string ({'48:00:00', '24:00:00'})), days ([1, 2, 3])) %!assert_equal (union (string ({'24:00:00', '48:00:00'}), days (2)), days ([1, 2])) %!assert_equal (union (string ({'24:00:00'}), days ([1, 4])), days ([1, 4])) %!assert_equal (union ('24:00:00', days ([0, 2])), days ([0, 1, 2])) %!assert_equal (union ([1, 2, 3], days (2)), days ([1, 2, 3])) %!assert_equal (union (days ([2, 3]), 1:3), days ([1, 2, 3])) %!error ... %! union (days ([1, 2]), days (2), 'legacy') %!error ... %! union ({1}, days ([1, 2])) %!error ... %! union (days ([1, 2]), caldays) %!error ... %! union (days ([1, 2]), datetime) ################################################################################ ## ** Array Operations ** ## ################################################################################ ## Available Methods ## ## ## ## 'cat' 'horzcat' 'vertcat' 'repmat' ## ## 'repelem' 'repelems' 'reshape' 'circshift' ## ## 'permute' 'ipermute' 'transpose' 'ctranspose' ## ## ## ################################################################################ %!test %! D = cat (2, days (2), hours (3)); %! assert_equal (D, days ([2, 0.125])); %! assert_equal (D.Format, 'd'); %!test %! D = cat (2, hours (3), days (3)); %! assert_equal (D, hours ([3, 72])); %! assert_equal (D.Format, 'h'); %!test %! D = cat (2, '24:00:00', hours (3), days (3)); %! assert_equal (D, hours ([24, 3, 72])); %! assert_equal (D.Format, 'h'); %!test %! D = cat (2, string ('24:00:00'), hours (3), days (3)); %! assert_equal (D, hours ([24, 3, 72])); %! assert_equal (D.Format, 'h'); %!test %! D = cat (2, string ('24:00:00:00'), hours (3), days (3)); %! assert_equal (D, hours ([576, 3, 72])); %! assert_equal (D.Format, 'h'); %!test %! D = cat (2, seconds (3), string ('24:00:00:00'), hours (3), days (3)); %! assert_equal (D, seconds ([3, 2.0736e+06, 10800, 2.592e+05])); %! assert_equal (D.Format, 's'); %!test %! D = cat (2, string ('24:00:00'), duration (3, 0, 0), days (3)); %! assert_equal (D, hours ([24, 3, 72])); %! assert_equal (D.Format, 'hh:mm:ss'); %!test %! D = cat (1, days (2), hours (3)); %! assert_equal (D, days ([2; 0.125])); %! assert_equal (D.Format, 'd'); %!test %! D = cat (1, hours (3), days (3)); %! assert_equal (D, hours ([3; 72])); %! assert_equal (D.Format, 'h'); %!test %! D = cat (1, '24:00:00', hours (3), days (3)); %! assert_equal (D, hours ([24; 3; 72])); %! assert_equal (D.Format, 'h'); %!test %! D = cat (1, string ('24:00:00'), hours (3), days (3)); %! assert_equal (D, hours ([24; 3; 72])); %! assert_equal (D.Format, 'h'); %!test %! D = cat (1, string ('24:00:00:00'), hours (3), days (3)); %! assert_equal (D, hours ([576; 3; 72])); %! assert_equal (D.Format, 'h'); %!test %! D = cat (1, seconds (3), string ('24:00:00:00'), hours (3), days (3)); %! assert_equal (D, seconds ([3; 2.0736e+06; 10800; 2.592e+05])); %! assert_equal (D.Format, 's'); %!test %! D = cat (1, string ('24:00:00'), duration (3, 0, 0), days (3)); %! assert_equal (D, hours ([24; 3; 72])); %! assert_equal (D.Format, 'hh:mm:ss'); %!test %! D = [days(2), hours(3)]; %! assert_equal (D, days ([2, 0.125])); %! assert_equal (D.Format, 'd'); %!test %! D = [hours(3), days(3)]; %! assert_equal (D, hours ([3, 72])); %! assert_equal (D.Format, 'h'); %!test %! D = ['24:00:00', hours(3), days(3)]; %! assert_equal (D, hours ([24, 3, 72])); %! assert_equal (D.Format, 'h'); %!test %! D = [string('24:00:00'), hours(3), days(3)]; %! assert_equal (D, hours ([24, 3, 72])); %! assert_equal (D.Format, 'h'); %!test %! D = [string('24:00:00:00'), hours(3), days(3)]; %! assert_equal (D, hours ([576, 3, 72])); %! assert_equal (D.Format, 'h'); %!test %! D = [seconds(3), string('24:00:00:00'), hours(3), days(3)]; %! assert_equal (D, seconds ([3, 2.0736e+06, 10800, 2.592e+05])); %! assert_equal (D.Format, 's'); %!test %! D = [string('24:00:00'), duration(3, 0, 0), days(3)]; %! assert_equal (D, hours ([24, 3, 72])); %! assert_equal (D.Format, 'hh:mm:ss'); %!test %! D = [days(2); hours(3)]; %! assert_equal (D, days ([2; 0.125])); %! assert_equal (D.Format, 'd'); %!test %! D = [hours(3); days(3)]; %! assert_equal (D, hours ([3; 72])); %! assert_equal (D.Format, 'h'); %!test %! D = ['24:00:00'; hours(3); days(3)]; %! assert_equal (D, hours ([24; 3; 72])); %! assert_equal (D.Format, 'h'); %!test %! D = [string('24:00:00'); hours(3); days(3)]; %! assert_equal (D, hours ([24; 3; 72])); %! assert_equal (D.Format, 'h'); %!test %! D = [string('24:00:00:00'); hours(3); days(3)]; %! assert_equal (D, hours ([576; 3; 72])); %! assert_equal (D.Format, 'h'); %!test %! D = [seconds(3); string('24:00:00:00'); hours(3); days(3)]; %! assert_equal (D, seconds ([3; 2.0736e+06; 10800; 2.592e+05])); %! assert_equal (D.Format, 's'); %!test %! D = [string('24:00:00'); duration(3, 0, 0); days(3)]; %! assert_equal (D, hours ([24; 3; 72])); %! assert_equal (D.Format, 'hh:mm:ss'); %!assert_equal (repmat (days ([1, 2]), 4), days (repmat ([1, 2], 4))) %!assert_equal (repmat (days ([1, 2]), 2, 3, 4), days (repmat ([1, 2], 2, 3, 4))) %!assert_equal (repmat (days ([1, 2]), [2, 3]), days (repmat ([1, 2], [2, 3]))) %!assert_equal (repelem (days ([1:3]), 4), days (repelem ([1:3], 4))) %!assert_equal (repelem (days ([1:3]), 2, 3, 4), days (repelem ([1:3], 2, 3, 4))) %!assert_equal (repelem (days ([1:3]), [2, 3, 3]), days (repelem ([1:3], [2, 3, 3]))) %!assert_equal (repelems (days ([1:5]), [1, 3, 5; 2, 1, 2]), days ([1, 1, 3, 5, 5])) %!assert_equal (reshape (days (magic (4)), 2, 2, 4), days (reshape (magic (4), 2, 2, 4))) %!assert_equal (reshape (days (magic (4)), [2, 2, 4]), days (reshape (magic (4), [2, 2, 4]))) %!assert_equal (reshape (days (magic (4)), [], 2, 2), days (reshape (magic (4), [], 2, 2))) %!assert_equal (circshift (days (magic (4)), 2), days (circshift (magic (4), 2))) %!assert_equal (circshift (days (magic (4)), 1, 2), days (circshift (magic (4), 1, 2))) %!assert_equal (circshift (days (magic (4)), 1, 1), days (circshift (magic (4), 1, 1))) %!assert_equal (circshift (days (magic (4)), 1, 1), days (circshift (magic (4), 1))) %!test %! X = randi (10, 2, 3, 4); %! D = days (X); %! assert_equal (size (permute (D, [3, 1, 2])), [4, 2, 3]); %! assert_equal (permute (D, [3, 1, 2]), days (permute (X, [3, 1, 2]))); %! assert_equal (D, ipermute (permute (D, [3, 1, 2]), [3, 1, 2])); %!assert_equal (transpose (days ([1, 2; 3, 4])), days ([1, 3; 2, 4])) %!assert_equal (transpose (days ([1, 2; 3, 4])), ctranspose (days ([1, 2; 3, 4]))) %!assert_equal (ctranspose (days ([1, 2; 3, 4])), days ([1, 3; 2, 4])) %!assert_equal (ctranspose (days ([1, 2; 3, 4])), transpose (days ([1, 2; 3, 4]))) ################################################################################ ## ** Reference and Assignment Operations ** ## ################################################################################ ## Available Methods ## ## ## ## 'end' 'subsref' 'subsasgn' ## ## ## ################################################################################ %!test %! X = magic (3); %! D = days (X); %! assert_equal (days (D(:, end)), X(:,end)); %!test %! X = magic (3); %! D = days (X); %! assert_equal (days (D([1:end-1], end)), [6; 7]); %!test %! D = duration ([1:5], 0, 0); %! out = D([2,3]); %! assert_equal (size (out), [1, 2]); %! assert_equal (hours (out), [2, 3]); %!test %! D = duration ([1:5], 0, 0); %! assert_equal (D.Format, 'hh:mm:ss'); %!test %! D = duration ([1:5], 0, 0, 'Format', 'hh:mm'); %! assert_equal (D.Format, 'hh:mm'); %!test %! D = duration ([1:5], 0, 0, 'Format', 'y'); %! assert_equal (D.Format, 'y'); %!error ... %! D = duration ([1:5], 0, 0); D{1} %!error ... %! D = duration ([1:5], 0, 0); D.form %!test %! D = duration ([1:5], 0, 0); %! D([2,3]) = [4, 5]; %! assert_equal (hours (D), [1, 96, 120, 4, 5]); %!test %! D = duration ([1:5], 0, 0); %! D([2,3]) = hours ([4, 5]); %! assert_equal (hours (D), [1, 4, 5, 4, 5]); %!test %! D = days ([1:5]); %! D([2,3]) = {'12:23:42', '00:12:23'}; %! assert_equal (days (D), [1, 0.51646, 0.0085995, 4, 5], 1e-5); %!test %! D = days ([1:5]); %! D([2,3]) = string ({'12:23:42', '00:12:23'}); %! assert_equal (days (D), [1, 0.51646, 0.0085995, 4, 5], 1e-5); %!test %! D = days ([1:5]); %! D([2,3]) = ['12:23:42'; '00:12:23']; %! assert_equal (days (D), [1, 0.51646, 0.0085995, 4, 5], 1e-5); %!test %! D = days ([1:5]); %! D([2,3]) = {'05:12:00:00', '01:00:00:00'}; %! assert_equal (days (D), [1, 5.5, 1, 4, 5]); %!test %! D = days ([1:5]); %! D([2,3]) = string ({'05:12:00:00', '01:00:00:00'}); %! assert_equal (days (D), [1, 5.5, 1, 4, 5]); %!test %! D = days ([1:5]); %! D([2,3]) = ['05:12:00:00'; '01:00:00:00']; %! assert_equal (days (D), [1, 5.5, 1, 4, 5]); %!test %! D = days ([1:5]); %! D([2,3]) = NaN; %! assert_equal (days (D), [1, NaN, NaN, 4, 5]); %! assert_equal (dispstrings (D), {'1 day', 'NaN days', 'NaN days', '4 days', '5 days'}); %!test %! D = days ([1:5]); %! D([1:3]) = missing; %! assert_equal (days (D), [NaN, NaN, NaN, 4, 5]); %! assert_equal (dispstrings (D), {'NaN days', 'NaN days', 'NaN days', '4 days', '5 days'}); %!test %! D = days ([1:5]); %! D([2,3]) = [Inf, -Inf]; %! assert_equal (days (D), [1, Inf, -Inf, 4, 5]); %! assert_equal (dispstrings (D), {'1 day', 'Inf days', '-Inf days', '4 days', '5 days'}); %!test %! D = days ([1:5]); %! D([1:3]) = 2; %! assert_equal (days (D), [2, 2, 2, 4, 5]); %!test %! D = days ([1:5]); %! D([1:3]) = [NaN, 2, 3]; %! assert_equal (days (D), [NaN, 2, 3, 4, 5]); %!test %! D = duration (0, [1:5], 0); %! D([1:3]) = [NaN, 2, 3]; %! assert_equal (minutes (D), [NaN, 48 * 60, 72 * 60, 4, 5]); %! assert_equal (dispstrings (D), {'NaN', '48:00:00', '72:00:00', '00:04:00', '00:05:00'}); %!error ... %! D = duration (); D(1)(1) = 1 %!error ... %! D = duration (); D(1) = {1} %!error ... %! D = duration ([1:5], 0, 0); D{1} = 1 %!error ... %! D = duration ([1:5], 0, 0); D.form = 'f' %!error ... %! D = duration ([1:5], 0, 0); D.Format = 3 %!error ... %! D = duration ([1:5], 0, 0); D.Format = ['y';'d'] %!error ... %! D = duration ([1:5], 0, 0); D.Format = string ({'mm:ss', 'hh:mm'}) %!error ... %! D = duration ([1:5], 0, 0); D.Format = 't' %!error ... %! D = duration ([1:5], 0, 0); D.Format = 'd.h.S' %!error ... %! D = duration ([1:5], 0, 0); D.Format = 's.SS' %!error ... %! D = duration ([1:5], 0, 0); D.Format = 'hh:mm.SS' %!error ... %! D = duration ([1:5], 0, 0); D.Format = 'hh:mm.SM' %!error ... %! D = duration ([1:5], 0, 0); D.Format = 'hh:mm:ss.SSSSSSSSSS' pr0m1th3as-datatypes-9c9a8d3/inst/tests/string.m-tst000066400000000000000000003416421522766574100225020ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the datatypes package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ################################################################################ ## ** Create String and Convert Type ** ## ################################################################################ ## Available Methods ## ## ## ## 'string' 'dispstrings' 'cellstr' 'cell' ## ## 'char' 'double' ## ## ## ################################################################################ %!assert_equal (string, string ('')) %!assert_equal (string, string ({''})) %!assert_equal (string (['a', 'b', 'c']), string ('abc')) %!assert_equal (string (['a', 'b', 'c']), string ({'abc'})) %!test %! str = string (["a";"b";"c"]); %! assert_equal (cellstr (str), {"a";"b";"c"}); ## Constructing from a char vector preserves trailing whitespace (the core ## 'cellstr' would deblank it); MATLAB's 'string' preserves it too. %!assert_equal (cellstr (string ('z ')), {'z '}) %!assert_equal (cellstr (string (' z ')), {' z '}) ## Each row of a padded char matrix keeps its trailing whitespace. %!test %! str = string (['ab '; 'c ']); %! assert_equal (cellstr (str), {'ab '; 'c '}); %! assert_equal (size (str), [2, 1]); %!test %! str = string ({"a";"b";"c"}); %! assert_equal (cellstr (str), {"a";"b";"c"}); %!test %! str = string ({"a";"";"c"}); %! tfM = ismissing (str); %! assert_equal (cell (str), {"a";"";"c"}); %! assert_equal (tfM, logical ([0; 0; 0])); %!test %! str = string ([1 2 3 NaN 5]); %! tfM = ismissing (str); %! assert_equal (cellstr (str), {"1", "2", "3", "", "5"}); %! assert_equal (tfM, logical ([0 0 0 1 0])); %!test %! str = string (duration ([3,4,5; NaN,NaN,NaN])); %! tfM = ismissing (str); %! assert_equal (cellstr (str), {"03:04:05"; ""}); %! assert_equal (tfM, logical ([0; 1])); %!test %! str = string (calendarDuration ([3,4,5; NaN,NaN,NaN])); %! tfM = ismissing (str); %! assert_equal (cellstr (str), {"3y 4mo 5d"; ""}); %! assert_equal (tfM, logical ([0; 1])); %!test %! str = string (repmat ('a', 3, 4, 5)); %! assert_equal (str, repmat (string ('aaaa'), 3, 5)); %! assert_equal (size (str), [3, 5]); %!error ... %! string ({[1 2], false}) %!error string ({"d", @(x)x}); %!error string (@(x)x); %!error string (struct ()) %!assert_equal (dispstrings (string ('a')), {'"a"'}) %!assert_equal (dispstrings (string ("a")), {'"a"'}) %!assert_equal (dispstrings (string ('a\na')), {'"a\na"'}) %!assert_equal (dispstrings (string (NaN)), {''}) %!assert_equal (cellstr (string ('a')), {'a'}) %!assert_equal (cellstr (string ("a")), {'a'}) %!assert_equal (cellstr (string ('a\na')), {'a\na'}) %!assert_equal (cellstr (string (NaN)), {''}) %!test %! str = string ({'3', 4, 'asd', [], NaN, hours(3)}); %! out = {3, 4, 'asd', [], [], '3 hr'}; %! assert_equal (cell (str), out); %!assert_equal (cell ([missing, string('')]), {[], ''}) %!assert_equal (cell ([NaN, string('')]), {[], ''}) %!assert_equal (cell (['', string('')]), {'', ''}) %!assert_equal (cell ([string, string('')]), {'', ''}) %!assert_equal (char (string), ''); %!assert_equal (char (string ('')), ''); %!assert_equal (char (string ({'a', 's', 'd'})), ['a'; 's'; 'd']) %!assert_equal (double (string ({'a', 's', 'd'})), [NaN, NaN, NaN]) %!assert_equal (double (string ({'a'; 's'; 'd'})), [NaN; NaN; NaN]) %!assert_equal (double (string ([1, 2, NaN])), [1, 2, NaN]) %!assert_equal (double (string ({1, 2, 'a', missing})), [1, 2, NaN, NaN]) %!assert_equal (double ([string([1, 2]) 'a', missing]), [1, 2, NaN, NaN]) ################################################################################ ## ** Summary Information ** ## ################################################################################ ## Available Methods ## ## ## ## 'size' 'ndims' 'numel' 'length' ## ## 'strlength' 'count' 'keyHash' ## ## ## ################################################################################ %!assert_equal (size (string), [1, 1]) %!assert_equal (size (char (string)), [0, 0]) %!assert_equal (size (char (string (''))), [0, 0]) %!assert_equal (size (string ('')), [1, 1]) %!assert_equal (size (string ([])), [0, 0]) %!assert_equal (size (string (ones (2, 0, 3, 4))), [2, 0, 3, 4]) %!assert_equal (size (string (ones (2, 4))), [2, 4]) %!assert_equal (size (string (ones (2, 4, 1, 1))), [2, 4]) %!assert_equal (size (string (ones (2, 4, 1, 0))), [2, 4, 1, 0]) %!assert_equal (size (string ('a')), [1, 1]) %!assert_equal (size (string ('abcd')), [1, 1]) %!assert_equal (size (string (char (0, 1))), [2, 1]) %!assert_equal (size (string (char (1, 0))), [2, 1]) %!assert_equal (size (string (repmat ('a', 3, 4))), [3, 1]) %!assert_equal (ndims (string), 2) %!assert_equal (ndims (string ([])), 2) %!assert_equal (ndims (string (ones (2, 0, 3, 4))), 4) %!assert_equal (ndims (string (ones (2, 4))), 2) %!assert_equal (ndims (string (zeros (6, 3))), 2) %!assert_equal (numel (string), 1) %!assert_equal (numel (string ([])), 0) %!assert_equal (numel (string (ones (2, 0, 3, 4))), 0) %!assert_equal (numel (string (ones (2, 4))), 8) %!assert_equal (numel (string (zeros (6, 3))), 18) %!assert_equal (length (string ('1')), 1) %!assert_equal (length (string ('asd')), 1) %!assert_equal (length (string (1:5)), 5) %!assert_equal (length (string (repmat ('a', 2, 3, 4))), 4) %!assert_equal (length (string (ones (5, 3, 4), 0, 0)), 5) %!assert_equal (length (string (ones (2, 10, 4), 0, 0)), 10) %!test %! str = string ({'asd', '', missing, 'a\na', "a\na"}); %! assert_equal (strlength (str), [3, 0, NaN, 4, 3]); %!assert_equal (strlength (string (char (0, 1))), [1; 1]) %!assert_equal (strlength (string (char (1, 0))), [1; 1]) %!test %! str = string ('Hello, World'); %! assert_equal (strlength (str), 12); %!test %! str = string ({'Amis', 'Chekhov', 'Joyce'; 'Stein', '', 'Proust'}); %! assert_equal (strlength (str), [4, 7, 5; 5, 0, 6]); %!assert_equal (count (string ({'paired with red shoes'}), 'red'), 2) %!test %! str = string ({'red green red red blue blue green'; ... %! 'green red blue green green blue'}); %! assert_equal (count (str, 'red'), [3; 1]); %!test %! str = string ({'red green red red blue blue green'; ... %! 'green red blue green green blue'}); %! assert_equal (count (str, string ('blue')), [2; 2]); %!test %! str = string ({'red green blue'; 'green red blue green blue'}); %! assert_equal (count (str, {'red', 'blue'}), [2; 3]); %!test %! str = string ({'Edgar Allan Poe', 'Louisa May Alcott'}); %! assert_equal (count (str, 'E', 'IgnoreCase',true), [2, 0]); %!test %! str = [string({'Andreas', 'Thomas'}), missing, string('Collin')]; %! assert_equal (count (str, 'a'), [1, 1, 0, 0]); %!test %! str = [string({'Andreas', 'Thomas'}), missing, string('Collin')]; %! assert_equal (count (str, 'a', 'IgnoreCase', true), [2, 1, 0, 0]); %!error count (string) %!error ... %! count (string ('a'), 1) %!error ... %! count (string ('a'), 'a', 'IgnoreCase', 1) %!error ... %! count (string ('a'), 'a', 'IgnoreCase', [true, true]) %!error ... %! count (string ('a'), 'a', 'whatever') %!assert_equal (keyHash (string), uint64 (11885290805827245494)) %!assert_equal (keyHash (string (1:5)), uint64 (123959226970788855)) %!assert_equal (keyHash (string ({'A', 'B'})), uint64 (10138706323013565148)) %!assert_equal (keyHash (string ({'a', 'a'})), uint64 (16542844838776277277)) %!assert_equal (keyHash (string (['A', 'B'])), uint64 (15822693213880664227)) %!assert_equal (keyHash (string (['a', 'a'])), uint64 (14201447922909418088)) %!assert_equal (keyHash (string ('AB')), uint64 (15822693213880664227)) %!assert_equal (keyHash (string ('aa')), uint64 (14201447922909418088)) %!assert_equal (keyHash (string ({'A'; 'B'})), uint64 (12731811233575238676)) %!assert_equal (keyHash (string ({'a'; 'a'})), uint64 (16082861040433618837)) %!assert_equal (keyHash (string (['A'; 'B'])), uint64 (12731811233575238676)) %!assert_equal (keyHash (string (['a'; 'a'])), uint64 (16082861040433618837)) %!assert_equal (keyHash (string ('')), uint64 (11885290805827245494)) %!assert_equal (keyHash (string ({''})), uint64 (11885290805827245494)) %!assert_equal (keyHash (string (NaN)), uint64 (11885291905338873705)) %!assert_equal (keyHash (string (missing)), uint64 (11885291905338873705)) %!assert_equal (keyHash (string ([missing, missing])), uint64 (2773158418737685269)) %!assert_equal (keyHash (string ([missing; missing])), uint64 (3067086112843206157)) %!assert_equal (keyHash (string ('1')), uint64 (15098403913664737211)) %!test %! base_key = uint64 (14695981039346656037); %! key1 = keyHash (string ('1'), base_key); %! key2 = keyHash (string ('1')); %! assert_equal (key1, key2); %!test %! base_key = uint64 (2342124352342344234); %! key = keyHash (string ('1'), base_key); %! assert_equal (key, uint64 (325711939436065646)); %!error ... %! keyHash (string (1:5), uint64 ([1, 2])) %!error ... %! keyHash (string (1:5), 2231107818551636405) ################################################################################ ## ** Query Operations ** ## ################################################################################ ## Available Methods ## ## ## ## 'contains' 'endsWith' 'startsWith' 'matches' ## ## 'iscolumn' 'isempty' 'ismatrix' 'ismember' ## ## 'ismissing' 'isrow' 'isscalar' 'issorted' ## ## 'issortedrows' 'isstring' 'isvector' ## ## ## ################################################################################ %!test %! str = string ({'Mary Ann Jones', 'Paul Jay Burns', 'John Paul Smith'}); %! TF = contains (str, 'Paul'); %! assert_equal (TF, [false, true, true]); %!test %! str = string ({'Mary Ann Jones', 'Paul Jay Burns', 'John Paul Smith'}); %! TF = contains (str, string ('Paul')); %! assert_equal (TF, [false, true, true]); %!test %! str = string ({'Mary Ann Jones', 'Paul Jay Burns', 'John Paul Smith'}); %! TF = contains (str, 'paul'); %! assert_equal (TF, [false, false, false]); %!test %! str = string ({'Mary Ann Jones', 'Paul Jay Burns', 'John Paul Smith'}); %! TF = contains (str, 'paul', 'IgnoreCase', true); %! assert_equal (TF, [false, true, true]); %!test %! str = string ({'Anne', 'Elizabeth', 'Marianne', '"Tracy'}); %! TF = contains (str, 'anne', 'IgnoreCase', true); %! assert_equal (TF, [true, false, true, false]); %!test %! str = string ({'Anne', 'Elizabeth', 'Marianne', '"Tracy'}); %! TF = contains (str, {'anne', 'Anne'}, 'IgnoreCase', false); %! assert_equal (TF, [true, false, true, false]); %!error contains (string) %!error ... %! contains (string ('a'), 1) %!error ... %! contains (string ('a'), 'a', 'IgnoreCase', 1) %!error ... %! contains (string ('a'), 'a', 'IgnoreCase', [true, true]) %!error ... %! contains (string ('a'), 'a', 'whatever') %!test %! str = string ({'a.doc', 'b.odt', 'c.doc', 'd.ods'}); %! TF = endsWith (str, 'odt'); %! assert_equal (TF, [false, true, false, false]); %!test %! str = string ({'a.doc', 'b.odt', 'c.doc', 'd.ods'}); %! TF = endsWith (str, string ('odt')); %! assert_equal (TF, [false, true, false, false]); %!test %! str = string ({'a.doc', 'b.odt', 'c.doc', 'd.ods'}); %! TF = endsWith (str, {'odt', 'ods'}); %! assert_equal (TF, [false, true, false, true]); %!test %! str = string ({'a.doc', 'b.odt', 'c.doc', 'd.ods'}); %! TF = endsWith (str, {'odt', 'ODS'}); %! assert_equal (TF, [false, true, false, false]); %!test %! str = string ({'a.doc', 'b.odt', 'c.doc', 'd.ods'}); %! TF = endsWith (str, {'odt', 'ODS'}, 'IgnoreCase', true); %! assert_equal (TF, [false, true, false, true]); %!error endsWith (string) %!error ... %! endsWith (string ('a'), 1) %!error ... %! endsWith (string ('a'), 'a', 'IgnoreCase', 1) %!error ... %! endsWith (string ('a'), 'a', 'IgnoreCase', [true, true]) %!error ... %! endsWith (string ('a'), 'a', 'whatever') %!test %! str = string ({'ball', 'base', 'Balkan', 'bison'}); %! TF = startsWith (str, 'ba'); %! assert_equal (TF, [true, true, false, false]); %!test %! str = string ({'ball', 'base', 'Balkan', 'bison'}); %! TF = startsWith (str, string ('ba')); %! assert_equal (TF, [true, true, false, false]); %!test %! str = string ({'ball', 'base', 'Balkan', 'bison'}); %! TF = startsWith (str, {'ba', 'bi'}); %! assert_equal (TF, [true, true, false, true]); %!test %! str = string ({'ball', 'base', 'Balkan', 'bison'}); %! TF = startsWith (str, {'ba', 'Bi'}); %! assert_equal (TF, [true, true, false, false]); %!test %! str = string ({'ball', 'base', 'Balkan', 'bison'}); %! TF = startsWith (str, {'ba', 'Bi'}, 'IgnoreCase', true); %! assert_equal (TF, [true, true, true, true]); %!error startsWith (string) %!error ... %! startsWith (string ('a'), 1) %!error ... %! startsWith (string ('a'), 'a', 'IgnoreCase', 1) %!error ... %! startsWith (string ('a'), 'a', 'IgnoreCase', [true, true]) %!error ... %! startsWith (string ('a'), 'a', 'whatever') %!test %! str = string ({'Mercury', 'Venus', 'Earth', 'Mars'}); %! TF = matches (str, 'Earth'); %! assert_equal (TF, [false, false, true, false]); %!test %! str = string ({'Mercury', 'Venus', 'Earth', 'Mars'}); %! TF = matches (str, string ('Earth')); %! assert_equal (TF, [false, false, true, false]); %!test %! str = string ({'Mercury', 'Venus', 'Earth', 'Mars'}); %! TF = matches (str, 'earth'); %! assert_equal (TF, [false, false, false, false]); %!test %! str = string ({'Mercury', 'Venus', 'Earth', 'Mars'}); %! TF = matches (str, 'earth', 'IgnoreCase', true); %! assert_equal (TF, [false, false, true, false]); %!test %! str = string ({'Mercury', 'Venus', 'Earth', 'Mars'}); %! TF = matches (str, {'Earth', 'Mercury'}); %! assert_equal (TF, [true, false, true, false]); %!test %! str = string ({'Mercury', 'Venus', 'Earth', 'Mars'}); %! TF = matches (str, {'earth', 'mercury'}); %! assert_equal (TF, [false, false, false, false]); %!test %! str = string ({'Mercury', 'Venus', 'Earth', 'Mars'}); %! TF = matches (str, {'earth', 'mercury'}, 'IgnoreCase', true); %! assert_equal (TF, [true, false, true, false]); %!error matches (string) %!error ... %! matches (string ('a'), 1) %!error ... %! matches (string ('a'), 'a', 'IgnoreCase', 1) %!error ... %! matches (string ('a'), 'a', 'IgnoreCase', [true, true]) %!error ... %! matches (string ('a'), 'a', 'whatever') %!assert_equal (iscolumn (string ([1, 2, 3, 4])), false) %!assert_equal (iscolumn (string ([1; 2; 3; 4])), true) %!assert_equal (iscolumn (string ([1, 2; 3, 4])), false) %!assert_equal (iscolumn (string ({'1', '2', '3', '4'})), false) %!assert_equal (iscolumn (string ({'1'; '2'; '3'; '4'})), true) %!assert_equal (iscolumn (string ({'1', '2'; '3', '4'})), false) %!assert_equal (iscolumn (string ([])), false) %!assert_equal (iscolumn (string (ones (1, 0))), false) %!assert_equal (iscolumn (string (ones (0, 1))), true) %!assert_equal (iscolumn (string (1)), true) %!assert_equal (iscolumn (string (ones (2, 3, 4))), false) %!assert_equal (isempty (string ([])), true) %!assert_equal (isempty (string ('')), false) %!assert_equal (isempty (string (ones (2, 0, 3))), true) %!assert_equal (isempty (string (ones (2, 1, 3))), false) %!assert_equal (isempty (string (1)), false) %!assert_equal (isempty (string), false) %!assert_equal (ismatrix (string), true) %!assert_equal (ismatrix (string ([])), true) %!assert_equal (ismatrix (string (ones (2, 4))), true) %!assert_equal (ismatrix (string (ones (2, 4, 1))), true) %!assert_equal (ismatrix (string (ones (2, 1, 4))), false) %!assert_equal (ismatrix (string (ones (2, 3, 4))), false) %!assert_equal (ismatrix (string (ones (0, 1))), true) %!assert_equal (ismatrix (string (ones (1, 0))), true) %!assert_equal (ismatrix (string (char (0, 1))), true) %!assert_equal (ismatrix (string (char (1, 0))), true) %!test %! A = string ({'dog', 'cat', 'fish', 'horse'}); %! B = string ({'dog ', 'cat', 'fish ', 'horse'}); %! [TF, index] = ismember (A, B); %! assert_equal (TF, [false, true, false, true]); %! assert_equal (index, [0, 2, 0, 4]); %!test %! A = string ({'dog', 'cat', 'fish', 'horse'}); %! B = ['dog';'cat']; %! [TF, index] = ismember (A, B); %! assert_equal (TF, [true, true, false, false]); %! assert_equal (index, [1, 2, 0, 0]); %! [TF, index] = ismember (B, A); %! assert_equal (TF, [true; true]); %! assert_equal (index, [1; 2]); %!test %! A = string ({'dog', 'cat'; 'fish', 'horse'}); %! B = string ({'dog ', 'cat'}); %! [TF, index] = ismember (A, B); %! assert_equal (TF, [false, true; false, false]); %! assert_equal (index, [0, 2; 0, 0]); %! [TF, index] = ismember (A, B, 'rows'); %! assert_equal (TF, [false; false]); %! assert_equal (index, [0; 0]); %!test %! A = string ({'dog', 'cat'; 'fish', 'horse'}); %! B = string ({'dog', 'cat'}); %! [TF, index] = ismember (A, B); %! assert_equal (TF, [true, true; false, false]); %! assert_equal (index, [1, 2; 0, 0]); %! [TF, index] = ismember (A, B, 'rows'); %! assert_equal (TF, [true; false]); %! assert_equal (index, [1; 0]); %!error ismember (1, string) %!error ismember (string, 1) %!error ... %! ismember (string (1), string ([1, 2]), 'rows') %!assert_equal (ismissing (string ('a'), 'a'), true) %!assert_equal (ismissing (string ('a'), string ({'a', 'c'})), true) %!assert_equal (ismissing (string ({'a', 'b'}), string ({'a', 'c'})), [true, false]) %!test %! TF = ismissing (string ({'a', 'b', ''}), string ({'a', 'c'})); %! assert_equal (TF, logical ([1, 0, 0])); %!test %! TF = ismissing (string ({'a', 'b', ''}), string ({'a'; 'c'})); %! assert_equal (TF, logical ([1, 0, 0])); %!test %! TF = ismissing (string ({'a'; 'b'; ''}), string ({'a', 'c'})); %! assert_equal (TF, logical ([1; 0; 0])); %!test %! TF = ismissing ([string({'a', 'b', ''}), missing], string ({'a', 'c'})); %! assert_equal (TF, logical ([1, 0, 0, 0])); %!error ... %! ismissing (string, string, string) %!error ... %! ismissing (string, 1) %!error ... %! ismissing (string, {1}) %!error ... %! ismissing (string, string (ones (2))) %!assert_equal (isrow (string ([1, 2, 3, 4])), true) %!assert_equal (isrow (string ([1; 2; 3; 4])), false) %!assert_equal (isrow (string ([1, 2; 3, 4])), false) %!assert_equal (isrow (string ([])), false) %!assert_equal (isrow (string ()), true) %!assert_equal (isrow (string (ones (1, 0))), true) %!assert_equal (isrow (string (ones (0, 1))), false) %!assert_equal (isrow (string (1)), true) %!assert_equal (isrow (string (ones (2, 3, 4))), false) %!assert_equal (isscalar (string ([])), false) %!assert_equal (isscalar (string ()), true) %!assert_equal (isscalar (string (ones (1, 0))), false) %!assert_equal (isscalar (string (ones (0, 1))), false) %!assert_equal (isscalar (string (1)), true) %!assert_equal (isscalar (string (NaN)), true) %!assert_equal (isscalar (string (missing)), true) %!assert_equal (isscalar (string ([1, 2])), false) %!test ## issorted, verified against MATLAB R2026a %! assert_equal (issorted (string ({'a', 'b', 'c'})), true); %! assert_equal (issorted (string ({'a', 'b', 'c'}), 'strictascend'), true); %! assert_equal (issorted (string ({'a', 'a', 'b'}), 'strictascend'), false); %! assert_equal (issorted (string ({'c', 'b', 'a'}), 'descend'), true); %! assert_equal (issorted (string ({'a', 'b', 'c'}), 'monotonic'), true); %! assert_equal (issorted (string ({'b', 'a', 'c'}), 'monotonic'), false); %! assert_equal (issorted ([string('a') string('b') missing], 'ascend'), true); %! assert_equal (issorted ([string('a') string('b') missing], 'strictascend'), false); %!error ... %! issorted (string ({'a', 'b'}), 'MissingPlacement', 'bad') %!error ... %! issorted (string ({'a', 'b'}), 'asd') %!test ## strict issortedrows semantics, verified against MATLAB R2026a %! assert_equal (issortedrows (string ({'a', 'x'; 'a', 'y'}), 'strictascend'), false); %! assert_equal (issortedrows (string ({'a', 'x'; 'a', 'x'}), 'strictascend'), false); %! assert_equal (issortedrows (string ({'a', 'p'; 'a', 'q'}), ... %! {'strictascend', 'ascend'}), false); %! assert_equal (issortedrows (string ({'a', 'p'; 'b', 'p'}), ... %! {'ascend', 'strictascend'}), true); %! assert_equal (issortedrows (string ({'b', 'z'; 'a', 'y'}), 'monotonic'), true); %! assert_equal (issortedrows (string ({'b', 'z'; 'a', 'y'}), 'descend'), true); %! assert_equal (issortedrows (string ({'a', 'q'; 'b', 'p'}), 2, ... %! 'strictascend'), false); %! assert_equal (issortedrows (string ({'b', 'p'; 'a', 'q'}), 2, ... %! 'strictascend'), true); %!test ## strict issortedrows with missing values (vs MATLAB R2026a) %! assert_equal (issortedrows ([string('a') string('p'); missing string('q')], ... %! {'strictascend', 'ascend'}), false); %! assert_equal (issortedrows ([string('a') string('p'); missing string('q')], ... %! {'ascend', 'strictascend'}), true); %! assert_equal (issortedrows ([missing string('p') string('z'); ... %! string('x') string('q') string('y')], ... %! 2, 'strictascend'), true); %! assert_equal (issortedrows ([string('a') missing; string('b') string('y')], ... %! 'strictascend'), true); %!error ... %! issortedrows (string ({'a', 'b'}), {'asd'}) %!error ... %! issortedrows (string ({'a', 'b'; 'c', 'd'}), [1, 2], ... %! {'strictascend', 'ascend', 'descend'}) %!assert_equal (isstring (string), true) %!assert_equal (isstring (cellstr (string)), false) %!assert_equal (isvector (string ([1, 2, 3, 4])), true) %!assert_equal (isvector (string ([1; 2; 3; 4])), true) %!assert_equal (isvector (string ([1, 2; 3, 4])), false) %!assert_equal (isvector (string ([])), false) %!assert_equal (isvector (string ()), true) %!assert_equal (isvector (string ()), true) %!assert_equal (isvector (string (ones (1, 0))), true) %!assert_equal (isvector (string (ones (0, 1))), true) %!assert_equal (isvector (string (1)), true) %!assert_equal (isvector (string (ones (2, 3, 4))), false) ################################################################################ ## ** Relational Operations ** ## ################################################################################ ## Available Methods ## ## ## ## 'eq' 'ge' 'gt' 'le' ## ## 'lt' 'ne' 'strcmp' 'strcmpi' ## ## 'strncmp' 'strncmpi' ## ## ## ################################################################################ %!assert_equal (eq (string ('A'), string ('A')), true); %!assert_equal (eq (string ('A'), string ('b')), false); %!assert_equal (eq (string ('A'), {'A', 'b'}), [true, false]); %!assert_equal (eq ({'A', 'b'}, string ('A')), [true, false]); %!assert_equal (eq (string ({'A', 'b'}), 'A'), [true, false]); %!assert_equal (eq ('A', string ({"A", "b"})), [true, false]); %!assert_equal (eq (string ({'A', 'B'}), string ({'A'; 'B'})), logical (eye (2))) %!error ... %! eq (string ('A'), 2) %!error ... %! NaN == string ('A') %!error ... %! eq (string ({'A', 'B', 'A'; 'A', 'D', 'F'}), string ({'A', 'B'})) %!assert_equal (ge (string ('A'), string ('A')), true); %!assert_equal (ge (string ('A'), string ('b')), false); %!assert_equal (ge (string ('b'), {'A', 'b'}), [true, true]); %!assert_equal (ge ({'A', 'b'}, string ('b')), [false, true]); %!assert_equal (ge (string ({'A', 'b'}), 'A'), [true, true]); %!assert_equal (ge ('A', string ({'A', 'b'})), [true, false]); %!assert_equal (ge (string ({'A', 'B'}), string ({'A'; 'B'})), logical ([1, 1; 0, 1])) %!error ... %! ge (string ('A'), 2) %!error ... %! NaN >= string ('A') %!error ... %! ge (string ({'A', 'B', 'A'; 'A', 'D', 'F'}), string ({'A', 'B'})) %!assert_equal (gt (string ('A'), string ('A')), false); %!assert_equal (gt (string ('A'), string ('b')), false); %!assert_equal (gt (string ('b'), {'A', 'b'}), [true, false]); %!assert_equal (gt ({'A', 'b'}, string ('b')), [false, false]); %!assert_equal (gt (string ({'A', 'b'}), 'A'), [false, true]); %!assert_equal (gt ('A', string ({'A', 'b'})), [false, false]); %!assert_equal (gt (string ({'A', 'B'}), string ({'A'; 'B'})), logical ([0, 1; 0, 0])) %!error ... %! gt (string ('A'), 2) %!error ... %! NaN > string ('A') %!error ... %! gt (string ({'A', 'B', 'A'; 'A', 'D', 'F'}), string ({'A', 'B'})) %!assert_equal (le (string ('A'), string ('A')), true); %!assert_equal (le (string ('A'), string ('b')), true); %!assert_equal (le (string ('b'), {'A', 'b'}), [false, true]); %!assert_equal (le ({'A', 'b'}, string ('b')), [true, true]); %!assert_equal (le (string ({'A', 'b'}), 'A'), [true, false]); %!assert_equal (le ('A', string ({'A', 'b'})), [true, true]); %!assert_equal (le (string ({'A', 'B'}), string ({'A'; 'B'})), logical ([1, 0; 1, 1])) %!error ... %! le (string ('A'), 2) %!error ... %! NaN <= string ('A') %!error ... %! le (string ({'A', 'B', 'A'; 'A', 'D', 'F'}), string ({'A', 'B'})) %!assert_equal (lt (string ('A'), string ('A')), false); %!assert_equal (lt (string ('A'), string ('b')), true); %!assert_equal (lt (string ('b'), {'A', 'b'}), [false, false]); %!assert_equal (lt ({'A', 'b'}, string ("b")), [true, false]); %!assert_equal (lt (string ({'A', 'b'}), 'A'), [false, false]); %!assert_equal (lt ('A', string ({'A', 'b'})), [false, true]); %!assert_equal (lt (string ({'A', 'B'}), string ({'A'; 'B'})), logical ([0, 0; 1, 0])) %!error ... %! lt (string ('A'), 2) %!error ... %! NaN < string ('A') %!error ... %! lt (string ({'A', 'B', 'A'; 'A', 'D', 'F'}), string ({'A', 'B'})) %!assert_equal (ne (string ('A'), string ('A')), false); %!assert_equal (ne (string ('A'), string ('b')), true); %!assert_equal (ne (string ('A'), {'A', 'b'}), [false, true]); %!assert_equal (ne ({'A', 'b'}, string ('A')), [false, true]); %!assert_equal (ne (string ({'A', 'b'}), 'A'), [false, true]); %!assert_equal (ne ('A', string ({'A', 'b'})), [false, true]); %!assert_equal (ne (string ({'A', 'B'}), string ({'A'; 'B'})), logical ([0, 1; 1, 0])) %!error ... %! ne (string ('A'), 2) %!error ... %! NaN != string ('A') %!error ... %! ne (string ({'A', 'B', 'A'; 'A', 'D', 'F'}), string ({'A', 'B'})) %!assert_equal (strcmp (string ("A"), string ("A")), true); %!assert_equal (strcmp (string ("A"), string ("b")), false); %!assert_equal (strcmp (string ("A"), {"A", "b"}), [true, false]); %!assert_equal (strcmp ({"A", "b"}, string ("A")), [true, false]); %!assert_equal (strcmp (string ({'A', 'b'}), 'A'), [true, false]); %!assert_equal (strcmp ('A', string ({"A", "b"})), [true, false]); %!error ... %! strcmp (string ("A"), 2) %!error ... %! strcmp (2, string ("A")) %!error ... %! strcmp (string ({"A","B"}), string ({"A";"B"})) %!assert_equal (strcmpi (string ("A"), string ("a")), true); %!assert_equal (strcmpi (string ("A"), string ("b")), false); %!assert_equal (strcmpi (string ("a"), {"A", "b"}), [true, false]); %!assert_equal (strcmpi ({"A", "b"}, string ("a")), [true, false]); %!assert_equal (strcmpi (string ({'a', 'b'}), 'A'), [true, false]); %!assert_equal (strcmpi ('A', string ({"a", "b"})), [true, false]); %!error ... %! strcmpi (string ("A"), 2) %!error ... %! strcmpi (2, string ("A")) %!error ... %! strcmpi (string ({"A","B"}), string ({"a";"b"})) %!assert_equal (strncmp (string ("ASDFG"), "ASDER", 3), true); %!assert_equal (strncmp (string ("ASDFG"), "ASDER", 4), false); %!assert_equal (strncmp (string ("ASDFG"), {"ASDER","ASFGH"}, 3), [true, false]); %!assert_equal (strncmp (string ("ASDFG"), {"ASDER","ASFGH"}, 2), [true, true]); %!error ... %! strncmp (string ("A"), 2) %!error ... %! strncmp (2, string ("A")) %!error ... %! strncmp (string ({"A","B"}), string ({"a";"b"}), 1) %!assert_equal (strncmpi (string ("asDFG"), "ASDER", 3), true); %!assert_equal (strncmpi (string ("ASDFG"), "asDER", 4), false); %!assert_equal (strncmpi (string ("asdfg"), {"ASDER","ASFGH"}, 3), [true, false]); %!assert_equal (strncmpi (string ("asdfg"), {"ASDER","ASFGH"}, 2), [true, true]); %!error ... %! strncmpi (string ("A"), 2) %!error ... %! strncmpi (2, string ("A")) %!error ... %! strncmpi (string ({"A","B"}), string ({"a";"b"}), 1) ################################################################################ ## ** String Operations ** ## ################################################################################ ## Available Methods ## ## ## ## 'append' 'compose' 'erase' 'eraseBetween' ## ## 'extract' 'extractAfter' 'extractBefore' 'extractBetween' ## ## 'insertAfter' 'insertBefore' 'replace' 'replaceBetween' ## ## 'reverse' 'sort' 'sortrows' 'split' ## ## 'splitlines' 'strcat' 'strip' 'pad' ## ## 'join' 'plus' 'lower' 'upper' ## ## 'unique' ## ## ## ################################################################################ ## append: concatenates corresponding elements of its inputs %!assert_equal (append (string ('Hello'), string (' World')), string ('Hello World')) %!assert_equal (append (string ('a'), string ('b'), string ('c')), string ('abc')) ## append: preserves trailing whitespace (unlike 'strcat') %!assert_equal (append (string ('a '), string ('b')), string ('a b')) ## append: char vectors are one element, cellstr arguments are accepted %!assert_equal (append (string ('x'), '_', string ('y')), string ('x_y')) %!assert_equal (append (string ('x'), {'_'}, string ('y')), string ('x_y')) ## append: char matrices contribute one element per row (whitespace preserved) %!assert_equal (append (string ({'1'; '2'}), char ('a', 'bb')), string ({'1a '; '2bb'})) ## append: element-wise over equal-sized arrays %!assert_equal (append (string ({'a'; 'b'}), string ({'1'; '2'})), string ({'a1'; 'b2'})) ## append: scalar expands against a non-scalar argument %!assert_equal (append (string ('p_'), string ({'a'; 'b'; 'c'})), ... %! string ({'p_a'; 'p_b'; 'p_c'})) ## append: implicit expansion of a row and a column yields a matrix %!assert_equal (append (string ({'a', 'b'}), string ({'1'; '2'})), ... %! string ({'a1', 'b1'; 'a2', 'b2'})) ## append: a single argument is returned unchanged %!assert_equal (append (string ({'a'; 'b'})), string ({'a'; 'b'})) ## append: a missing value propagates like NaN (any missing input -> missing), ## and a missing element carries no text %!test %! str = append (string ('a'), string (NaN)); %! assert_equal (ismissing (str), true); %! assert_equal (cellstr (str), {''}); %!test %! str = append (string ({'a'; 'b'}), string ({missing; 'z'})); %! assert_equal (ismissing (str), [true; false]); %! assert_equal (cellstr (str), {''; 'bz'}); ## append: error branches %!error ... %! append (string ('a'), 5) %!error ... %! append (string ({'a'; 'b'; 'c'}), string ({'x'; 'y'})) ## compose: formatting syntax -- single operator %!assert_equal (compose (string ('%d apples'), 3), string ('3 apples')) %!assert_equal (compose (string ('%d'), [1 2 3]), string ({'1', '2', '3'})) %!assert_equal (compose (string ('%d'), [1; 2; 3]), string ({'1'; '2'; '3'})) %!assert_equal (compose (string ('%d apples'), [1; 2; 3]), ... %! string ({'1 apples'; '2 apples'; '3 apples'})) ## compose: formatting syntax -- multiple operators per row %!assert_equal (compose (string ('%d + %d = %d'), [1 2 3]), string ('1 + 2 = 3')) %!assert_equal (compose (string ('%d %d %d'), [1 2 3; 4 5 6]), ... %! string ({'1 2 3'; '4 5 6'})) ## compose: FORMATSPEC repeats when A has more columns than operators %!assert_equal (compose (string ('%d-%d '), [1 2 3 4]), string ({'1-2 ', '3-4 '})) ## compose: leftover operators are emitted unchanged %!assert_equal (compose (string ('%d and %d'), 5), string ('5 and %d')) ## compose: multiple input arrays consumed left to right %!assert_equal (compose (string ('%s = %d'), string ('a'), 1), string ('a = 1')) %!assert_equal (compose (string ('%s=%d'), string ({'a'; 'b'}), [1; 2]), ... %! string ({'a=1'; 'b=2'})) ## compose: scalar input expands against a column vector %!assert_equal (compose (string ('%s%d'), string ('row'), [1; 2; 3]), ... %! string ({'row1'; 'row2'; 'row3'})) ## compose: accepted input types %!assert_equal (compose (string ('%s!'), 'hi'), string ('hi!')) %!assert_equal (compose (string ('%s!'), string ('hi')), string ('hi!')) %!assert_equal (compose (string ('%d'), true), string ('1')) %!assert_equal (compose (string ('%5.2f'), pi), string (' 3.14')) ## compose: '*' dynamic field width consumes an extra value %!assert_equal (compose (string ('%*d'), [5 42]), string (' 42')) ## compose: literal '%%' becomes a single '%' %!assert_equal (compose (string ('%d%% done'), 50), string ('50% done')) ## compose: escape sequences in literal text of FORMATSPEC are translated %!assert_equal (compose (string ('Line %d\n'), [1; 2]), ... %! string ({sprintf('Line 1\n'); sprintf('Line 2\n')})) ## compose: zero-column input preserves size and applies no formatting %!test %! out = compose (string ('%d'), zeros (2, 0)); %! assert_equal (class (out), 'string'); %! assert_equal (size (out), [2, 0]); ## compose: escape-translation syntax -- compose (TXT) %!assert_equal (compose (string ('a\tb\nc')), string (sprintf ('a\tb\nc'))) %!assert_equal (compose (string ('100%% done')), string ('100% done')) ## compose (TXT) leaves formatting operators unchanged %!assert_equal (compose (string ('val=%d\n')), string (sprintf ('val=%%d\n'))) ## compose (TXT) preserves size over a multi-element array %!test %! out = compose (string ({'a\tb'; 'c'; 'd\ne'})); %! assert_equal (size (out), [3, 1]); %! assert_equal (cellstr (out), {sprintf('a\tb'); 'c'; sprintf('d\ne')}); ## compose: error branches %!error ... %! compose (string ({'%d', '%d'}), 1) %!error ... %! compose (string (NaN), 1) %!error ... %! compose (string ('%d'), {1, 2}) %!error ... %! compose (string ('%d%d'), [1; 2; 3], [1; 2]) ## erase: removes every occurrence of a single match from each element %!assert_equal (erase (string ('the quick brown fox'), 'o'), ... %! string ('the quick brwn fx')) %!assert_equal (erase (string ('Hello World'), ' World'), string ('Hello')) ## erase: a match that does not occur leaves the text unchanged %!assert_equal (erase (string ('abc'), 'z'), string ('abc')) ## erase: an empty match removes nothing %!assert_equal (erase (string ('abc'), ''), string ('abc')) ## erase: a char-vector match keeps trailing whitespace (bare cellstr deblanks) %!assert_equal (erase (string ('the quick brown'), 'quick '), string ('the brown')) ## erase: accepts char vectors, cell arrays of char vectors, and string scalars %!assert_equal (erase (string ('abcabc'), string ('bc')), string ('aa')) %!assert_equal (erase (string ('aXa'), {'X'}), string ('aa')) ## erase: applied element-wise across an array, preserving its size %!assert_equal (erase (string ({'aXa', 'bXb'}), 'X'), string ({'aa', 'bb'})) %!assert_equal (erase (string ({'a.b'; 'c.d'}), '.'), string ({'ab'; 'cd'})) ## erase: a multi-element match deletes every occurrence of every element %!assert_equal (erase (string ('The quick brown fox'), string ({'The ', 'quick '})), ... %! string ('brown fox')) %!assert_equal (erase (string ('aXbYc'), {'X', 'Y'}), string ('abc')) ## erase: MATCH size need not match STR size (every element is removed from all) %!assert_equal (erase (string ({'aXbYc', 'XYZ'}), string ({'X', 'Y'})), ... %! string ({'abc', 'Z'})) ## erase: erasing the entire content yields an empty, non-missing string %!test %! str = erase (string ('abc'), 'abc'); %! assert_equal (str, string ('')); %! assert_equal (ismissing (str), false); ## erase: missing values are preserved and carry no text %!test %! str = erase (string ({'a.b.c', missing}), '.'); %! assert_equal (ismissing (str), [false, true]); %! assert_equal (cellstr (str), {'abc', ''}); ## erase: error branch %!error ... %! erase (string ('ab'), 5) ## eraseBetween: pattern syntax erases between boundaries (default exclusive) %!assert_equal (eraseBetween (string ('The quick brown fox'), 'quick', ' fox'), ... %! string ('The quick fox')) ## eraseBetween: pattern syntax with explicit exclusive boundaries %!assert_equal (eraseBetween (string ('a[xyz]b'), '[', ']', 'Boundaries', 'exclusive'), ... %! string ('a[]b')) ## eraseBetween: pattern syntax with inclusive boundaries erases them too %!assert_equal (eraseBetween (string ('The quick brown fox jumps over the lazy dog'), ... %! ' brown', 'lazy', 'Boundaries', 'inclusive'), ... %! string ('The quick dog')) %!assert_equal (eraseBetween (string ('a[xyz]b'), '[', ']', 'Boundaries', 'inclusive'), ... %! string ('ab')) ## eraseBetween: char boundaries keep trailing whitespace (cellstr deblanks) %!assert_equal (eraseBetween (string ('a quick b'), 'a ', ' b', ... %! 'Boundaries', 'inclusive'), string ('')) ## eraseBetween: boundary substrings accepted as char, cellstr, or string %!assert_equal (eraseBetween (string ('ab'), '<', '>'), string ('a<>b')) %!assert_equal (eraseBetween (string ('ab'), {'<'}, {'>'}), string ('a<>b')) %!assert_equal (eraseBetween (string ('ab'), string ('<'), string ('>')), ... %! string ('a<>b')) ## eraseBetween: a missing boundary leaves the element unchanged %!assert_equal (eraseBetween (string ('hello'), 'X', 'o'), string ('hello')) %!assert_equal (eraseBetween (string ('hello'), 'h', 'X'), string ('hello')) ## eraseBetween: the end boundary must follow the start match %!assert_equal (eraseBetween (string ('a|b|c'), 'b', 'a'), string ('a|b|c')) ## eraseBetween: a scalar boundary pair broadcasts over an array %!assert_equal (eraseBetween (string ({'1', '2'}), '<', '>', ... %! 'Boundaries', 'inclusive'), string ({'1', '2'})) ## eraseBetween: boundary pairs given element-wise, preserving size %!assert_equal (eraseBetween (string ({'aXbYc'; '1P2Q3'}), string ({'X'; 'P'}), ... %! string ({'Y'; 'Q'})), string ({'aXYc'; '1PQ3'})) ## eraseBetween: position syntax erases between positions (default inclusive) %!assert_equal (eraseBetween (string ('Edgar Allen Poe'), 6, 11), string ('Edgar Poe')) %!assert_equal (eraseBetween (string ('mushrooms, peppers, and onions'), 10, 19), ... %! string ('mushrooms and onions')) ## eraseBetween: position syntax with exclusive boundaries keeps the endpoints %!assert_equal (eraseBetween (string ('small|medium|large'), 6, 13, ... %! 'Boundaries', 'exclusive'), string ('small||large')) ## eraseBetween: equal positions erase a single character (inclusive) or none %!assert_equal (eraseBetween (string ('abcde'), 3, 3), string ('abde')) %!assert_equal (eraseBetween (string ('abcde'), 3, 3, 'Boundaries', 'exclusive'), ... %! string ('abcde')) ## eraseBetween: scalar positions broadcast; array positions map by element %!assert_equal (eraseBetween (string ({'abcdef'; 'ABCDEF'}), 2, 4), ... %! string ({'aef'; 'AEF'})) %!assert_equal (eraseBetween (string ({'abcdef'; 'ABCDEF'}), [2; 3], [4; 5]), ... %! string ({'aef'; 'ABF'})) ## eraseBetween: 'Boundaries' name and string option are case-insensitive %!assert_equal (eraseBetween (string ('small|medium|large'), 6, 13, ... %! 'boundaries', string ('exclusive')), string ('small||large')) ## eraseBetween: missing values are preserved and carry no text %!test %! str = eraseBetween (string ({'a[x]b', missing}), '[', ']', 'Boundaries', 'inclusive'); %! assert_equal (ismissing (str), [false, true]); %! assert_equal (cellstr (str), {'ab', ''}); ## eraseBetween: error branches %!error ... %! eraseBetween (string ('ab'), 1) %!error ... %! eraseBetween (string ('ab'), 1, 'x') %!error ... %! eraseBetween (string ('ab'), 'a', 'b', 'Boundaries', 'foo') %!error ... %! eraseBetween (string ('ab'), 1, 2, 'Foo', 'bar') %!error ... %! eraseBetween (string ('abc'), 2, 9) %!error ... %! eraseBetween (string ('abc'), 0, 2) %!error ... %! eraseBetween (string ('abc'), 1.5, 2) %!error ... %! eraseBetween (string ('abcde'), 4, 2) %!error ... %! eraseBetween (string ({'a', 'b', 'c'}), [1, 2], [1, 2]) %!error ... %! eraseBetween (string ({'a', 'b', 'c'}), 1, [1, 2]) ## extract: pattern form returns each match of PAT along the columns %!assert_equal (extract (string ('xax ax'), 'ax'), string ({'ax', 'ax'})) %!assert_equal (extract (string ('hello world'), 'o'), string ({'o', 'o'})) ## extract: a multi-element PAT matches any alternative, first listed winning %!assert_equal (extract (string ('cabbage'), {'a', 'b'}), ... %! string ({'a', 'b', 'b', 'a'})) %!assert_equal (extract (string ('ababab'), string ({'ab', 'a'})), ... %! string ({'ab', 'ab', 'ab'})) ## extract: matches do not overlap and are taken left to right %!assert_equal (extract (string ('aaaa'), 'aa'), string ({'aa', 'aa'})) ## extract: a scalar with one match yields a 1x1 string %!assert_equal (extract (string ('a-b'), '-'), string ('-')) ## extract: no match yields an empty string array %!test %! str = extract (string ('hello'), 'z'); %! assert_equal (class (str), 'string'); %! assert_equal (size (str), [1, 0]); ## extract: a non-scalar STR gives one row per element and one column per match %!assert_equal (extract (string ({'p.q.r'; 'x.y.z'}), '.'), ... %! string ({'.', '.'; '.', '.'})) ## extract: elements that do not match are treated as having zero matches %!test %! str = extract (string ({'ab', missing}), '.'); %! assert_equal (class (str), 'string'); %! assert_equal (size (str), [2, 0]); ## extract: position form returns the single character at POS %!assert_equal (extract (string ("All's well that ends well"), 1), string ('A')) %!assert_equal (extract (string ("All's well that ends well"), 25), string ('l')) ## extract: position form is element-wise and preserves the size of STR %!assert_equal (extract (string ({'abc'; 'xyz'}), [1; 3]), string ({'a'; 'z'})) ## extract: a scalar position broadcasts over the array %!assert_equal (extract (string ({'abc', 'xyz'}), 2), string ({'b', 'y'})) ## extract: position form preserves missing values %!test %! str = extract (string ({'abc', missing}), 1); %! assert_equal (ismissing (str), [false, true]); %! assert_equal (cellstr (str), {'a', ''}); ## extract: error branches %!error ... %! extract (string ('ab')) %!error ... %! extract (string ('ab'), {1, 2}) %!error ... %! extract (string ({'a.b'; 'c.d.e'}), '.') %!error ... %! extract (string ({'a', 'b', 'c'}), [1, 2]) %!error ... %! extract (string ('ab'), 0) %!error ... %! extract (string ('ab'), 1.5) %!error ... %! extract (string ('ab'), 5) ## extractAfter: pattern form returns the text after the first match of PAT %!assert_equal (extractAfter (string ('The quick brown fox'), 'quick '), ... %! string ('brown fox')) %!assert_equal (extractAfter (string ('a-b-c'), '-'), string ('b-c')) ## extractAfter: boundary pattern given per element %!assert_equal (extractAfter (string ({'The quick brown fox jumps'; 'over the lazy dog'}), ... %! string ({'quick '; 'the '})), ... %! string ({'brown fox jumps'; 'lazy dog'})) ## extractAfter: a scalar pattern broadcasts over the array %!assert_equal (extractAfter (string ({'a:1', 'a:2'}), 'a:'), string ({'1', '2'})) ## extractAfter: a pattern that is not found yields a missing value %!test %! str = extractAfter (string ('hello'), 'z'); %! assert_equal (ismissing (str), true); ## extractAfter: position form returns the text after POS %!assert_equal (extractAfter (string ('Edgar Allen Poe'), 12), string ('Poe')) %!assert_equal (extractAfter (string ({'abcd'; 'wxyz'}), [1; 2]), string ({'bcd'; 'yz'})) ## extractAfter: POS at the last character yields an empty string %!assert_equal (extractAfter (string ('abc'), 3), string ('')) ## extractAfter: missing values are preserved %!test %! str = extractAfter (string ({'a-b', missing}), '-'); %! assert_equal (ismissing (str), [false, true]); %! assert_equal (cellstr (str), {'b', ''}); ## extractAfter: error branches %!error ... %! extractAfter (string ('ab')) %!error ... %! extractAfter (string ('ab'), {1, 2}) %!error ... %! extractAfter (string ('ab'), string ({'a', 'b'})) %!error ... %! extractAfter (string ('ab'), 0) %!error ... %! extractAfter (string ('ab'), 5) ## extractBefore: pattern form returns the text before the first match of PAT %!assert_equal (extractBefore (string ('The quick brown fox'), ' brown'), ... %! string ('The quick')) %!assert_equal (extractBefore (string ('a-b-c'), '-'), string ('a')) ## extractBefore: a scalar pattern broadcasts over the array %!assert_equal (extractBefore (string ({'1:a', '2:a'}), ':a'), string ({'1', '2'})) ## extractBefore: a pattern that is not found yields a missing value %!test %! str = extractBefore (string ('hello'), 'z'); %! assert_equal (ismissing (str), true); ## extractBefore: position form returns the text before POS %!assert_equal (extractBefore (string ('Edgar Allen Poe'), 6), string ('Edgar')) %!assert_equal (extractBefore (string ({'abcd'; 'wxyz'}), [3; 2]), string ({'ab'; 'w'})) ## extractBefore: POS at the first character yields an empty string %!assert_equal (extractBefore (string ('abc'), 1), string ('')) ## extractBefore: missing values are preserved %!test %! str = extractBefore (string ({'a-b', missing}), '-'); %! assert_equal (ismissing (str), [false, true]); %! assert_equal (cellstr (str), {'a', ''}); ## extractBefore: error branches %!error ... %! extractBefore (string ('ab')) %!error ... %! extractBefore (string ('ab'), {1, 2}) %!error ... %! extractBefore (string ('ab'), 5) ## extractBetween: pattern form returns the text between boundaries (exclusive) %!assert_equal (extractBetween (string ('The quick brown fox'), 'quick ', ' fox'), ... %! string ('brown')) ## extractBetween: pattern form with inclusive boundaries keeps the boundaries %!assert_equal (extractBetween (string ('The quick brown fox jumps'), ' brown', ... %! 'fox', 'Boundaries', 'inclusive'), string (' brown fox')) ## extractBetween: position form returns the span (inclusive by default) %!assert_equal (extractBetween (string ('Edgar Allen Poe'), 7, 11), string ('Allen')) ## extractBetween: position form with exclusive boundaries drops the endpoints %!assert_equal (extractBetween (string ('small|medium|large'), 6, 13, ... %! 'Boundaries', 'exclusive'), string ('medium')) ## extractBetween: equal positions extract a single character (inclusive) %!assert_equal (extractBetween (string ('abcde'), 3, 3), string ('c')) ## extractBetween: a scalar string with several matches gives a 1xN row %!assert_equal (extractBetween (string ('a[1][2][3]b'), '[', ']'), ... %! string ({'1', '2', '3'})) ## extractBetween: one match per element preserves the size of STR %!assert_equal (extractBetween (string ({''; ''}), '<', '>'), ... %! string ({'a'; 'bb'})) %!assert_equal (extractBetween (string ({'(x)'; '(yy)'}), '(', ')'), ... %! string ({'x'; 'yy'})) ## extractBetween: no match yields an empty string array %!test %! str = extractBetween (string ('hello'), '[', ']'); %! assert_equal (class (str), 'string'); %! assert_equal (size (str), [1, 0]); ## extractBetween: 'Boundaries' name and string option are case-insensitive %!assert_equal (extractBetween (string ('small|medium|large'), 6, 13, ... %! 'boundaries', string ('exclusive')), string ('medium')) ## extractBetween: missing values are preserved (position form) %!test %! str = extractBetween (string ({'abcd', missing}), 2, 3); %! assert_equal (ismissing (str), [false, true]); %! assert_equal (cellstr (str), {'bc', ''}); ## extractBetween: error branches %!error ... %! extractBetween (string ('ab'), 1) %!error ... %! extractBetween (string ('ab'), 1, 'x') %!error ... %! extractBetween (string ('ab'), 'a', 'b', 'Boundaries', 'foo') %!error ... %! extractBetween (string ('ab'), 1, 2, 'Foo', 'bar') %!error ... %! extractBetween (string ({'[1]'; '[1][2]'}), '[', ']') %!error ... %! extractBetween (string ('abc'), 2, 9) %!error ... %! extractBetween (string ({'a', 'b', 'c'}), 1, [1, 2]) ## Code-point positions: numeric indexing counts whole characters, not UTF-8 ## bytes. The test text "abc" has 5 code points spanning ## 1-, 3-, and 4-byte UTF-8 sequences (built from raw bytes so this file stays ## ASCII): a(1) euro(2) b(3) emoji(4) c(5). %!test %! s = string (native2unicode (uint8 ([97 226 130 172 98 240 159 152 128 99]), 'UTF-8')); %! euro = string (native2unicode (uint8 ([226 130 172]), 'UTF-8')); %! emoji = string (native2unicode (uint8 ([240 159 152 128]), 'UTF-8')); %! ## extract: the 4th character is the whole emoji, not a stray byte %! assert_equal (extract (s, 4), emoji); %! assert_equal (extract (s, 2), euro); %! ## extractBefore / extractAfter split on character boundaries %! assert_equal (extractBefore (s, 4), append (string ('a'), euro, string ('b'))); %! assert_equal (extractAfter (s, 2), append (string ('b'), emoji, string ('c'))); %! ## eraseBetween (inclusive removes chars 2..4; exclusive removes char 3) %! assert_equal (eraseBetween (s, 2, 4), string ('ac')); %! assert_equal (eraseBetween (s, 2, 4, 'Boundaries', 'exclusive'), ... %! append (string ('a'), euro, emoji, string ('c'))); %! ## extractBetween (inclusive keeps chars 2..4; exclusive keeps char 3) %! assert_equal (extractBetween (s, 2, 4), append (euro, string ('b'), emoji)); %! assert_equal (extractBetween (s, 2, 4, 'Boundaries', 'exclusive'), string ('b')); ## insertAfter: pattern form inserts after the match (single occurrence) %!assert_equal (insertAfter (string ('The quick fox'), 'quick', ' brown'), ... %! string ('The quick brown fox')) ## insertAfter: pattern form inserts after EVERY non-overlapping occurrence %!assert_equal (insertAfter (string ('a-b-c'), '-', 'X'), string ('a-Xb-Xc')) ## insertAfter: boundary pattern and NEW given per element %!assert_equal (insertAfter (string ({'foo'; 'bar'}), string ({'o'; 'r'}), ... %! string ({'1'; '2'})), string ({'fo1o1'; 'bar2'})) ## insertAfter: a scalar pattern and scalar NEW broadcast over the array %!assert_equal (insertAfter (string ({'a:1', 'a:2'}), 'a', '_'), ... %! string ({'a_:1', 'a_:2'})) ## insertAfter: a pattern that is not found leaves the element unchanged %!assert_equal (insertAfter (string ('hello'), 'z', 'X'), string ('hello')) ## insertAfter: NEW preserves trailing whitespace (bare cellstr would deblank) %!assert_equal (insertAfter (string ('ab'), 'a', 'X '), string ('aX b')) ## insertAfter: position form inserts after POS %!assert_equal (insertAfter (string ('abcde'), 3, '_'), string ('abc_de')) %!assert_equal (insertAfter (string ({'abcd'; 'wxyz'}), [1; 2], '_'), ... %! string ({'a_bcd'; 'wx_yz'})) ## insertAfter: POS at the last character appends NEW %!assert_equal (insertAfter (string ('abc'), 3, 'X'), string ('abcX')) ## insertAfter: missing values in STR are preserved %!test %! str = insertAfter (string ({'a-b', missing}), '-', 'X'); %! assert_equal (ismissing (str), [false, true]); %! assert_equal (cellstr (str), {'a-Xb', ''}); ## insertAfter: a missing NEW makes the corresponding element missing %!test %! str = insertAfter (string ('abc'), 'a', string (missing)); %! assert_equal (ismissing (str), true); ## insertAfter: error branches %!error ... %! insertAfter (string ('ab'), 'a') %!error ... %! insertAfter (string ('ab'), {1, 2}, 'x') %!error ... %! insertAfter (string ('ab'), string ({'a', 'b'}), 'x') %!error ... %! insertAfter (string ('ab'), 'a', {1, 2}) %!error ... %! insertAfter (string ({'a', 'b'}), 'x', string ({'1', '2', '3'})) %!error ... %! insertAfter (string ('ab'), 0, 'x') %!error ... %! insertAfter (string ('ab'), 5, 'x') ## insertBefore: pattern form inserts before the match (single occurrence) %!assert_equal (insertBefore (string ('The quick fox'), 'fox', 'brown '), ... %! string ('The quick brown fox')) ## insertBefore: pattern form inserts before EVERY non-overlapping occurrence %!assert_equal (insertBefore (string ('a-b-c'), '-', 'X'), string ('aX-bX-c')) ## insertBefore: a scalar pattern and scalar NEW broadcast over the array %!assert_equal (insertBefore (string ({'cat', 'dog'}), string ({'c', 'd'}), '_'), ... %! string ({'_cat', '_dog'})) ## insertBefore: a pattern that is not found leaves the element unchanged %!assert_equal (insertBefore (string ('hello'), 'z', 'X'), string ('hello')) ## insertBefore: position form inserts before POS %!assert_equal (insertBefore (string ('abcde'), 3, '_'), string ('ab_cde')) %!assert_equal (insertBefore (string ({'abcd'; 'wxyz'}), [3; 2], '_'), ... %! string ({'ab_cd'; 'w_xyz'})) ## insertBefore: POS at the first character prepends NEW %!assert_equal (insertBefore (string ('abc'), 1, 'X'), string ('Xabc')) ## insertBefore: missing values in STR are preserved %!test %! str = insertBefore (string ({'a-b', missing}), '-', 'X'); %! assert_equal (ismissing (str), [false, true]); %! assert_equal (cellstr (str), {'aX-b', ''}); ## insertBefore: a missing NEW makes the corresponding element missing %!test %! str = insertBefore (string ('abc'), 'c', string (missing)); %! assert_equal (ismissing (str), true); ## insertBefore: error branches %!error ... %! insertBefore (string ('ab'), 'a') %!error ... %! insertBefore (string ('ab'), {1, 2}, 'x') %!error ... %! insertBefore (string ('ab'), 'a', {1, 2}) %!error ... %! insertBefore (string ('ab'), 0, 'x') %!error ... %! insertBefore (string ('ab'), 5, 'x') ## Code-point positions for insertAfter / insertBefore: numeric indexing counts ## whole characters, not UTF-8 bytes (same multibyte text as above). %!test %! s = string (native2unicode (uint8 ([97 226 130 172 98 240 159 152 128 99]), 'UTF-8')); %! euro = string (native2unicode (uint8 ([226 130 172]), 'UTF-8')); %! emoji = string (native2unicode (uint8 ([240 159 152 128]), 'UTF-8')); %! ## insertAfter after the euro (position 2) lands on a character boundary %! assert_equal (insertAfter (s, 2, string ('X')), ... %! append (string ('a'), euro, string ('Xb'), emoji, string ('c'))); %! ## insertBefore the emoji (position 4) lands on a character boundary %! assert_equal (insertBefore (s, 4, string ('X')), ... %! append (string ('a'), euro, string ('bX'), emoji, string ('c'))); ## replace: replaces every occurrence of a single substring %!assert_equal (replace (string ('a-b-c'), '-', '_'), string ('a_b_c')) ## replace: a substring that does not occur leaves the text unchanged %!assert_equal (replace (string ('abc'), 'z', 'Y'), string ('abc')) ## replace: applied element-wise across an array, preserving its size %!assert_equal (replace (string ({'a.b', 'c.d'}), '.', '/'), ... %! string ({'a/b', 'c/d'})) ## replace: OLD/NEW size is independent of STR size %!assert_equal (replace (string ('a-b_c'), {'-', '_'}, '/'), string ('a/b/c')) ## replace: a multi-element OLD pairs up with a same-size NEW %!assert_equal (replace (string ('a-b_c'), {'-', '_'}, {'+', '='}), ... %! string ('a+b=c')) ## replace: multiple patterns applied simultaneously (a swap, not sequential) %!assert_equal (replace (string ('ab'), {'a', 'b'}, {'b', 'a'}), string ('ba')) ## replace: at each position the OLD substrings are tried in order, first wins %!assert_equal (replace (string ('aaa'), {'aa', 'a'}, {'X', 'Y'}), string ('XY')) ## replace: an empty OLD element matches nothing (single and multi-pattern) %!assert_equal (replace (string ('abc'), '', 'X'), string ('abc')) %!assert_equal (replace (string ('abc'), {'', 'b'}, {'X', 'Y'}), string ('aYc')) ## replace: trailing whitespace is preserved in both OLD and NEW %!assert_equal (replace (string ('a b'), 'a ', 'Z'), string ('Zb')) %!assert_equal (replace (string ('ab'), 'a', 'X '), string ('X b')) ## replace: accepts char vectors, cell arrays of char vectors, and string arrays %!assert_equal (replace (string ('a-b'), string ('-'), string ('_')), string ('a_b')) ## replace: missing values are preserved and carry no text %!test %! str = replace (string ({'a-b', missing}), '-', '_'); %! assert_equal (ismissing (str), [false, true]); %! assert_equal (cellstr (str), {'a_b', ''}); ## replace: error branches %!error ... %! replace (string ('ab'), 'a') %!error ... %! replace (string ('ab'), {1, 2}, 'x') %!error ... %! replace (string ('ab'), 'a', {1, 2}) %!error ... %! replace (string ('ab'), {'a', 'b'}, {'1', '2', '3'}) ## replaceBetween: pattern replaces between boundaries (default exclusive) %!assert_equal (replaceBetween (string ('The quick brown fox'), 'quick ', ' fox', 'red'), ... %! string ('The quick red fox')) ## replaceBetween: pattern syntax with explicit exclusive boundaries %!assert_equal (replaceBetween (string ('a[x]b'), '[', ']', 'Y', 'Boundaries', 'exclusive'), ... %! string ('a[Y]b')) ## replaceBetween: pattern syntax with inclusive boundaries replaces them too %!assert_equal (replaceBetween (string ('a[x]b'), '[', ']', 'Y', 'Boundaries', 'inclusive'), ... %! string ('aYb')) ## replaceBetween: an empty exclusive span inserts NEW between boundaries %!assert_equal (replaceBetween (string ('ab'), 'a', 'b', 'X', 'Boundaries', 'exclusive'), ... %! string ('aXb')) ## replaceBetween: a scalar boundary broadcasts over the array %!assert_equal (replaceBetween (string ({'[1]', '[2]'}), '[', ']', '#'), ... %! string ({'[#]', '[#]'})) ## replaceBetween: boundaries and NEW given per element %!assert_equal (replaceBetween (string ({'(a)'; ''}), string ({'('; '<'}), ... %! string ({')'; '>'}), string ({'1'; '2'})), ... %! string ({'(1)'; '<2>'})) ## replaceBetween: an unmatched boundary leaves the element unchanged %!assert_equal (replaceBetween (string ('abc'), '[', ']', 'X'), string ('abc')) ## replaceBetween: position syntax replaces the span (inclusive by default) %!assert_equal (replaceBetween (string ('abcde'), 2, 4, 'X'), string ('aXe')) ## replaceBetween: position syntax with exclusive boundaries keeps the endpoints %!assert_equal (replaceBetween (string ('abcde'), 2, 4, 'X', 'Boundaries', 'exclusive'), ... %! string ('abXde')) ## replaceBetween: equal positions replace a single character (inclusive) %!assert_equal (replaceBetween (string ('abc'), 2, 2, 'XY'), string ('aXYc')) ## replaceBetween: 'Boundaries' name and string option are case-insensitive %!assert_equal (replaceBetween (string ('a[x]b'), '[', ']', 'Y', 'boundaries', string ('Inclusive')), ... %! string ('aYb')) ## replaceBetween: missing values in STR are preserved %!test %! str = replaceBetween (string ({'[x]', missing}), '[', ']', 'Y'); %! assert_equal (ismissing (str), [false, true]); %! assert_equal (cellstr (str), {'[Y]', ''}); ## replaceBetween: a missing NEW makes the corresponding element missing %!test %! str = replaceBetween (string ('a[x]b'), '[', ']', string (missing)); %! assert_equal (ismissing (str), true); ## replaceBetween: error branches %!error ... %! replaceBetween (string ('ab'), 'a', 'b') %!error ... %! replaceBetween (string ('ab'), 1, 'x', 'Y') %!error ... %! replaceBetween (string ('a[x]b'), '[', ']', 'Y', 'Boundaries', 'foo') %!error ... %! replaceBetween (string ('ab'), 1, 2, 'Y', 'Foo', 'bar') %!error ... %! replaceBetween (string ('abc'), 2, 9, 'Y') %!error ... %! replaceBetween (string ({'a', 'b', 'c'}), 1, [1, 2], 'Y') %!error ... %! replaceBetween (string ({'a', 'b'}), 1, 1, string ({'1', '2', '3'})) %!error ... %! replaceBetween (string ('a[x]b'), '[', ']', {1, 2}) ## Code-point positions for replaceBetween: numeric indexing counts whole ## characters, not UTF-8 bytes (same multibyte text as above): a euro b emoji c. %!test %! s = string (native2unicode (uint8 ([97 226 130 172 98 240 159 152 128 99]), 'UTF-8')); %! euro = string (native2unicode (uint8 ([226 130 172]), 'UTF-8')); %! emoji = string (native2unicode (uint8 ([240 159 152 128]), 'UTF-8')); %! ## inclusive default replaces chars 2..4 (euro,b,emoji) with 'X' %! assert_equal (replaceBetween (s, 2, 4, string ('X')), string ('aXc')); %! ## exclusive keeps chars 2 and 4, replacing char 3 (b) with 'X' %! assert_equal (replaceBetween (s, 2, 4, string ('X'), 'Boundaries', 'exclusive'), ... %! append (string ('a'), euro, string ('X'), emoji, string ('c'))); ## reverse: reverses the order of the characters in a string scalar %!assert_equal (reverse (string ('hello')), string ('olleh')) ## reverse: the verbatim MATLAB documentation example %!assert_equal (reverse (string ({'airport', 'control tower', 'radar', 'runway'})), ... %! string ({'tropria', 'rewot lortnoc', 'radar', 'yawnur'})) ## reverse: a palindrome is returned unchanged %!assert_equal (reverse (string ('radar')), string ('radar')) ## reverse: applied element-wise across an array, preserving its size %!assert_equal (reverse (string ({'ab', 'cd'; 'ef', 'gh'})), ... %! string ({'ba', 'dc'; 'fe', 'hg'})) ## reverse: a column vector keeps its shape %!assert_equal (reverse (string ({'ab'; 'cde'})), string ({'ba'; 'edc'})) ## reverse: output is a string array of the same size as the input %!test %! r = reverse (string ({'ab', 'cd', 'ef'})); %! assert_equal (isa (r, 'string'), true); %! assert_equal (size (r), [1, 3]); ## reverse: a single character is unchanged %!assert_equal (reverse (string ('x')), string ('x')) ## reverse: an empty string stays empty %!assert_equal (reverse (string ('')), string ('')) ## reverse: an empty (0x0) string array is returned unchanged %!test %! r = reverse (string ([])); %! assert_equal (isa (r, 'string'), true); %! assert_equal (size (r), [0, 0]); ## reverse: leading and trailing whitespace is preserved (and moved) %!assert_equal (reverse (string ('ab ')), string (' ba')) %!assert_equal (reverse (string (' ab')), string ('ba ')) %!assert_equal (reverse (string ('a b c')), string ('c b a')) ## reverse: applying reverse twice restores the original %!test %! s = string ({'hello', 'world'; 'foo bar', 'baz'}); %! assert_equal (reverse (reverse (s)), s); ## reverse: missing values are preserved and carry no text %!test %! r = reverse (string ({'ab', missing, 'cd'})); %! assert_equal (ismissing (r), [false, true, false]); %! assert_equal (cellstr (r), {'ba', '', 'dc'}); ## reverse: an all-missing array stays all missing %!test %! r = reverse (string ({missing, missing})); %! assert_equal (ismissing (r), [true, true]); ## reverse: counts whole characters, not UTF-8 bytes (multibyte text ## a euro b emoji c reverses to c emoji b euro a) %!test %! s = string (native2unicode (uint8 ([97 226 130 172 98 240 159 152 128 99]), 'UTF-8')); %! euro = string (native2unicode (uint8 ([226 130 172]), 'UTF-8')); %! emoji = string (native2unicode (uint8 ([240 159 152 128]), 'UTF-8')); %! assert_equal (reverse (s), ... %! append (string ('c'), emoji, string ('b'), euro, string ('a'))); %! ## reversing twice restores the multibyte original exactly %! assert_equal (reverse (reverse (s)), s); ## sort: a vector is sorted in ascending order by default %!assert_equal (sort (string ({'b', 'c', 'a'})), string ({'a', 'b', 'c'})) ## sort: a column vector keeps its shape %!assert_equal (sort (string ({'b'; 'a'; 'c'})), string ({'a'; 'b'; 'c'})) ## sort: the second output indexes the original elements (B = A(index)) %!test %! a = string ({'b', 'c', 'a'}); %! [b, i] = sort (a); %! assert_equal (i, [3, 1, 2]); %! assert_equal (b, a(i)); ## sort: 'descend' reverses the order %!assert_equal (sort (string ({'b', 'c', 'a'}), 'descend'), ... %! string ({'c', 'b', 'a'})) ## sort: a matrix is sorted column-wise by default %!assert_equal (sort (string ({'d', 'a'; 'b', 'c'})), ... %! string ({'b', 'a'; 'd', 'c'})) ## sort: DIM selects the operating dimension %!assert_equal (sort (string ({'d', 'a'; 'b', 'c'}), 2), ... %! string ({'a', 'd'; 'b', 'c'})) ## sort: DIM and DIRECTION can be combined %!assert_equal (sort (string ({'d', 'a'; 'b', 'c'}), 2, 'descend'), ... %! string ({'d', 'a'; 'c', 'b'})) ## sort: the empty string sorts before any non-empty string %!assert_equal (sort (string ({'b', '', 'a'})), string ({'', 'a', 'b'})) ## sort: a scalar is returned unchanged %!assert_equal (sort (string ('x')), string ('x')) ## sort: ties are stable -- equal elements keep their original order %!test %! [~, i] = sort (string ({'b', 'a', 'b'})); %! assert_equal (i, [2, 1, 3]); ## sort: missing values go last for ascending order ('auto' default) %!test %! r = sort (string ({'b', missing, 'a'})); %! assert_equal (ismissing (r), [false, false, true]); %! assert_equal (cellstr (r), {'a', 'b', ''}); ## sort: missing values go first for descending order ('auto' default) %!test %! r = sort (string ({'b', missing, 'a'}), 'descend'); %! assert_equal (ismissing (r), [true, false, false]); %! assert_equal (cellstr (r), {'', 'b', 'a'}); ## sort: 'MissingPlacement','first' forces missing to the front %!test %! r = sort (string ({'b', missing, 'a'}), 'MissingPlacement', 'first'); %! assert_equal (ismissing (r), [true, false, false]); %! assert_equal (cellstr (r), {'', 'a', 'b'}); ## sort: 'MissingPlacement','last' forces missing to the end (descending) %!test %! r = sort (string ({'b', missing, 'a'}), 'descend', ... %! 'MissingPlacement', 'last'); %! assert_equal (ismissing (r), [false, false, true]); %! assert_equal (cellstr (r), {'b', 'a', ''}); ## sort: error branches %!error ... %! sort (string ('a'), 'MissingPlacement', 'bogus') ## sortrows: rows are sorted ascending, ties broken by later columns %!assert_equal (sortrows (string ({'b', 'x'; 'a', 'z'; 'b', 'a'})), ... %! string ({'a', 'z'; 'b', 'a'; 'b', 'x'})) ## sortrows: the second output indexes the original rows (B = A(index,:)) %!test %! a = string ({'b', 'x'; 'a', 'z'; 'b', 'a'}); %! [b, i] = sortrows (a); %! assert_equal (i, [2; 3; 1]); %! assert_equal (b, a(i, :)); ## sortrows: COL selects columns; a negative index sorts that column descending %!assert_equal (sortrows (string ({'b', 'x'; 'a', 'z'; 'b', 'a'}), [1, -2]), ... %! string ({'a', 'z'; 'b', 'x'; 'b', 'a'})) ## sortrows: a single DIRECTION applies to all columns %!assert_equal (sortrows (string ({'b', 'x'; 'a', 'z'; 'b', 'a'}), 'descend'), ... %! string ({'b', 'x'; 'b', 'a'; 'a', 'z'})) ## sortrows: DIRECTION can be a cell array, one entry per column %!assert_equal (sortrows (string ({'b', 'x'; 'a', 'z'; 'b', 'a'}), ... %! {'ascend', 'descend'}), ... %! string ({'a', 'z'; 'b', 'x'; 'b', 'a'})) ## sortrows: COL and DIRECTION together (the sign of COL is ignored) %!assert_equal (sortrows (string ({'b', 'x'; 'a', 'z'; 'b', 'a'}), ... %! [1, 2], {'ascend', 'descend'}), ... %! string ({'a', 'z'; 'b', 'x'; 'b', 'a'})) ## sortrows: a missing element sends its row last for ascending ('auto') %!test %! a = string ({'b', 'x'; 'a', 'z'}); %! a(1,1) = string (missing); %! r = sortrows (a); %! assert_equal (cellstr (r), {'a', 'z'; '', 'x'}); %! assert_equal (ismissing (r), [false, false; true, false]); ## sortrows: 'MissingPlacement','first' sends the missing row first %!test %! a = string ({'b', 'x'; 'a', 'z'}); %! a(1,1) = string (missing); %! r = sortrows (a, 'MissingPlacement', 'first'); %! assert_equal (cellstr (r), {'', 'x'; 'a', 'z'}); ## sortrows: 'MissingPlacement','last' keeps the missing row last (descending) %!test %! a = string ({'b', 'x'; 'a', 'z'}); %! a(1,1) = string (missing); %! r = sortrows (a, 'descend', 'MissingPlacement', 'last'); %! assert_equal (cellstr (r), {'a', 'z'; '', 'x'}); ## sortrows: error branches %!error ... %! sortrows (string (reshape (1:8, [2, 2, 2]))) %!error ... %! sortrows (string ({'a', 'b'}), 'MissingPlacement', 'bogus') %!error ... %! sortrows (string ({'a', 'b'}), [1.5]) %!error ... %! sortrows (string ({'a', 'b'}), [0]) %!error ... %! sortrows (string ({'a', 'b'}), [3]) %!error ... %! sortrows (string ({'a', 'b'}), {'up', 'down'}) %!error ... %! sortrows (string ({'a', 'b'}), [1, 2], {'ascend', 'descend'}, 'x') %!error ... %! sortrows (string ({'a', 'b'}), {'ascend', 'descend'}, 'ascend') ## split: a scalar splits into an Nx1 column at the delimiter %!assert_equal (split (string ('a-b-c'), '-'), string ({'a'; 'b'; 'c'})) ## split: the default delimiter is whitespace %!assert_equal (split (string ('one two three')), string ({'one'; 'two'; 'three'})) ## split: delimiters are not collapsed -- consecutive ones yield empty strings %!assert_equal (split (string ('Jones,,,68,175'), ','), ... %! string ({'Jones'; ''; ''; '68'; '175'})) ## split: a trailing delimiter yields a trailing empty string %!assert_equal (split (string ('a-'), '-'), string ({'a'; ''})) ## split: an Mx1 column splits into an MxN array %!assert_equal (split (string ({'a-b'; 'c-d'}), '-'), string ({'a', 'b'; 'c', 'd'})) ## split: a 1xM row splits into a 1xMxN array %!test %! r = split (string ({'a-b', 'c-d'}), '-'); %! assert_equal (size (r), [1, 2, 2]); %! assert_equal (cellstr (r(1,1,:))(:), {'a'; 'b'}); %! assert_equal (cellstr (r(1,2,:))(:), {'c'; 'd'}); ## split: multiple delimiters are all used (first match wins at a position) %!assert_equal (split (string ('a, b and c'), {', ', ' and '}), ... %! string ({'a'; 'b'; 'c'})) ## split: the second output returns the matched delimiters (one fewer piece) %!test %! [s, m] = split (string ('a, b and c'), {', ', ' and '}); %! assert_equal (s, string ({'a'; 'b'; 'c'})); %! assert_equal (m, string ({', '; ' and '})); ## split: DIM lays the pieces out along the given dimension %!test %! r = split (string ('x.y.z'), '.', 2); %! assert_equal (size (r), [1, 3]); %! assert_equal (cellstr (r), {'x', 'y', 'z'}); ## split: a string with no delimiter match returns itself (single piece) %!assert_equal (split (string ('abc'), '-'), string ('abc')) ## split: missing values are preserved and count as a single piece %!test %! r = split (string (missing), '-'); %! assert_equal (ismissing (r), true); %! assert_equal (size (r), [1, 1]); %!test %! r = split (string ({'ab', missing}), '-'); %! assert_equal (ismissing (r), [false, true]); %! assert_equal (cellstr (r), {'ab', ''}); ## split: error branches %!error ... %! split (string ('a'), '-', 2, 3) %!error ... %! split (string ('a'), {1, 2}) %!error ... %! split (string ('a'), '-', 0) %!error ... %! split (string ({'a-b'; 'c-d'}), '-', 1) %!error ... %! split (string ({'a-b', 'c'}), '-') ## splitlines: a scalar splits into an Nx1 column at line feeds %!assert_equal (splitlines (string (sprintf ("a\nb\nc"))), string ({'a'; 'b'; 'c'})) ## splitlines: a CR+LF pair is a single boundary (no empty line between) %!assert_equal (splitlines (string (sprintf ("a\r\nb"))), string ({'a'; 'b'})) ## splitlines: a lone carriage return is also a boundary %!assert_equal (splitlines (string (sprintf ("a\rb"))), string ({'a'; 'b'})) ## splitlines: consecutive newlines are not collapsed (empty line between) %!assert_equal (splitlines (string (sprintf ("a\n\nb"))), string ({'a'; ''; 'b'})) ## splitlines: vertical tab and form feed are boundaries too %!assert_equal (splitlines (string (sprintf ("a\vb\fc"))), string ({'a'; 'b'; 'c'})) ## splitlines: the Unicode line separator (U+2028) is a boundary %!test %! ls = native2unicode (uint8 ([226, 128, 168]), "UTF-8"); %! assert_equal (splitlines (string (["a", ls, "b"])), string ({'a'; 'b'})); ## splitlines: an Mx1 column splits into an MxN array %!assert_equal (splitlines (string ({sprintf("a\nb"); sprintf("c\nd")})), ... %! string ({'a', 'b'; 'c', 'd'})) ## splitlines: a string with no newline returns itself (single line) %!assert_equal (splitlines (string ('abc')), string ('abc')) ## splitlines: missing values are preserved and count as a single line %!test %! r = splitlines (string ({'ab', missing})); %! assert_equal (ismissing (r), [false, true]); %! assert_equal (cellstr (r), {'ab', ''}); ## splitlines: error branch -- elements must have the same number of lines %!error ... %! splitlines (string ({sprintf("a\nb"), 'c'})) ## strcat: concatenates corresponding elements (verbatim MATLAB example) %!assert_equal (strcat (string ({'John ', 'Mary '}), string ({'Smith', 'Jones'})), ... %! string ({'John Smith', 'Mary Jones'})) ## strcat: string inputs preserve trailing whitespace (unlike char strcat) %!assert_equal (strcat (string ('a '), string ('b')), string ('a b')) ## strcat: a scalar argument expands, and char/cellstr may be mixed in %!assert_equal (strcat (string ({'x', 'y'}), '_', string ('Z')), ... %! string ({'x_Z', 'y_Z'})) %!assert_equal (strcat (string ({'a', 'b'}), {'1', '2'}), string ({'a1', 'b2'})) ## strcat: a single argument is returned as a string %!assert_equal (strcat (string ('hi')), string ('hi')) ## strcat: output is a string array %!test %! r = strcat (string ({'a', 'b'}), string ('!')); %! assert_equal (isa (r, 'string'), true); %! assert_equal (cellstr (r), {'a!', 'b!'}); ## strcat: error branch %!error ... %! strcat (string ('a'), 5) ## strip: removes leading and trailing whitespace from both sides by default %!assert_equal (strip (string (' Ann Marie ')), string ('Ann Marie')) ## strip: 'right' removes trailing whitespace only %!assert_equal (strip (string (' Ann Marie '), 'right'), string (' Ann Marie')) ## strip: 'left' removes leading whitespace only %!assert_equal (strip (string (' Ann Marie '), 'left'), string ('Ann Marie ')) ## strip: a custom strip character on a given side (verbatim MATLAB example) %!assert_equal (strip (string ('0095.36'), 'left', '0'), string ('95.36')) ## strip: a single-character second argument is the strip character %!assert_equal (strip (string ('xxhixx'), 'x'), string ('hi')) ## strip: 'both' with an explicit strip character %!assert_equal (strip (string ('--a--'), 'both', '-'), string ('a')) ## strip: the default whitespace set includes tab and newline %!assert_equal (strip (string (sprintf ("\t a \n"))), string ('a')) ## strip: an all-whitespace string becomes empty %!assert_equal (strip (string (' ')), string ('')) ## strip: an empty string stays empty %!assert_equal (strip (string ('')), string ('')) ## strip: applied element-wise across an array, preserving its size %!assert_equal (strip (string ({' a ', ' b '})), string ({'a', 'b'})) ## strip: a non-breaking space (char 160) is not treated as whitespace %!test %! nbsp = native2unicode (uint8 ([194, 160]), "UTF-8"); %! s = string ([nbsp, 'a', nbsp]); %! assert_equal (strip (s), s); ## strip: a multibyte strip character is matched as a whole code point %!test %! euro = native2unicode (uint8 ([226, 130, 172]), "UTF-8"); %! assert_equal (strip (string ([euro, euro, 'a', euro]), euro), string ('a')); ## strip: missing values are preserved and carry no text %!test %! r = strip (string ({' a ', missing})); %! assert_equal (ismissing (r), [false, true]); %! assert_equal (cellstr (r), {'a', ''}); ## strip: error branches %!error ... %! strip (string ('a'), 'left', '-', 'extra') %!error ... %! strip (string ('a'), 'middle') %!error ... %! strip (string ('a'), 'left', 'xy') ## pad: by default pads with trailing spaces to the longest element %!assert_equal (pad (string ({'a'; 'bbb'; 'cc'})), string ({'a '; 'bbb'; 'cc '})) ## pad: a single element is returned unchanged (it is the longest) %!assert_equal (pad (string ('abc')), string ('abc')) ## pad: NCHARS gives an explicit width, padding on the right by default %!assert_equal (pad (string ('ab'), 5), string ('ab ')) ## pad: 'left' adds the padding before the text %!assert_equal (pad (string ('ab'), 5, 'left'), string (' ab')) ## pad: 'both' splits the padding, putting an odd extra on the right %!assert_equal (pad (string ('ab'), 5, 'both'), string (' ab ')) %!assert_equal (pad (string ('ab'), 6, 'both'), string (' ab ')) ## pad: a custom pad character on a given side %!assert_equal (pad (string ('7'), 4, 'left', '0'), string ('0007')) ## pad: a single-character second argument is the pad character (to longest) %!assert_equal (pad (string ({'a'; 'bbb'}), '*'), string ({'a**'; 'bbb'})) ## pad: a SIDE-only second argument pads to the longest element %!assert_equal (pad (string ({'a'; 'bbb'}), 'left'), string ({' a'; 'bbb'})) ## pad: elements longer than NCHARS are not truncated %!assert_equal (pad (string ('abcdef'), 3), string ('abcdef')) ## pad: width is measured in characters (code points), not bytes %!test %! euro = native2unicode (uint8 ([226, 130, 172]), "UTF-8"); %! assert_equal (pad (string (euro), 3), string ([euro, ' '])); ## pad: missing values are preserved and ignored when finding the longest %!test %! r = pad (string ({'a', missing, 'ccc'})); %! assert_equal (ismissing (r), [false, true, false]); %! assert_equal (cellstr (r), {'a ', '', 'ccc'}); ## pad: error branches %!error ... %! pad (string ('a'), 1, 'left', '-', 'extra') %!error ... %! pad (string ('a'), -1) %!error ... %! pad (string ('a'), 'middle') %!error ... %! pad (string ('a'), 5, 'left', 'xy') ## join: combines along the last non-singleton dimension with a space (verbatim) %!assert_equal (join (string ({'Carlos', 'Sada'; 'Ella', 'Olsen'; 'Diana', 'Lee'})), ... %! string ({'Carlos Sada'; 'Ella Olsen'; 'Diana Lee'})) ## join: a single custom delimiter is placed between all elements (verbatim) %!assert_equal (join (string ({'x', 'y', 'z'; 'a', 'b', 'c'}), '-'), ... %! string ({'x-y-z'; 'a-b-c'})) ## join: an array of delimiters goes between consecutive elements (verbatim) %!assert_equal (join (string ({'x', 'y', 'z'; 'a', 'b', 'c'}), ... %! string ({' + ', ' = '; ' - ', ' = '})), ... %! string ({'x + y = z'; 'a - b = c'})) ## join: DIM selects the joined dimension (verbatim example, dim 1) %!assert_equal (join (string ({'Carlos', 'Sada'; 'Ella', 'Olsen'; 'Diana', 'Lee'}), 1), ... %! string ({'Carlos Ella Diana', 'Sada Olsen Lee'})) ## join: a row or column vector collapses to a string scalar %!assert_equal (join (string ({'a', 'b', 'c'})), string ('a b c')) %!assert_equal (join (string ({'a'; 'b'; 'c'})), string ('a b c')) ## join: a string scalar is returned unchanged %!assert_equal (join (string ('solo')), string ('solo')) ## join: a delimiter and a dimension together %!assert_equal (join (string ({'a', 'b'; 'c', 'd'}), '-', 1), ... %! string ({'a-c', 'b-d'})) ## join: join is the inverse of split for a single delimiter %!assert_equal (join (split (string ('a-b-c'), '-'), '-'), string ('a-b-c')) ## join: a missing element (or delimiter) propagates to the joined result %!test %! r = join (string ({'a', 'b'; 'c', missing})); %! assert_equal (ismissing (r), [false; true]); %! assert_equal (cellstr (r(1)), {'a b'}); ## join: error branches %!error ... %! join (string ('a'), '-', 2, 3) %!error ... %! join (string ('a'), {1, 2}) %!error ... %! join (string ({'a', 'b'}), string ({'-', '-'})) %!error ... %! join (string ('a'), '-', 0) ## plus: appends STR2 to STR1, equivalent to the '+' operator %!assert_equal (string ('foo') + string ('bar'), string ('foobar')) %!assert_equal (plus (string ('foo'), string ('bar')), string ('foobar')) ## plus: applied element-wise across equal-sized arrays %!assert_equal (string ({'a', 'b'}) + string ({'1', '2'}), string ({'a1', 'b2'})) ## plus: a scalar operand broadcasts over the array %!assert_equal (string ({'a', 'b'}) + string ('!'), string ({'a!', 'b!'})) ## plus: a missing value propagates (any missing operand -> missing) %!test %! r = string ({'a', missing}) + string ('X'); %! assert_equal (ismissing (r), [false, true]); %! assert_equal (cellstr (r), {'aX', ''}); ## plus: error branch -- both operands must be string arrays %!error ... %! string ('a') + 5 ## lower: converts uppercase characters to lowercase (verbatim MATLAB example) %!assert_equal (lower (string ('The SOONER,')), string ('the sooner,')) ## lower: digits, punctuation, and already-lowercase letters are unchanged %!assert_equal (lower (string ('aB3!Cd')), string ('ab3!cd')) ## lower: applied element-wise across an array, preserving its size %!assert_equal (lower (string ({'AB', 'Cd'; 'eF', 'GH'})), ... %! string ({'ab', 'cd'; 'ef', 'gh'})) ## lower: an empty string stays empty %!assert_equal (lower (string ('')), string ('')) ## lower: missing values are preserved and carry no text %!test %! r = lower (string ({'AB', missing})); %! assert_equal (ismissing (r), [false, true]); %! assert_equal (cellstr (r), {'ab', ''}); ## upper: converts lowercase characters to uppercase (verbatim MATLAB example) %!assert_equal (upper (string ('Hello, World.')), string ('HELLO, WORLD.')) ## upper: digits, punctuation, and already-uppercase letters are unchanged %!assert_equal (upper (string ('aB3!cD')), string ('AB3!CD')) ## upper: applied element-wise across an array, preserving its size %!assert_equal (upper (string ({'ab', 'cD'; 'Ef', 'gh'})), ... %! string ({'AB', 'CD'; 'EF', 'GH'})) ## upper: an empty string stays empty %!assert_equal (upper (string ('')), string ('')) ## upper: missing values are preserved and carry no text %!test %! r = upper (string ({'ab', missing})); %! assert_equal (ismissing (r), [false, true]); %! assert_equal (cellstr (r), {'AB', ''}); ## unique: returns sorted unique elements with index vectors B=A(ixA), A=B(ixB) %!test %! A = string ({'b', 'a', 'b', 'c', 'a'}); %! [B, ixA, ixB] = unique (A); %! assert_equal (B, string ({'a', 'b', 'c'})); %! assert_equal (ixA, [2; 1; 4]); %! assert_equal (ixB, [2; 1; 2; 3; 1]); ## unique: 'stable' keeps first-seen order %!assert_equal (unique (string ({'b', 'a', 'b', 'c', 'a'}), 'stable'), ... %! string ({'b', 'a', 'c'})) ## unique: 'first' and 'last' select which occurrence the index points to %!test %! [~, iaF] = unique (string ({'b', 'a', 'b', 'c', 'a'}), 'first'); %! [~, iaL] = unique (string ({'b', 'a', 'b', 'c', 'a'}), 'last'); %! assert_equal (iaF, [2; 1; 4]); %! assert_equal (iaL, [5; 3; 4]); ## unique: 'rows' returns the unique rows of a 2-D string matrix %!assert_equal (unique (string ({'a', 'b'; 'a', 'b'; 'c', 'd'}), 'rows'), ... %! string ({'a', 'b'; 'c', 'd'})) ## unique: missing values are not de-duplicated and are appended at the end %!test %! B = unique (string ({'b', missing, 'a', missing})); %! assert_equal (ismissing (B), [false, false, true, true]); %! assert_equal (cellstr (B(~ismissing (B))), {'a', 'b'}); ## unique: error branches %!error ... %! unique (string ('a'), 'bogus') %!error ... %! unique (reshape (string ({'a','b','c','d','e','f','g','h'}), 2, 2, 2), 'rows') ################################################################################ ## ** Array Operations ** ## ################################################################################ ## Available Methods ## ## ## ## 'cat' 'horzcat' 'vertcat' 'repmat' ## ## 'repelem' 'repelems' 'reshape' 'circshift' ## ## 'permute' 'ipermute' 'transpose' 'ctranspose' ## ## ## ################################################################################ ## cat: concatenates string arrays along the first dimension (rows) %!assert_equal (cat (1, string ({'a', 'b'}), string ({'c', 'd'})), ... %! string ({'a', 'b'; 'c', 'd'})) ## cat: concatenates string arrays along the second dimension (columns) %!assert_equal (cat (2, string ({'a'; 'b'}), string ({'c'; 'd'})), ... %! string ({'a', 'c'; 'b', 'd'})) ## cat: a higher dimension stacks the inputs into an N-D array %!test %! c = cat (3, string ({'a', 'b'}), string ({'c', 'd'})); %! assert_equal (size (c), [1, 2, 2]); %! assert_equal (cellstr (c(:,:,1)), {'a', 'b'}); %! assert_equal (cellstr (c(:,:,2)), {'c', 'd'}); ## cat: accepts more than two inputs %!assert_equal (cat (2, string ('a'), string ('b'), string ('c')), ... %! string ({'a', 'b', 'c'})) ## cat: converts char, cellstr, numeric, and logical arguments to string %!assert_equal (cat (2, string ('x'), 'yy', {'z'}), string ({'x', 'yy', 'z'})) %!assert_equal (cat (2, string ('n'), 5, true, false), ... %! string ({'n', '5', 'true', 'false'})) ## cat: numeric arrays keep their shape when converted %!assert_equal (cat (1, string ({'a'; 'b'}), [1; 2]), ... %! string ({'a'; 'b'; '1'; '2'})) ## cat: a char matrix contributes one element per row, preserving trailing ## whitespace (unlike the core 'cellstr', which would deblank it) %!assert_equal (cat (1, string ({'1'; '2'}), char ('a', 'bb')), ... %! string ({'1'; '2'; 'a '; 'bb'})) ## cat: an empty argument drops out of the concatenation %!assert_equal (cat (2, string ('a'), string ([])), string ('a')) %!assert_equal (cat (1, string ([]), string ({'a'; 'b'})), string ({'a'; 'b'})) ## cat: a single argument is returned unchanged %!assert_equal (cat (2, string ({'a'; 'b'})), string ({'a'; 'b'})) ## cat: missing values are carried through positionally %!test %! c = cat (2, string ('a'), string (NaN), string ('b')); %! assert_equal (ismissing (c), [false, true, false]); %! assert_equal (cellstr (c), {'a', '', 'b'}); ## cat: a categorical, datetime, or duration argument promotes the result to ## that class (the first input is forwarded to its constructor) %!assert_equal (class (cat (2, string ('a'), categorical ({'b'}))), ... %! 'categorical') %!assert_equal (class (cat (2, string ('2020-01-01'), ... %! datetime (2021, 1, 1))), 'datetime') %!assert_equal (class (cat (2, string ('1:00:00'), duration (2, 0, 0))), ... %! 'duration') ## horzcat: '[A, B]' equals 'horzcat (A, B)' equals 'cat (2, A, B)' %!assert_equal ([string({'a', 'b'}), string({'c', 'd'})], ... %! horzcat (string ({'a', 'b'}), string ({'c', 'd'}))) %!assert_equal (horzcat (string ({'a', 'b'}), string ({'c', 'd'})), ... %! cat (2, string ({'a', 'b'}), string ({'c', 'd'}))) ## horzcat: converts and concatenates non-string arguments %!assert_equal ([string('x'), 'yy', {'z'}, 5, true], ... %! string ({'x', 'yy', 'z', '5', 'true'})) ## horzcat: missing values are carried through positionally %!test %! h = [string('a'), string(NaN)]; %! assert_equal (ismissing (h), [false, true]); %! assert_equal (cellstr (h), {'a', ''}); ## vertcat: '[A; B]' equals 'vertcat (A, B)' equals 'cat (1, A, B)' %!assert_equal ([string({'a', 'b'}); string({'c', 'd'})], ... %! vertcat (string ({'a', 'b'}), string ({'c', 'd'}))) %!assert_equal (vertcat (string ({'a', 'b'}), string ({'c', 'd'})), ... %! cat (1, string ({'a', 'b'}), string ({'c', 'd'}))) ## vertcat: converts and concatenates non-string arguments %!assert_equal ([string('x'); 'yy'; {'z'}; 5; true], ... %! string ({'x'; 'yy'; 'z'; '5'; 'true'})) ## vertcat: missing values are carried through positionally %!test %! v = [string('a'); string(NaN)]; %! assert_equal (ismissing (v), [false; true]); %! assert_equal (cellstr (v), {'a'; ''}); ## repmat: a scalar count tiles the array along every dimension %!assert_equal (repmat (string ({'a', 'b'}), 2), ... %! string ({'a', 'b', 'a', 'b'; 'a', 'b', 'a', 'b'})) ## repmat: separate row and column counts %!assert_equal (repmat (string ('x'), 2, 3), ... %! string ({'x', 'x', 'x'; 'x', 'x', 'x'})) ## repmat: a dimension vector is equivalent to the scalar-list form %!assert_equal (repmat (string ('x'), [2, 3]), repmat (string ('x'), 2, 3)) ## repmat: a zero count yields an empty string array %!assert_equal (size (repmat (string ('a'), 0)), [0, 0]) %!assert_equal (class (repmat (string ('a'), 0)), 'string') ## repmat: missing values are preserved %!test %! r = repmat (string (NaN), 1, 3); %! assert_equal (ismissing (r), [true, true, true]); %! assert_equal (size (r), [1, 3]); ## repelem: a scalar count repeats each element that many times %!assert_equal (repelem (string ({'a', 'b'}), 2), ... %! string ({'a', 'a', 'b', 'b'})) ## repelem: a count vector repeats each element by its own count %!assert_equal (repelem (string ({'a', 'b', 'c'}), [1, 2, 3]), ... %! string ({'a', 'b', 'b', 'c', 'c', 'c'})) ## repelem: a zero count drops the corresponding element %!assert_equal (repelem (string ({'a', 'b'}), [0, 2]), string ({'b', 'b'})) ## repelem: separate per-dimension counts repeat a matrix block-wise %!test %! r = repelem (string ({'a', 'b'; 'c', 'd'}), 2, 3); %! assert_equal (size (r), [4, 6]); %! assert_equal (cellstr (r(1,:)), {'a', 'a', 'a', 'b', 'b', 'b'}); %! assert_equal (cellstr (r(3,:)), {'c', 'c', 'c', 'd', 'd', 'd'}); ## repelem: missing values are repeated like any other element %!test %! r = repelem (string ({'a', missing}), 2); %! assert_equal (ismissing (r), [false, false, true, true]); %! assert_equal (cellstr (r), {'a', 'a', '', ''}); ## repelems: the first row of R indexes elements, the second row repeats them %!assert_equal (repelems (string ({'a', 'b', 'c'}), [1, 3; 2, 2]), ... %! string ({'a', 'a', 'c', 'c'})) ## repelems: a single column selects and repeats one element %!assert_equal (repelems (string ({'x', 'y', 'z'}), [2; 3]), ... %! string ({'y', 'y', 'y'})) ## repelems: a selected missing element stays missing %!test %! r = repelems (string ({'a', missing, 'c'}), [2, 3; 1, 2]); %! assert_equal (ismissing (r), [true, false, false]); %! assert_equal (cellstr (r), {'', 'c', 'c'}); ## reshape: dimensions are filled column-major %!assert_equal (reshape (string ({'a', 'b', 'c', 'd', 'e', 'f'}), 2, 3), ... %! string ({'a', 'c', 'e'; 'b', 'd', 'f'})) ## reshape: an empty dimension '[]' is inferred from the element count %!assert_equal (reshape (string ({'a', 'b', 'c', 'd', 'e', 'f'}), 2, []), ... %! reshape (string ({'a', 'b', 'c', 'd', 'e', 'f'}), 2, 3)) %!assert_equal (reshape (string ({'a', 'b', 'c', 'd', 'e', 'f'}), [], 3), ... %! reshape (string ({'a', 'b', 'c', 'd', 'e', 'f'}), 2, 3)) ## reshape: a dimension vector is equivalent to the scalar-list form %!assert_equal (reshape (string ({'a', 'b', 'c', 'd', 'e', 'f'}), [3, 2]), ... %! reshape (string ({'a', 'b', 'c', 'd', 'e', 'f'}), 3, 2)) ## reshape: missing values follow their elements column-major %!test %! r = reshape (string ({'a', missing, 'c', missing}), 2, 2); %! assert_equal (ismissing (r), [false, false; true, true]); ## circshift: a positive scalar shifts along the first non-singleton dimension %!assert_equal (circshift (string ({'a', 'b', 'c', 'd'}), 1), ... %! string ({'d', 'a', 'b', 'c'})) ## circshift: a negative shift moves elements the other way %!assert_equal (circshift (string ({'a', 'b', 'c', 'd'}), -1), ... %! string ({'b', 'c', 'd', 'a'})) ## circshift: a trailing DIM argument selects the dimension to shift %!assert_equal (circshift (string ({'a', 'b'; 'c', 'd'}), 1, 2), ... %! string ({'b', 'a'; 'd', 'c'})) ## circshift: a vector shift applies one value per dimension %!assert_equal (circshift (string ({'a', 'b'; 'c', 'd'}), [1, 1]), ... %! string ({'d', 'c'; 'b', 'a'})) ## circshift: missing values are shifted like any other element %!test %! r = circshift (string ({'a', missing, 'c'}), 1); %! assert_equal (ismissing (r), [false, false, true]); %! assert_equal (cellstr (r), {'c', 'a', ''}); ## permute: rearranges the dimensions (here equivalent to a transpose) %!assert_equal (permute (string ({'a', 'b'; 'c', 'd'}), [2, 1]), ... %! string ({'a', 'c'; 'b', 'd'})) ## permute: missing values move with their elements %!test %! p = permute (string ({'a', missing; 'c', 'd'}), [2, 1]); %! assert_equal (ismissing (p), [false, false; true, false]); ## ipermute: inverts permute, so the round-trip returns the original array %!test %! A = string ({'a', 'b', 'c'; 'd', 'e', 'f'}); %! assert_equal (ipermute (permute (A, [2, 1]), [2, 1]), A); ## ipermute: a 2-element permutation is its own inverse (a transpose) %!assert_equal (ipermute (string ({'a', 'b'; 'c', 'd'}), [2, 1]), ... %! string ({'a', 'c'; 'b', 'd'})) ## transpose: turns a row into a column and vice versa %!assert_equal (transpose (string ({'a', 'b', 'c'})), string ({'a'; 'b'; 'c'})) ## transpose: flips a matrix across its diagonal %!assert_equal (transpose (string ({'a', 'b'; 'c', 'd'})), ... %! string ({'a', 'c'; 'b', 'd'})) ## transpose: the '.'' operator dispatches to this method %!assert_equal (string ({'a', 'b'}).', string ({'a'; 'b'})) ## transpose: missing values are preserved %!test %! t = transpose (string ({'a', missing})); %! assert_equal (ismissing (t), [false; true]); %! assert_equal (size (t), [2, 1]); ## ctranspose: identical to transpose for string arrays %!assert_equal (ctranspose (string ({'a', 'b', 'c'})), string ({'a'; 'b'; 'c'})) %!assert_equal (ctranspose (string ({'a', 'b'; 'c', 'd'})), ... %! transpose (string ({'a', 'b'; 'c', 'd'}))) ## ctranspose: the ''' operator dispatches to this method %!assert_equal (string ({'a', 'b'})', string ({'a'; 'b'})) ################################################################################ ## ** Reference and Assignment Operations ** ## ################################################################################ ## Available Methods ## ## ## ## 'end' 'subsref' 'subsasgn' ## ## ## ################################################################################ ## end: indexes the last element of a vector (and counts back from it) %!test %! a = string ({'a', 'b', 'c', 'd'}); %! assert_equal (a(end), string ('d')); %! assert_equal (a(end-1), string ('c')); ## end: resolves per-dimension and linearly within a matrix %!test %! m = string ({'a', 'b', 'c'; 'd', 'e', 'f'}); %! assert_equal (m(end, end), string ('f')); %! assert_equal (m(end), string ('f')); %! assert_equal (m(1, end), string ('c')); ## subsref: '()' returns a string sub-array (class and contents preserved) %!test %! a = string ({'a', 'b', 'c', 'd'}); %! assert_equal (class (a(2:3)), 'string'); %! assert_equal (a(2:3), string ({'b', 'c'})); ## subsref: '()' carries the missing mask along with the elements %!test %! a = string ({'a', missing, 'c'}); %! r = a([1, 2]); %! assert_equal (ismissing (r), [false, true]); %! assert_equal (cellstr (r), {'a', ''}); ## subsref: '{}' returns the backing cell-array slice (a cell, not a string) %!test %! a = string ({'a', 'b', 'c', 'd'}); %! v = a{2}; %! assert_equal (class (v), 'cell'); %! assert_equal (v, {'b'}); ## subsref: chained '()' references resolve left to right %!assert_equal (string ({'aa', 'bb', 'cc'})([1, 3])(2), string ('cc')) ## subsref: '.' indexing for reference is an error %!error ... %! (string ({'a', 'b'}).foo) ## subsasgn: '()' assignment from another string replaces the elements %!test %! a = string ({'a', 'b', 'c', 'd'}); %! a(2) = string ('Z'); %! assert_equal (a, string ({'a', 'Z', 'c', 'd'})); ## subsasgn: '()' converts a char, cellstr, or numeric right-hand side %!test %! a = string ({'a', 'b', 'c'}); %! a(1) = 'Y'; %! a(2) = {'CC'}; %! a(3) = 5; %! assert_equal (a, string ({'Y', 'CC', '5'})); ## subsasgn: '()' assignment of a missing value sets the missing mask %!test %! a = string ({'a', 'b', 'c'}); %! a(2) = string (NaN); %! assert_equal (ismissing (a), [false, true, false]); %! assert_equal (cellstr (a), {'a', '', 'c'}); ## subsasgn: '()' assignment past the end grows the array, filling the gap ## elements with (MATLAB-confirmed, forum probe) %!test %! a = string ({'a', 'b'}); %! a(4) = string ('x'); %! assert_equal (size (a), [1, 4]); %! assert_equal (ismissing (a), [false, false, true, false]); %! assert_equal (cellstr (a), {'a', 'b', '', 'x'}); ## subsasgn: growing with a char-vector right-hand side also fills %!test %! b = string ({'a', 'b', 'c'}); %! b(5) = 'z'; %! assert_equal (ismissing (b), [false, false, false, true, false]); %! assert_equal (cellstr (b), {'a', 'b', 'c', '', 'z'}); ## subsasgn: a two-dimensional grow fills every new cell with %!test %! c = string ({'a', 'b'; 'c', 'd'}); %! c(3, 3) = string ('z'); %! assert_equal (size (c), [3, 3]); %! assert_equal (ismissing (c), ... %! [false, false, true; false, false, true; true, true, false]); ## subsasgn: '()' assignment of '[]' deletes the indexed elements %!test %! a = string ({'a', 'b', 'c', 'd'}); %! a(2) = []; %! assert_equal (a, string ({'a', 'c', 'd'})); ## subsasgn: '{}' assigns a single element from a character vector %!test %! a = string ({'a', 'b', 'c'}); %! a{2} = 'QQ'; %! assert_equal (a, string ({'a', 'QQ', 'c'})); ## subsasgn: '{}' assignment clears a previously-missing element %!test %! a = string ({'a', missing}); %! a{2} = 'now'; %! assert_equal (ismissing (a), [false, false]); %! assert_equal (a, string ({'a', 'now'})); ## subsasgn: '{}' requires a character-vector right-hand side %!error ... %! (subsasgn (string ({'a', 'b'}), substruct ('{}', {2}), 5)) ## subsasgn: '{}' is restricted to a single (scalar) element %!error ... %! (subsasgn (string ({'a', 'b'}), substruct ('{}', {[1, 2]}), 'x')) ## subsasgn: '.' indexing for assignment is an error %!error ... %! (subsasgn (string ({'a', 'b'}), substruct ('.', 'foo'), 1)) ################################################################################ ## ** Overloaded methods for duration and categorical classes ** ## ################################################################################ ## Available Methods ## ## ## ## 'colon' 'linspace' 'intersect' 'setdiff' ## ## 'setxor' 'union' 'isequal' 'isequaln' ## ## ## ################################################################################ ## colon: a duration operand makes the string range delegate to duration %!assert_equal (class (string ('1:00:00'):hours (5)), 'duration') ## colon: without a duration (or categorical) operand it is an error %!error ... %! colon (string ('a'), string ('b')) ## linspace: a duration endpoint makes the result a duration array %!assert_equal (class (linspace (string ('0:00:00'), hours (5), 6)), 'duration') ## linspace: a non-duration second argument is an error %!error ... %! linspace (string ('a'), string ('b'), 5) ## intersect: with a categorical it returns the common elements (categorical) %!test %! C = intersect (string ({'a', 'b', 'c'}), categorical ({'b', 'c', 'd'})); %! assert_equal (class (C), 'categorical'); %! assert_equal (cellstr (C), {'b', 'c'}); ## intersect: a non-duration, non-categorical second argument is an error %!error ... %! intersect (string ({'a'}), string ({'b'})) ## setdiff: with a categorical it returns the elements of A that are not in B %!test %! C = setdiff (string ({'a', 'b', 'c'}), categorical ({'b'})); %! assert_equal (class (C), 'categorical'); %! assert_equal (cellstr (C), {'a', 'c'}); ## setdiff: a non-duration, non-categorical second argument is an error %!error ... %! setdiff (string ({'a'}), string ({'b'})) ## setxor: with a categorical it returns the symmetric difference %!test %! C = setxor (string ({'a', 'b'}), categorical ({'b', 'c'})); %! assert_equal (class (C), 'categorical'); %! assert_equal (cellstr (C), {'a', 'c'}); ## setxor: a non-duration, non-categorical second argument is an error %!error ... %! setxor (string ({'a'}), string ({'b'})) ## union: with a categorical it returns the combined unique elements %!test %! C = union (string ({'a', 'b'}), categorical ({'b', 'c'})); %! assert_equal (class (C), 'categorical'); %! assert_equal (cellstr (C), {'a', 'b', 'c'}); ## union: a non-duration, non-categorical second argument is an error %!error ... %! union (string ({'a'}), string ({'b'})) ## isequal: true for equal string arrays, false otherwise %!assert_equal (isequal (string ({'a', 'b'}), string ({'a', 'b'})), true) %!assert_equal (isequal (string ({'a', 'b'}), string ({'a', 'c'})), false) ## isequal: a missing element is never equal, even to another missing %!assert_equal (isequal (string ({'a', missing}), ... %! string ({'a', missing})), false) ## isequal: accepts more than two string arrays %!assert_equal (isequal (string ('a'), string ('a'), string ('a')), true) ## isequal: a categorical operand delegates to categorical isequal %!assert_equal (isequal (string ({'a', 'b'}), categorical ({'a', 'b'})), true) ## isequal: a non-string, non-categorical operand is an error %!error ... %! isequal (string ({'a'}), 5) ## isequaln: like isequal but treats same-position missing values as equal %!assert_equal (isequaln (string ({'a', missing}), ... %! string ({'a', missing})), true) %!assert_equal (isequaln (string ({'a', missing}), string ({'a', 'b'})), false) ## isequaln: true for equal string arrays %!assert_equal (isequaln (string ({'a', 'b'}), string ({'a', 'b'})), true) ## isequaln: a categorical operand delegates to categorical isequaln %!assert_equal (isequaln (string ({'a', 'b'}), categorical ({'a', 'b'})), true) ## isequaln: a non-string, non-categorical operand is an error %!error ... %! isequaln (string ({'a'}), 5) pr0m1th3as-datatypes-9c9a8d3/inst/tests/table.m-tst000066400000000000000000011253711522766574100222630ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the datatypes package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ################################################################################ ## ** Create Table and Convert Type ** ## ################################################################################ ## Available Methods ## ## ## ## 'table' 'table2array' 'table2cell' 'table2struct' ## ## ## ################################################################################ ## Test the constructor %!test # variables become table columns; names auto-detected from inputname %! Name = {"John"; "Mary"; "Peter"; "Barbara"}; %! Age = [23; 34; 42; 28]; %! Height = [167; 163; 183; 178]; %! T = table (Name, Age, Height); %! assert_equal (size (T), [4, 3]); %! assert_equal (size (T.Age), [4, 1]); %! assert_equal (mean (T.Age), 31.75); %! assert_equal (T.Properties.VariableNames, {"Name", "Age", "Height"}); %!test # an empty constructor yields a 0x0 table %! T = table (); %! assert_equal (size (T), [0, 0]); %! assert_equal (height (T), 0); %! assert_equal (width (T), 0); %! assert_equal (class (T), "table"); %! assert_equal (istable (T), true); %!test # a single variable gives a one-variable table with an auto name %! x = [1; 2; 3]; %! T = table (x); %! assert_equal (size (T), [3, 1]); %! assert_equal (T.Properties.VariableNames, {"x"}); %! assert_equal (T.x, [1; 2; 3]); %!test # an unnamed expression falls back to 'Var' names %! T = table ([1; 2], [3; 4]); %! assert_equal (T.Properties.VariableNames, {"Var1", "Var2"}); %!test # a multicolumn variable counts as a single table variable %! T = table ([1, 2; 3, 4; 5, 6], 'VariableNames', {'bp'}); %! assert_equal (size (T), [3, 1]); %! assert_equal (width (T), 1); %! assert_equal (size (T.bp), [3, 2]); %!test # 'VariableNames' overrides the detected names %! Name = {"John"; "Mary"; "Peter"; "Barbara"}; %! Age = [23; 34; 42; 28]; %! Height = [167; 163; 183; 178]; %! T = table (Name, Age, Height, "VariableNames", {"A", "B", "C"}); %! assert_equal (T.Properties.VariableNames, {"A", "B", "C"}); %!test # 'VariableNames' may be given as a string array %! T = table (1, 2, 'VariableNames', string ({'AA', 'BB'})); %! assert_equal (T.Properties.VariableNames, {'AA', 'BB'}); %! assert_equal (class (T.Properties.VariableNames), 'cell'); %!test # a multicolumn variable is kept intact %! Name = {"John"; "Mary"; "Peter"; "Barbara"}; %! Age = [23; 34; 42; 28]; %! Height = [167; 163; 183; 178]; %! BloodPressure = [114, 83; 119, 75; 115, 73; 107, 80]; %! T = table (Name, Age, Height, BloodPressure); %! assert_equal (size (T), [4, 4]); %! assert_equal (size (T.BloodPressure), [4, 2]); %!test # 'RowNames' labels the rows without adding a variable %! T = table ([1; 2; 3], 'VariableNames', {'x'}, ... %! 'RowNames', {'a'; 'b'; 'c'}); %! assert_equal (size (T), [3, 1]); %! assert_equal (T.Properties.RowNames, {'a'; 'b'; 'c'}); %!test # 'RowNames' may be given as a string array %! T = table ([1; 2], 'VariableNames', {'x'}, 'RowNames', string ({'a', 'b'})); %! assert_equal (T.Properties.RowNames, {'a'; 'b'}); %!test # 'DimensionNames' sets the row/variable dimension labels %! T = table ([1; 2], 'VariableNames', {'x'}, ... %! 'DimensionNames', {'Samples', 'Measures'}); %! assert_equal (T.Properties.DimensionNames, {'Samples', 'Measures'}); %!test # the default dimension names are 'Row' and 'Variables' %! T = table ([1; 2]); %! assert_equal (T.Properties.DimensionNames, {'Row', 'Variables'}); %!test # heterogeneous variable types are preserved %! T = table ([1; 2], logical ([1; 0]), {'a'; 'b'}, string ({'c'; 'd'}), ... %! 'VariableNames', {'n', 'l', 'c', 's'}); %! assert_equal (class (T.n), 'double'); %! assert_equal (class (T.l), 'logical'); %! assert_equal (class (T.c), 'cell'); %! assert_equal (class (T.s), 'string'); %!test # variables with zero rows are allowed %! T = table (zeros (0, 1), cell (0, 1), 'VariableNames', {'a', 'b'}); %! assert_equal (size (T), [0, 2]); %! assert_equal (height (T), 0); %! assert_equal (width (T), 2); %!test # 'VariableDescriptions' and 'VariableUnits' default to empty %! T = table ([1; 2], [3; 4], 'VariableNames', {'a', 'b'}); %! assert_equal (T.Properties.VariableDescriptions, {'', ''}); %! assert_equal (T.Properties.VariableUnits, {'', ''}); %!test # a nested table is accepted as a variable %! T = table ([1; 2], table ([3; 4], [5; 6]), 'VariableNames', {'a', 'b'}); %! assert_equal (width (T), 2); %! assert_equal (class (T.b), 'table'); ## Test the constructor -- 'Size'/'VariableTypes' preallocation %!test # preallocates a table of the requested size with default values %! T = table ('Size', [4, 3], 'VariableTypes', {'double', 'datetime', 'string'}); %! assert_equal (size (T), [4, 3]); %! assert_equal (T.Properties.VariableNames, {'Var1', 'Var2', 'Var3'}); %!test # 'VariableNames' names the preallocated variables %! T = table ('Size', [4, 3], 'VariableTypes', {'double', 'datetime', 'string'}, ... %! 'VariableNames', {'A', 'B', 'C'}); %! assert_equal (T.Properties.VariableNames, {'A', 'B', 'C'}); %!test # numeric and logical types default to zeros/false %! T = table ('Size', [2, 3], 'VariableTypes', {'double', 'int8', 'logical'}); %! assert_equal (T.Var1, [0; 0]); %! assert_equal (T.Var2, int8 ([0; 0])); %! assert_equal (class (T.Var2), 'int8'); %! assert_equal (T.Var3, [false; false]); %!test # the 'doublenan'/'singlenan' types default to NaN %! T = table ('Size', [3, 2], 'VariableTypes', {'doublenan', 'singlenan'}); %! assert_equal (all (isnan (T.Var1)), true); %! assert_equal (class (T.Var2), 'single'); %! assert_equal (all (isnan (T.Var2)), true); %!test # categorical, datetime, duration and string default to missing %! T = table ('Size', [2, 4], 'VariableTypes', ... %! {'categorical', 'datetime', 'duration', 'string'}); %! assert_equal (all (ismissing (T.Var1)), true); %! assert_equal (all (isnat (T.Var2)), true); %! assert_equal (seconds (T.Var3), [0; 0]); %! assert_equal (all (ismissing (T.Var4)), true); %!test # cellstr, cell and struct types preallocate accordingly %! T = table ('Size', [3, 3], 'VariableTypes', {'cellstr', 'cell', 'struct'}); %! assert_equal (iscellstr (T.Var1), true); %! assert_equal (T.Var1, {''; ''; ''}); %! assert_equal (class (T.Var2), 'cell'); %! assert_equal (class (T.Var3), 'struct'); %!test # the 'char' type preallocates a cell array of empty character vectors %! T = table ('Size', [3, 1], 'VariableTypes', {'char'}); %! assert_equal (class (T.Var1), 'cell'); %! assert_equal (iscellstr (T.Var1), true); %! assert_equal (T.Var1, {''; ''; ''}); %!test # a 'table' typed variable preallocates an empty nested table %! T = table ('Size', [4, 3], 'VariableTypes', {'double', 'datetime', 'table'}, ... %! 'VariableNames', {'A', 'B', 'C'}); %! assert_equal (size (T), [4, 3]); %! assert_equal (class (T.A), 'double'); %! assert_equal (class (T.B), 'datetime'); %! assert_equal (class (T.C), 'table'); %! assert_equal (size (T.C), [0, 1]); %!test # 'RowNames' labels the preallocated rows %! T = table ('Size', [3, 1], 'VariableTypes', {'double'}, ... %! 'RowNames', {'a'; 'b'; 'c'}); %! assert_equal (T.Properties.RowNames, {'a'; 'b'; 'c'}); ## Test input validation for the constructor %!error ... %! table ([1; 2], [3; 4], "VariableNames", {2, 3}); %!error ... %! table ([1; 2], [3; 4], "VariableNames", [2, 3]); %!error ... %! table ([1; 2], [3; 4], "VariableNames", [true, true]); %!error ... %! table ([1; 2], [3; 4], "VariableNames", {"", "A"}); %!error ... %! table ([1; 2], [3; 4], "RowNames", {2, 3}); %!error ... %! table ([1; 2], [3; 4], "RowNames", [2, 3]); %!error ... %! table ([1; 2], [3; 4], "RowNames", [true, false]); %!error ... %! table ([1; 2], [3; 4], "DimensionNames", {2, 3}); %!error ... %! table ([1; 2], [3; 4], "DimensionNames", [2, 3]); %!error ... %! table ([1; 2], [3; 4], "DimensionNames", [true, false]); %!error ... %! table ([1; 2], [3; 4], "DimensionNames", {"A", "B", "C"}); %!error ... %! table (1, "DimensionNames", {"Properties", "Variables"}); %!error ... %! table (1, "DimensionNames", {"VariableNames", "Row"}); %!error ... %! table (1, 'VariableNames', {'Variables'}); %!error ... %! table ("Size", [4, 2], "VariableTypes", {"A", "B", "C"}); %!error ... %! table ("Size", [4, 3], "VariableTypes", ["A", "B", "C"]); %!error ... %! table ("Size", [4, 3], "VariableTypes", {1, 2, 3}); %!error ... %! table ("Size", [4, 3], "VariableTypes", {"double", "double", "logical"}, ... %! "VariableNames", {"A", "B"}); %!error ... %! table ("Size", [4, 3], "VariableTypes", {"double", "double", "logical"}, ... %! "RowNames", {"A", "B", "C"}); %!error ... %! table ("Size", [4, 1], "VariableTypes", {"uint128"}); %!error ... %! table ("Size", 3, "VariableTypes", {"double"}); %!error ... %! table ("Size", [3, 1, 2], "VariableTypes", {"double"}); %!error ... %! table ("Size", [4, 3], "VariableTypes", {"double", "double", "timetable"}, ... %! "VariableNames", {"A", "B", "C"}); %!error ... %! table ("Size", [4, 3], "VariableTypes", {"double", "double", "timetable"}); %!error ... %! Age = [1; 2]; %! table (Age, Age); %!error ... %! Age = [1; 2]; %! Height = [3; 4]; %! table (Age, Age, Height, Height); %!error ... %! table ([1; 2], [3; 4], "VariableNames", {"A"}); %!error ... %! table (ones (2, 2, 2), [1; 2], "VariableNames", {"A", "B"}); %!error ... %! table ([1; 2], ones (2, 2, 2), "VariableNames", {"A", "B"}); %!error ... %! table (struct ("age", 1), [1; 2; 3; 4; 5], "VariableNames", {"A", "B"}); %!error ... %! table ([1; 2; 3; 4; 5], struct ("age", 1), "VariableNames", {"A", "B"}); %!error ... %! table ("Size", [4, 2], "VariableTypes", {"double", "double"}, ... %! "RowNames", {"A", "B", "B", "D"}); %!error ... %! table ([1; 2; 3], "RowNames", {"a", "b"}); %!shared LastName, Age, Smoker, Height, Weight, BloodPressure, T, tblA %! LastName = {"Sanchez"; "Johnson"; "Li"; "Diaz"; "Brown"}; %! Age = [38; 43; 38; 40; 49]; %! Smoker = logical ([1; 0; 1; 0; 1]); %! Height = [71; 69; 64; 67; 64]; %! Weight = [176; 163; 131; 133; 119]; %! BloodPressure = [124, 93; 109, 77; 125, 83; 117, 75; 122, 80]; %! T = table (Age, Smoker, Height, Weight, BloodPressure); ## Test 'table2array' method %!test # the canonical patient table concatenates into a numeric matrix %! assert_equal (table2array (T), [Age, Smoker, Height, Weight, BloodPressure]); %!test # single-column numeric variables concatenate into a matrix %! A = table ([1; 2; 3], [4; 5; 6], 'VariableNames', {'a', 'b'}); %! assert_equal (table2array (A), [1, 4; 2, 5; 3, 6]); %!test # multicolumn variables expand into multiple columns %! A = table ([1, 2; 3, 4], [5; 6], 'VariableNames', {'a', 'b'}); %! M = table2array (A); %! assert_equal (size (M), [2, 3]); %! assert_equal (M, [1, 2, 5; 3, 4, 6]); %!test # a single variable is returned as-is %! M = table2array (table ([1; 2; 3], 'VariableNames', {'x'})); %! assert_equal (M, [1; 2; 3]); %! assert_equal (class (M), 'double'); %!test # the result takes the dominant numeric type (double + single -> single) %! M = table2array (table ([1; 2], single ([3; 4]), 'VariableNames', {'a', 'b'})); %! assert_equal (class (M), 'single'); %!test # an integer variable dominates a double variable %! M = table2array (table (int8 ([1; 2]), [3; 4], 'VariableNames', {'a', 'b'})); %! assert_equal (class (M), 'int8'); %!test # a logical variable widens to double %! M = table2array (table (logical ([1; 0]), [3; 4], 'VariableNames', {'a', 'b'})); %! assert_equal (class (M), 'double'); %!test # homogeneous string variables yield a string array %! M = table2array (table (string ({'a'; 'b'}), string ({'c'; 'd'}), ... %! 'VariableNames', {'x', 'y'})); %! assert_equal (class (M), 'string'); %! assert_equal (size (M), [2, 2]); %!test # homogeneous categorical variables yield a categorical array %! M = table2array (table (categorical ({'a'; 'b'}), categorical ({'c'; 'd'}), ... %! 'VariableNames', {'x', 'y'})); %! assert_equal (class (M), 'categorical'); %! assert_equal (size (M), [2, 2]); %!test # homogeneous datetime variables yield a datetime array %! d = datetime (2020, 1, [1; 2]); %! M = table2array (table (d, d, 'VariableNames', {'x', 'y'})); %! assert_equal (class (M), 'datetime'); %! assert_equal (size (M), [2, 2]); %!test # an empty table returns an empty array %! assert_equal (table2array (table ()), []); %!test # homogeneous cell variables yield a cell array %! M = table2array (table ({'a'; 'b'}, {'c'; 'd'}, 'VariableNames', {'x', 'y'})); %! assert_equal (class (M), 'cell'); %! assert_equal (size (M), [2, 2]); ## Test input validation for 'table2array' method: a mix of cell and non-cell ## variables cannot form a homogeneous array (matches MATLAB, names the pair). %!error ... %! table2array (table ([1; 2; 3; 4; 5], {1; 2; 3; 4; 5}, "VariableNames", {"A", "B"})); ## Test 'table2cell' method %!test # the output cell array has the same size as the table %! A = table ([1; 2; 3], {'a'; 'b'; 'c'}, 'VariableNames', {'x', 'y'}); %! C = table2cell (A); %! assert_equal (size (C), [3, 2]); %! assert_equal (C{1, 1}, 1); %! assert_equal (C{1, 2}, 'a'); %!test # the patient table round-trips its numeric block %! C = table2cell (T); %! assert_equal (cell2mat (C(:, [1:4])), [Age, Smoker, Height, Weight]); %!test # a multicolumn variable becomes a row vector in a single cell %! C = table2cell (table ([1, 2; 3, 4], 'VariableNames', {'bp'})); %! assert_equal (size (C), [2, 1]); %! assert_equal (C{1}, [1, 2]); %! assert_equal (C{2}, [3, 4]); %!test # a cellstr variable keeps its char contents %! C = table2cell (table ({'a'; 'b'}, 'VariableNames', {'y'})); %! assert_equal (C{1}, 'a'); %! assert_equal (C{2}, 'b'); %!test # a logical variable is placed scalar-wise %! C = table2cell (table (logical ([1; 0]), 'VariableNames', {'l'})); %! assert_equal (C{1}, true); %! assert_equal (C{2}, false); %!test # a string variable is returned as character vectors %! C = table2cell (table (string ({'a'; 'b'}), 'VariableNames', {'s'})); %! assert_equal (C{1}, 'a'); %! assert_equal (class (C{1}), 'char'); %!test # a categorical variable is returned as character vectors %! C = table2cell (table (categorical ({'lo'; 'hi'}), 'VariableNames', {'c'})); %! assert_equal (C{1}, 'lo'); %! assert_equal (C{2}, 'hi'); %!test # a datetime variable is returned as character vectors %! C = table2cell (table (datetime (2020, 1, [1; 2]), 'VariableNames', {'d'})); %! assert_equal (C{1}, '01-Jan-2020'); %! assert_equal (class (C{1}), 'char'); %!test # a duration variable is returned as character vectors %! C = table2cell (table (days ([1.5; 2.5]), 'VariableNames', {'d'})); %! assert_equal (C{1}, '1.5 days'); %!test # a struct variable yields one struct per cell %! A = table ([struct('x', 1); struct('x', 2)], 'VariableNames', {'s'}); %! C = table2cell (A); %! assert_equal (size (C), [2, 1]); %! assert_equal (class (C{1}), 'struct'); %! assert_equal (C{2}.x, 2); %!test # row names are not included in the output %! A = table ([1; 2], 'VariableNames', {'x'}, 'RowNames', {'r1'; 'r2'}); %! C = table2cell (A); %! assert_equal (size (C), [2, 1]); %! assert_equal (C{1}, 1); ## Test 'table2struct' method %!test # default output is an m-by-1 struct array, one element per row %! A = table ([1; 2; 3], [4; 5; 6], 'VariableNames', {'x', 'y'}); %! S = table2struct (A); %! assert_equal (size (S), [3, 1]); %! assert_equal (numel (S), 3); %! assert_equal (fieldnames (S), {'x'; 'y'}); %! assert_equal ([S.x], [1, 2, 3]); %! assert_equal (S(2).y, 5); %!test # the patient table maps each row to a struct element %! S = table2struct (T); %! assert_equal ([S.Age], Age'); %! assert_equal (reshape ([S.BloodPressure], 2, 5)', BloodPressure); %! assert_equal (numel (S), height (T)); %!test # a multicolumn variable becomes a row vector field %! A = table ([1; 2], [3, 4; 5, 6], 'VariableNames', {'a', 'bp'}); %! S = table2struct (A); %! assert_equal (S(1).bp, [3, 4]); %! assert_equal (S(2).bp, [5, 6]); %!test # the struct array preserves object variable types %! d = datetime (2020, 1, [1; 2]); %! c = categorical ({'lo'; 'hi'}); %! S = table2struct (table (d, c, 'VariableNames', {'D', 'C'})); %! assert_equal (class (S(1).D), 'datetime'); %! assert_equal (class (S(1).C), 'categorical'); %! assert_equal (cellstr (S(2).C), {'hi'}); %!test # 'ToScalar' true returns one struct with whole-column fields %! A = table ([1; 2; 3], [4; 5; 6], 'VariableNames', {'x', 'y'}); %! S = table2struct (A, 'ToScalar', true); %! assert_equal (numel (S), 1); %! assert_equal (S.x, [1; 2; 3]); %! assert_equal (S.y, [4; 5; 6]); %!test # 'ToScalar' preserves object variable types %! d = datetime (2020, 1, [1; 2]); %! S = table2struct (table (d, categorical ({'a'; 'b'}), ... %! 'VariableNames', {'D', 'C'}), 'ToScalar', true); %! assert_equal (class (S.D), 'datetime'); %! assert_equal (class (S.C), 'categorical'); %!test # 'ToScalar' also accepts a numeric 1 for true %! S = table2struct (table ([1; 2], 'VariableNames', {'x'}), 'ToScalar', 1); %! assert_equal (numel (S), 1); %! assert_equal (S.x, [1; 2]); %!test # 'ToScalar' false gives the default struct array %! S = table2struct (table ([1; 2], 'VariableNames', {'x'}), 'ToScalar', false); %! assert_equal (numel (S), 2); %!test # row names are not carried into the struct %! A = table ([1; 2], 'VariableNames', {'x'}, 'RowNames', {'r1'; 'r2'}); %! S = table2struct (A); %! assert_equal (fieldnames (S), {'x'}); ## Test input validation for 'table2struct' method %!error ... %! table2struct (table (1), "ToScalar"); %!error ... %! table2struct (table (1), "Scalar", 1); ################################################################################ ## ** Save to Files ** ## ################################################################################ ## Available Methods ## ## ## ## 'table2csv' ## ## ## ################################################################################ ## Test 'table2csv' method (round-trips through 'csv2table') %!test # numeric, cellstr, and double variables round-trip by value and name %! T = table ([1; 2; 3], {'a'; 'b'; 'c'}, [1.5; 2.5; 3.5], ... %! 'VariableNames', {'id', 'name', 'val'}); %! T.Properties.VariableDescriptions = {'the id', 'the name', 'the value'}; %! T.Properties.VariableUnits = {'#', 'str', 'kg'}; %! fn = [tempname() '.csv']; %! unwind_protect %! table2csv (T, fn); %! R = csv2table (fn); %! assert_equal (R.Properties.VariableNames, {'id', 'name', 'val'}); %! assert_equal (R.id, [1; 2; 3]); %! assert_equal (R.val, [1.5; 2.5; 3.5]); %! assert_equal (R.name, {'a'; 'b'; 'c'}); %! assert_equal (R.Properties.VariableDescriptions, {'the id', 'the name', 'the value'}); %! assert_equal (R.Properties.VariableUnits, {'#', 'str', 'kg'}); %! unwind_protect_cleanup %! if (exist (fn, 'file')) %! delete (fn); %! endif %! end_unwind_protect %!test # integer and single variables preserve their class through the vartype row %! T = table (int8 ([1; 2]), single ([1.5; 2.5]), uint16 ([10; 20]), ... %! 'VariableNames', {'a', 'b', 'c'}); %! T.Properties.VariableDescriptions = {'sbyte', 'real', 'ushort'}; %! T.Properties.VariableUnits = {'au', 'au', 'au'}; %! fn = [tempname() '.csv']; %! unwind_protect %! table2csv (T, fn); %! R = csv2table (fn); %! assert_equal (class (R.a), 'int8'); %! assert_equal (class (R.b), 'single'); %! assert_equal (class (R.c), 'uint16'); %! assert_equal (R.a, int8 ([1; 2])); %! assert_equal (R.b, single ([1.5; 2.5])); %! assert_equal (R.Properties.VariableDescriptions, {'sbyte', 'real', 'ushort'}); %! assert_equal (R.Properties.VariableUnits, {'au', 'au', 'au'}); %! unwind_protect_cleanup %! if (exist (fn, 'file')) %! delete (fn); %! endif %! end_unwind_protect %!test # a logical variable round-trips as a logical column %! T = table ([true; false; true], 'VariableNames', {'flag'}); %! T.Properties.VariableDescriptions = {'a flag'}; %! T.Properties.VariableUnits = {'bool'}; %! fn = [tempname() '.csv']; %! unwind_protect %! table2csv (T, fn); %! R = csv2table (fn); %! assert_equal (class (R.flag), 'logical'); %! assert_equal (R.flag, [true; false; true]); %! assert_equal (R.Properties.VariableDescriptions, {'a flag'}); %! assert_equal (R.Properties.VariableUnits, {'bool'}); %! unwind_protect_cleanup %! if (exist (fn, 'file')) %! delete (fn); %! endif %! end_unwind_protect %!test # a multicolumn variable splits into same-named columns and is rejoined %! T = table ([1, 2; 3, 4], 'VariableNames', {'M'}); %! T.Properties.VariableDescriptions = {'a pair'}; %! T.Properties.VariableUnits = {'cm'}; %! fn = [tempname() '.csv']; %! unwind_protect %! table2csv (T, fn); %! R = csv2table (fn); %! assert_equal (width (R), 1); %! assert_equal (R.M, [1, 2; 3, 4]); %! assert_equal (R.Properties.VariableDescriptions, {'a pair'}); %! assert_equal (R.Properties.VariableUnits, {'cm'}); %! unwind_protect_cleanup %! if (exist (fn, 'file')) %! delete (fn); %! endif %! end_unwind_protect %!test # NA and NaN are written as distinct tokens and round-trip through csv2table %! T = table ([1; NA; NaN], 'VariableNames', {'v'}); %! T.Properties.VariableDescriptions = {'measurement'}; %! T.Properties.VariableUnits = {'mm'}; %! fn = [tempname() '.csv']; %! unwind_protect %! table2csv (T, fn); %! txt = fileread (fn); %! assert_equal (! isempty (regexp (txt, '^NA$', 'lineanchors', 'once')), true); %! assert_equal (! isempty (regexp (txt, '^NaN$', 'lineanchors', 'once')), true); %! R = csv2table (fn); %! assert_equal (class (R.v), 'double'); %! assert_equal (R.v(1), 1); %! assert_equal (isna (R.v'), [false, true, false]); %! assert_equal (isnan (R.v'), [false, true, true]); %! assert_equal (R.Properties.VariableDescriptions, {'measurement'}); %! assert_equal (R.Properties.VariableUnits, {'mm'}); %! unwind_protect_cleanup %! if (exist (fn, 'file')) %! delete (fn); %! endif %! end_unwind_protect %!test # a string variable round-trips as a string column %! T = table (string ({'a'; 'b'}), 'VariableNames', {'s'}); %! T.Properties.VariableDescriptions = {'a label'}; %! T.Properties.VariableUnits = {'txt'}; %! fn = [tempname() '.csv']; %! unwind_protect %! table2csv (T, fn); %! R = csv2table (fn); %! assert_equal (class (R.s), 'string'); %! assert_equal (cellstr (R.s), {'a'; 'b'}); %! assert_equal (R.Properties.VariableDescriptions, {'a label'}); %! assert_equal (R.Properties.VariableUnits, {'txt'}); %! unwind_protect_cleanup %! if (exist (fn, 'file')) %! delete (fn); %! endif %! end_unwind_protect %!test # a datetime variable round-trips through its display strings %! T = table (datetime (2020, 1, [1; 2; 3]), 'VariableNames', {'d'}); %! T.Properties.VariableDescriptions = {'a date'}; %! T.Properties.VariableUnits = {'cal'}; %! fn = [tempname() '.csv']; %! unwind_protect %! table2csv (T, fn); %! R = csv2table (fn); %! assert_equal (class (R.d), 'datetime'); %! assert_equal (cellstr (R.d), {'01-Jan-2020'; '02-Jan-2020'; '03-Jan-2020'}); %! assert_equal (R.Properties.VariableDescriptions, {'a date'}); %! assert_equal (R.Properties.VariableUnits, {'cal'}); %! unwind_protect_cleanup %! if (exist (fn, 'file')) %! delete (fn); %! endif %! end_unwind_protect %!test # a duration variable round-trips through its display strings %! T = table (duration ([1; 2], [30; 45], [0; 0]), 'VariableNames', {'du'}); %! T.Properties.VariableDescriptions = {'elapsed'}; %! T.Properties.VariableUnits = {'hms'}; %! fn = [tempname() '.csv']; %! unwind_protect %! table2csv (T, fn); %! R = csv2table (fn); %! assert_equal (class (R.du), 'duration'); %! assert_equal (cellstr (R.du), {'01:30:00'; '02:45:00'}); %! assert_equal (R.Properties.VariableDescriptions, {'elapsed'}); %! assert_equal (R.Properties.VariableUnits, {'hms'}); %! unwind_protect_cleanup %! if (exist (fn, 'file')) %! delete (fn); %! endif %! end_unwind_protect %!test # a calendarDuration is written as text; csv2table reads it back as cellstr (lossy) %! T = table (calendarDuration ([1; 2], [0; 1], [0; 0]), 'VariableNames', {'cd'}); %! T.Properties.VariableDescriptions = {'span'}; %! T.Properties.VariableUnits = {'ymd'}; %! fn = [tempname() '.csv']; %! unwind_protect %! table2csv (T, fn); %! txt = fileread (fn); %! assert_equal (! isempty (regexp (txt, '^"calendarDuration"$', 'lineanchors', 'once')), true); %! assert_equal (! isempty (regexp (txt, '^"1y"$', 'lineanchors', 'once')), true); %! assert_equal (! isempty (regexp (txt, '^"2y 1mo"$', 'lineanchors', 'once')), true); %! warning ('off', 'all', 'local'); %! R = csv2table (fn); %! assert_equal (R.cd, {'1y'; '2y 1mo'}); %! assert_equal (R.Properties.VariableDescriptions, {'span'}); %! assert_equal (R.Properties.VariableUnits, {'ymd'}); %! unwind_protect_cleanup %! if (exist (fn, 'file')) %! delete (fn); %! endif %! end_unwind_protect ## Test warning here %!warning ... %! T = table (calendarDuration ([1; 2], [0; 1], [0; 0]), 'VariableNames', {'cd'}); %! T.Properties.VariableDescriptions = {'span'}; %! T.Properties.VariableUnits = {'ymd'}; %! fn = [tempname() '.csv']; %! table2csv (T, fn); %! R = csv2table (fn); %!test # a categorical is written as labels; csv2table reads it back as cellstr (lossy) %! T = table (categorical ({'lo'; 'hi'; 'lo'}), 'VariableNames', {'c'}); %! T.Properties.VariableDescriptions = {'level'}; %! T.Properties.VariableUnits = {'cat'}; %! fn = [tempname() '.csv']; %! unwind_protect %! table2csv (T, fn); %! txt = fileread (fn); %! assert_equal (! isempty (regexp (txt, '^"categorical"$', 'lineanchors', 'once')), true); %! assert_equal (! isempty (regexp (txt, '^"lo"$', 'lineanchors', 'once')), true); %! assert_equal (! isempty (regexp (txt, '^"hi"$', 'lineanchors', 'once')), true); %! warning ('off', 'all', 'local'); %! R = csv2table (fn); %! assert_equal (R.c, {'lo'; 'hi'; 'lo'}); %! assert_equal (R.Properties.VariableDescriptions, {'level'}); %! assert_equal (R.Properties.VariableUnits, {'cat'}); %! unwind_protect_cleanup %! if (exist (fn, 'file')) %! delete (fn); %! endif %! end_unwind_protect ## Test warning here %!warning ... %! T = table (categorical ({'lo'; 'hi'; 'lo'}), 'VariableNames', {'c'}); %! T.Properties.VariableDescriptions = {'level'}; %! T.Properties.VariableUnits = {'cat'}; %! fn = [tempname() '.csv']; %! table2csv (T, fn); %! R = csv2table (fn); %!test # row names are written under a 'RowNames' column and restored %! T = table ([1; 2], 'VariableNames', {'v'}, 'RowNames', {'r1'; 'r2'}); %! T.Properties.VariableDescriptions = {'a value'}; %! T.Properties.VariableUnits = {'kg'}; %! fn = [tempname() '.csv']; %! unwind_protect %! table2csv (T, fn); %! R = csv2table (fn); %! assert_equal (R.Properties.RowNames, {'r1'; 'r2'}); %! assert_equal (R.v, [1; 2]); %! assert_equal (R.Properties.VariableDescriptions, {'a value'}); %! assert_equal (R.Properties.VariableUnits, {'kg'}); %! unwind_protect_cleanup %! if (exist (fn, 'file')) %! delete (fn); %! endif %! end_unwind_protect %!test # a nested table is split with two vartype rows and rebuilt on round-trip %! nt = table ([10; 20], [30; 40], 'VariableNames', {'p', 'q'}); %! T = table ([1; 2], nt, 'VariableNames', {'id', 'nest'}); %! fn = [tempname() '.csv']; %! unwind_protect %! table2csv (T, fn); %! assert_equal (! isempty (strfind (fileread (fn), 'varTypes 2 rows')), true); %! R = csv2table (fn); %! assert_equal (class (R.nest), 'table'); %! assert_equal (R.nest.p, [10; 20]); %! assert_equal (R.nest.q, [30; 40]); %! unwind_protect_cleanup %! if (exist (fn, 'file')) %! delete (fn); %! endif %! end_unwind_protect %!test # descriptions and units of a nested table round-trip on both levels %! nt = table ([10; 20], [30; 40], 'VariableNames', {'p', 'q'}); %! nt.Properties.VariableDescriptions = {'inner p', 'inner q'}; %! nt.Properties.VariableUnits = {'mm', 'cm'}; %! T = table ([1; 2], nt, 'VariableNames', {'id', 'nest'}); %! T.Properties.VariableDescriptions = {'an id', 'a nest'}; %! T.Properties.VariableUnits = {'#', 'box'}; %! fn = [tempname() '.csv']; %! unwind_protect %! table2csv (T, fn); %! txt = fileread (fn); %! assert_equal (! isempty (strfind (txt, 'varDescriptions 2 rows; varUnits 2 rows')), true); %! R = csv2table (fn); %! assert_equal (R.Properties.VariableDescriptions, {'an id', 'a nest'}); %! assert_equal (R.Properties.VariableUnits, {'#', 'box'}); %! assert_equal (R.nest.Properties.VariableDescriptions, {'inner p', 'inner q'}); %! assert_equal (R.nest.Properties.VariableUnits, {'mm', 'cm'}); %! unwind_protect_cleanup %! if (exist (fn, 'file')) %! delete (fn); %! endif %! end_unwind_protect %!test # a struct variable is split per field and rebuilt on round-trip %! sv = struct ('x', {1; 2}, 'y', {3; 4}); %! T = table ([1; 2], sv, 'VariableNames', {'id', 's'}); %! fn = [tempname() '.csv']; %! unwind_protect %! table2csv (T, fn); %! R = csv2table (fn); %! s = R.s; %! assert_equal (class (s), 'struct'); %! assert_equal ([s.x], [1, 2]); %! assert_equal ([s.y], [3, 4]); %! unwind_protect_cleanup %! if (exist (fn, 'file')) %! delete (fn); %! endif %! end_unwind_protect %!test # the top-level description and units of a struct variable round-trip %! sv = struct ('x', {1; 2}, 'y', {3; 4}); %! T = table ([1; 2], sv, 'VariableNames', {'id', 's'}); %! T.Properties.VariableDescriptions = {'an id', 'a struct'}; %! T.Properties.VariableUnits = {'#', 'rec'}; %! fn = [tempname() '.csv']; %! unwind_protect %! table2csv (T, fn); %! R = csv2table (fn); %! assert_equal (R.Properties.VariableDescriptions, {'an id', 'a struct'}); %! assert_equal (R.Properties.VariableUnits, {'#', 'rec'}); %! assert_equal (class (R.s), 'struct'); %! unwind_protect_cleanup %! if (exist (fn, 'file')) %! delete (fn); %! endif %! end_unwind_protect %!test # descriptions are written when any variable carries one (partial) %! T = table ([1; 2], [3; 4], 'VariableNames', {'a', 'b'}); %! T.Properties.VariableDescriptions = {'has one', ''}; %! fn = [tempname() '.csv']; %! unwind_protect %! table2csv (T, fn); %! assert_equal (! isempty (strfind (fileread (fn), 'varDescriptions 1 rows')), true); %! R = csv2table (fn); %! assert_equal (R.Properties.VariableDescriptions, {'has one', ''}); %! assert_equal (R.a, [1; 2]); %! unwind_protect_cleanup %! if (exist (fn, 'file')) %! delete (fn); %! endif %! end_unwind_protect %!test # partial units round-trip through csv (any variable, not all) %! T = table ([1; 2], [3; 4], 'VariableNames', {'a', 'b'}); %! T.Properties.VariableUnits = {'', 'kg'}; %! fn = [tempname() '.csv']; %! unwind_protect %! table2csv (T, fn); %! R = csv2table (fn); %! assert_equal (R.Properties.VariableUnits, {'', 'kg'}); %! unwind_protect_cleanup %! if (exist (fn, 'file')); delete (fn); endif %! end_unwind_protect %!test # a zone-aware datetime keeps its TimeZone through csv %! dt = datetime (2024, 6, [15; 16], 10, 30, 0, 'TimeZone', 'America/New_York'); %! T = table (dt, 'VariableNames', {'t'}); %! fn = [tempname() '.csv']; %! unwind_protect %! table2csv (T, fn); %! R = csv2table (fn); %! assert_equal (R.t.TimeZone, 'America/New_York'); %! assert_equal (cellstr (char (R.t)), cellstr (char (dt))); %! unwind_protect_cleanup %! if (exist (fn, 'file')); delete (fn); endif %! end_unwind_protect %!test # variable descriptions and units are written to the header and restored %! T = table ([1; 2], 'VariableNames', {'v'}); %! T.Properties.VariableDescriptions = {'mass'}; %! T.Properties.VariableUnits = {'kg'}; %! fn = [tempname() '.csv']; %! unwind_protect %! table2csv (T, fn); %! txt = fileread (fn); %! assert_equal (! isempty (strfind (txt, 'varDescriptions 1 rows; varUnits 1 rows')), true); %! assert_equal (! isempty (regexp (txt, '^"mass"$', 'lineanchors', 'once')), true); %! assert_equal (! isempty (regexp (txt, '^"kg"$', 'lineanchors', 'once')), true); %! R = csv2table (fn); %! assert_equal (R.v, [1; 2]); %! assert_equal (R.Properties.VariableDescriptions, {'mass'}); %! assert_equal (R.Properties.VariableUnits, {'kg'}); %! unwind_protect_cleanup %! if (exist (fn, 'file')) %! delete (fn); %! endif %! end_unwind_protect %!test # FILE accepts a char vector, a cellstr, and a string scalar %! T = table ([1; 2], 'VariableNames', {'v'}); %! fn = [tempname() '.csv']; %! unwind_protect %! table2csv (T, fn); %! assert_equal (exist (fn, 'file') > 0, true); %! delete (fn); %! table2csv (T, {fn}); %! assert_equal (exist (fn, 'file') > 0, true); %! delete (fn); %! table2csv (T, string (fn)); %! assert_equal (exist (fn, 'file') > 0, true); %! unwind_protect_cleanup %! if (exist (fn, 'file')) %! delete (fn); %! endif %! end_unwind_protect %!test # a complex table of many variable types round-trips with descriptions and units %! T = table ([1; 2; 3], single ([1.5; 2.5; 3.5]), int16 ([10; 20; 30]), ... %! [true; false; true], string ({'x'; 'y'; 'z'}), ... %! datetime (2021, 3, [4; 5; 6]), ... %! duration ([1; 2; 3], [0; 30; 0], [0; 0; 0]), ... %! categorical ({'lo'; 'hi'; 'lo'}), [1, 2; 3, 4; 5, 6], ... %! 'VariableNames', ... %! {'id', 'score', 'cnt', 'ok', 'name', 'when', 'dur', 'grade', 'bp'}); %! T.Properties.VariableDescriptions = ... %! {'identifier', 'a score', 'a count', 'flag', 'a name', 'a date', ... %! 'elapsed', 'level', 'pair'}; %! T.Properties.VariableUnits = ... %! {'#', 'pts', 'n', 'bool', 'txt', 'cal', 'hms', 'cat', 'cm'}; %! fn = [tempname() '.csv']; %! unwind_protect %! table2csv (T, fn); %! warning ('off', 'all', 'local'); %! R = csv2table (fn); %! assert_equal (width (R), 9); %! assert_equal (R.id, [1; 2; 3]); %! assert_equal (class (R.score), 'single'); %! assert_equal (R.score, single ([1.5; 2.5; 3.5])); %! assert_equal (class (R.cnt), 'int16'); %! assert_equal (R.cnt, int16 ([10; 20; 30])); %! assert_equal (R.ok, [true; false; true]); %! assert_equal (class (R.name), 'string'); %! assert_equal (cellstr (R.name), {'x'; 'y'; 'z'}); %! assert_equal (class (R.when), 'datetime'); %! assert_equal (cellstr (R.when), {'04-Mar-2021'; '05-Mar-2021'; '06-Mar-2021'}); %! assert_equal (class (R.dur), 'duration'); %! assert_equal (cellstr (R.dur), {'01:00:00'; '02:30:00'; '03:00:00'}); %! assert_equal (R.grade, {'lo'; 'hi'; 'lo'}); %! assert_equal (R.bp, [1, 2; 3, 4; 5, 6]); %! assert_equal (R.Properties.VariableNames, ... %! {'id', 'score', 'cnt', 'ok', 'name', 'when', 'dur', 'grade', 'bp'}); %! assert_equal (R.Properties.VariableDescriptions, ... %! {'identifier', 'a score', 'a count', 'flag', 'a name', ... %! 'a date', 'elapsed', 'level', 'pair'}); %! assert_equal (R.Properties.VariableUnits, ... %! {'#', 'pts', 'n', 'bool', 'txt', 'cal', 'hms', 'cat', 'cm'}); %! unwind_protect_cleanup %! if (exist (fn, 'file')) %! delete (fn); %! endif %! end_unwind_protect %!test # a complex table with row names round-trips data, row names, descriptions, and units %! T = table ([10; 20], string ({'aa'; 'bb'}), datetime (2022, 6, [1; 2]), ... %! 'VariableNames', {'n', 'tag', 'day'}, ... %! 'RowNames', {'first'; 'second'}); %! T.Properties.VariableDescriptions = {'a number', 'a tag', 'a day'}; %! T.Properties.VariableUnits = {'u1', 'u2', 'u3'}; %! fn = [tempname() '.csv']; %! unwind_protect %! table2csv (T, fn); %! R = csv2table (fn); %! assert_equal (R.Properties.RowNames, {'first'; 'second'}); %! assert_equal (R.n, [10; 20]); %! assert_equal (cellstr (R.tag), {'aa'; 'bb'}); %! assert_equal (class (R.day), 'datetime'); %! assert_equal (cellstr (R.day), {'01-Jun-2022'; '02-Jun-2022'}); %! assert_equal (R.Properties.VariableDescriptions, {'a number', 'a tag', 'a day'}); %! assert_equal (R.Properties.VariableUnits, {'u1', 'u2', 'u3'}); %! unwind_protect_cleanup %! if (exist (fn, 'file')) %! delete (fn); %! endif %! end_unwind_protect %!test # every integer class (full range, exact) and the float classes round-trip %! T = table (int8 ([intmin('int8'); intmax('int8')]), ... %! int16 ([intmin('int16'); intmax('int16')]), ... %! int32 ([intmin('int32'); intmax('int32')]), ... %! int64 ([intmin('int64'); intmax('int64')]), ... %! uint8 ([0; intmax('uint8')]), uint16 ([0; intmax('uint16')]), ... %! uint32 ([0; intmax('uint32')]), uint64 ([0; intmax('uint64')]), ... %! single ([1.5; -2.5]), [1.25; -3.75], ... %! 'VariableNames', ... %! {'i8', 'i16', 'i32', 'i64', 'u8', 'u16', 'u32', 'u64', 'sg', 'db'}); %! fn = [tempname() '.csv']; %! unwind_protect %! table2csv (T, fn); %! R = csv2table (fn); %! assert_equal (class (R.i8), 'int8'); %! assert_equal (class (R.i16), 'int16'); %! assert_equal (class (R.i32), 'int32'); %! assert_equal (class (R.i64), 'int64'); %! assert_equal (class (R.u8), 'uint8'); %! assert_equal (class (R.u16), 'uint16'); %! assert_equal (class (R.u32), 'uint32'); %! assert_equal (class (R.u64), 'uint64'); %! assert_equal (class (R.sg), 'single'); %! assert_equal (class (R.db), 'double'); %! assert_equal (R.i8, int8 ([intmin('int8'); intmax('int8')])); %! assert_equal (R.i32, int32 ([intmin('int32'); intmax('int32')])); %! assert_equal (R.i64, int64 ([intmin('int64'); intmax('int64')])); %! assert_equal (R.u32, uint32 ([0; intmax('uint32')])); %! assert_equal (R.u64, uint64 ([0; intmax('uint64')])); %! assert_equal (R.sg, single ([1.5; -2.5])); %! assert_equal (R.db, [1.25; -3.75]); %! unwind_protect_cleanup %! if (exist (fn, 'file')) %! delete (fn); %! endif %! end_unwind_protect %!test # a multicolumn string variable splits and rejoins as a 2-D string %! T = table (string ({'a', 'b'; 'c', 'd'}), 'VariableNames', {'s'}); %! fn = [tempname() '.csv']; %! unwind_protect %! table2csv (T, fn); %! R = csv2table (fn); %! assert_equal (class (R.s), 'string'); %! assert_equal (size (R.s), [2, 2]); %! assert_equal (cellstr (R.s), {'a', 'b'; 'c', 'd'}); %! unwind_protect_cleanup %! if (exist (fn, 'file')) %! delete (fn); %! endif %! end_unwind_protect %!test # a multicolumn datetime variable round-trips as a 2-D datetime %! T = table ([datetime(2020, 1, [1; 2]), datetime(2021, 1, [1; 2])], ... %! 'VariableNames', {'d'}); %! fn = [tempname() '.csv']; %! unwind_protect %! table2csv (T, fn); %! R = csv2table (fn); %! assert_equal (class (R.d), 'datetime'); %! assert_equal (size (R.d), [2, 2]); %! assert_equal (cellstr (R.d(:, 1)), {'01-Jan-2020'; '02-Jan-2020'}); %! assert_equal (cellstr (R.d(:, 2)), {'01-Jan-2021'; '02-Jan-2021'}); %! unwind_protect_cleanup %! if (exist (fn, 'file')) %! delete (fn); %! endif %! end_unwind_protect %!test # a multicolumn duration variable round-trips as a 2-D duration %! T = table ([duration(1, 0, 0) .* [1; 2], duration(2, 0, 0) .* [1; 2]], ... %! 'VariableNames', {'du'}); %! fn = [tempname() '.csv']; %! unwind_protect %! table2csv (T, fn); %! R = csv2table (fn); %! assert_equal (class (R.du), 'duration'); %! assert_equal (size (R.du), [2, 2]); %! assert_equal (cellstr (R.du(:, 1)), {'01:00:00'; '02:00:00'}); %! assert_equal (cellstr (R.du(:, 2)), {'02:00:00'; '04:00:00'}); %! unwind_protect_cleanup %! if (exist (fn, 'file')) %! delete (fn); %! endif %! end_unwind_protect %!test # a multicolumn logical variable round-trips as a 2-D logical %! T = table (logical ([1, 0; 0, 1]), 'VariableNames', {'L'}); %! fn = [tempname() '.csv']; %! unwind_protect %! table2csv (T, fn); %! R = csv2table (fn); %! assert_equal (class (R.L), 'logical'); %! assert_equal (R.L, logical ([1, 0; 0, 1])); %! unwind_protect_cleanup %! if (exist (fn, 'file')) %! delete (fn); %! endif %! end_unwind_protect %!test # a multicolumn integer variable preserves its class and shape %! T = table (int16 ([10, 20; 30, 40]), 'VariableNames', {'M'}); %! fn = [tempname() '.csv']; %! unwind_protect %! table2csv (T, fn); %! R = csv2table (fn); %! assert_equal (class (R.M), 'int16'); %! assert_equal (R.M, int16 ([10, 20; 30, 40])); %! unwind_protect_cleanup %! if (exist (fn, 'file')) %! delete (fn); %! endif %! end_unwind_protect %!test # a multicolumn categorical is written as labels and read back as 2-D cellstr (lossy) %! T = table (categorical ({'x', 'y'; 'y', 'x'}), 'VariableNames', {'c'}); %! fn = [tempname() '.csv']; %! unwind_protect %! table2csv (T, fn); %! warning ('off', 'all', 'local'); %! R = csv2table (fn); %! assert_equal (R.c, {'x', 'y'; 'y', 'x'}); %! unwind_protect_cleanup %! if (exist (fn, 'file')) %! delete (fn); %! endif %! end_unwind_protect %!test # a datetime variable with a NaT round-trips, restoring the missing entry %! T = table ([datetime(2020, 1, 1); NaT; datetime(2020, 1, 3)], 'VariableNames', {'d'}); %! fn = [tempname() '.csv']; %! unwind_protect %! table2csv (T, fn); %! R = csv2table (fn); %! assert_equal (class (R.d), 'datetime'); %! assert_equal (isnat (R.d'), [false, true, false]); %! assert_equal (cellstr (R.d([1, 3])), {'01-Jan-2020'; '03-Jan-2020'}); %! unwind_protect_cleanup %! if (exist (fn, 'file')) %! delete (fn); %! endif %! end_unwind_protect %!test # a duration variable with a NaN round-trips, restoring the missing entry %! T = table (duration ([1; NaN; 3], [0; 0; 0], [0; 0; 0]), 'VariableNames', {'du'}); %! fn = [tempname() '.csv']; %! unwind_protect %! table2csv (T, fn); %! R = csv2table (fn); %! assert_equal (class (R.du), 'duration'); %! assert_equal (isnan (R.du'), [false, true, false]); %! assert_equal (cellstr (R.du([1, 3])), {'01:00:00'; '03:00:00'}); %! unwind_protect_cleanup %! if (exist (fn, 'file')) %! delete (fn); %! endif %! end_unwind_protect %!test # a missing string value is written as an empty field and read back as "" %! T = table (string ({'a'; missing; 'c'}), 'VariableNames', {'s'}); %! fn = [tempname() '.csv']; %! unwind_protect %! table2csv (T, fn); %! R = csv2table (fn); %! assert_equal (class (R.s), 'string'); %! assert_equal (cellstr (R.s), {'a'; ''; 'c'}); %! unwind_protect_cleanup %! if (exist (fn, 'file')) %! delete (fn); %! endif %! end_unwind_protect %!test # text with embedded commas, double quotes, and padding survives quoting %! T = table ({'a,b'; 'he said "hi"'; ' pad '; 'plain'}, 'VariableNames', {'s'}); %! fn = [tempname() '.csv']; %! unwind_protect %! table2csv (T, fn); %! assert_equal (! isempty (strfind (fileread (fn), '"he said ""hi"""')), true); %! R = csv2table (fn); %! assert_equal (R.s, {'a,b'; 'he said "hi"'; ' pad '; 'plain'}); %! unwind_protect_cleanup %! if (exist (fn, 'file')) %! delete (fn); %! endif %! end_unwind_protect %!test # a nested table of special types round-trips (categorical is lossy) %! inner = table (datetime(2020, 1, [1; 2]), duration ([1; 2], [0; 0], [0; 0]), ... %! string ({'p'; 'q'}), categorical ({'lo'; 'hi'}), ... %! 'VariableNames', {'dt', 'du', 'st', 'ct'}); %! T = table ([1; 2], inner, 'VariableNames', {'id', 'nest'}); %! fn = [tempname() '.csv']; %! unwind_protect %! table2csv (T, fn); %! warning ('off', 'all', 'local'); %! R = csv2table (fn); %! assert_equal (class (R.nest), 'table'); %! assert_equal (class (R.nest.dt), 'datetime'); %! assert_equal (cellstr (R.nest.dt), {'01-Jan-2020'; '02-Jan-2020'}); %! assert_equal (class (R.nest.du), 'duration'); %! assert_equal (cellstr (R.nest.du), {'01:00:00'; '02:00:00'}); %! assert_equal (class (R.nest.st), 'string'); %! assert_equal (cellstr (R.nest.st), {'p'; 'q'}); %! assert_equal (R.nest.ct, {'lo'; 'hi'}); %! unwind_protect_cleanup %! if (exist (fn, 'file')) %! delete (fn); %! endif %! end_unwind_protect %!test # a nested table with a multicolumn inner variable round-trips %! inner = table ([1, 2; 3, 4], {'a'; 'b'}, 'VariableNames', {'mm', 'lab'}); %! T = table ([9; 8], inner, 'VariableNames', {'id', 'nest'}); %! fn = [tempname() '.csv']; %! unwind_protect %! table2csv (T, fn); %! R = csv2table (fn); %! assert_equal (R.nest.mm, [1, 2; 3, 4]); %! assert_equal (R.nest.lab, {'a'; 'b'}); %! unwind_protect_cleanup %! if (exist (fn, 'file')) %! delete (fn); %! endif %! end_unwind_protect %!test # a table mixing multicolumn, nested, and several types round-trips with metadata %! inner = table (duration ([1; 2], [0; 0], [0; 0]), string ({'p'; 'q'}), ... %! categorical ({'lo'; 'hi'}), 'VariableNames', {'idu', 'ist', 'ict'}); %! inner.Properties.VariableDescriptions = {'inner dur', 'inner str', 'inner cat'}; %! inner.Properties.VariableUnits = {'h', 't', 'c'}; %! T = table ([duration(1, 0, 0) .* [1; 2], duration(2, 0, 0) .* [1; 2]], ... %! string ({'aa'; 'bb'}), categorical ({'x'; 'y'}), ... %! [10, 11, 12; 20, 21, 22], inner, ... %! 'VariableNames', {'mdur', 'tag', 'grade', 'mat', 'nest'}); %! T.Properties.VariableDescriptions = {'mc dur', 'a tag', 'a grade', 'a matrix', 'nested'}; %! T.Properties.VariableUnits = {'h', 't', 'g', 'm', 'n'}; %! fn = [tempname() '.csv']; %! unwind_protect %! table2csv (T, fn); %! warning ('off', 'all', 'local'); %! R = csv2table (fn); %! assert_equal (R.Properties.VariableNames, {'mdur', 'tag', 'grade', 'mat', 'nest'}); %! assert_equal (class (R.mdur), 'duration'); %! assert_equal (size (R.mdur), [2, 2]); %! assert_equal (cellstr (R.tag), {'aa'; 'bb'}); %! assert_equal (R.grade, {'x'; 'y'}); %! assert_equal (R.mat, [10, 11, 12; 20, 21, 22]); %! assert_equal (class (R.nest), 'table'); %! assert_equal (cellstr (R.nest.idu), {'01:00:00'; '02:00:00'}); %! assert_equal (cellstr (R.nest.ist), {'p'; 'q'}); %! assert_equal (R.nest.ict, {'lo'; 'hi'}); %! assert_equal (R.Properties.VariableDescriptions, ... %! {'mc dur', 'a tag', 'a grade', 'a matrix', 'nested'}); %! assert_equal (R.Properties.VariableUnits, {'h', 't', 'g', 'm', 'n'}); %! assert_equal (R.nest.Properties.VariableDescriptions, {'inner dur', 'inner str', 'inner cat'}); %! assert_equal (R.nest.Properties.VariableUnits, {'h', 't', 'c'}); %! unwind_protect_cleanup %! if (exist (fn, 'file')) %! delete (fn); %! endif %! end_unwind_protect %!test # full-range 64-bit integers round-trip exactly (written/read without double) %! ii = [intmin('int64'); intmin('int64') + 1; intmax('int64')]; %! uu = [uint64(0); intmax('uint64') - uint64(1); intmax('uint64')]; %! T = table (ii, uu, 'VariableNames', {'i', 'u'}); %! fn = [tempname() '.csv']; %! unwind_protect %! table2csv (T, fn); %! assert_equal (! isempty (strfind (fileread (fn), '18446744073709551615')), true); %! R = csv2table (fn); %! assert_equal (R.i, ii); %! assert_equal (R.u, uu); %! unwind_protect_cleanup %! if (exist (fn, 'file')) %! delete (fn); %! endif %! end_unwind_protect %!test # a multicolumn 64-bit integer variable with extreme values round-trips %! M = int64 ([intmax('int64'), 1; -5, intmin('int64')]); %! T = table (M, 'VariableNames', {'M'}); %! fn = [tempname() '.csv']; %! unwind_protect %! table2csv (T, fn); %! R = csv2table (fn); %! assert_equal (class (R.M), 'int64'); %! assert_equal (R.M, M); %! unwind_protect_cleanup %! if (exist (fn, 'file')) %! delete (fn); %! endif %! end_unwind_protect ## Test input validation for 'table2csv' method %!error ... %! table2csv (table (1), fullfile (tempname (), 'x.csv')); ################################################################################ ## ** Save to Files ** ## ################################################################################ ## Available Methods ## ## ## ## 'table2ods' ## ## ## ################################################################################ ## Test 'table2ods' method (round-trips through 'ods2table') %!test # datetime and duration are written as native ODS date/time cells %! T = table (datetime (2024, 1, 15, 10, 30, 15), duration (25, 30, 0), ... %! 'VariableNames', {'d', 'e'}); %! fn = [tempname() '.fods']; %! unwind_protect %! table2ods (T, fn); %! txt = fileread (fn); %! assert_equal (! isempty (strfind (txt, 'office:value-type="date"')), true); %! assert_equal (! isempty (strfind (txt, 'office:date-value="2024-01-15T10:30:15"')), true); %! assert_equal (! isempty (strfind (txt, 'office:value-type="time"')), true); %! assert_equal (! isempty (strfind (txt, 'office:time-value="PT25H30M00S"')), true); %! unwind_protect_cleanup %! if (exist (fn, 'file')) %! delete (fn); %! endif %! end_unwind_protect %!test # a full-range int64 is written with its exact digits %! T = table (int64 (9223372036854775807), 'VariableNames', {'i'}); %! fn = [tempname() '.fods']; %! unwind_protect %! table2ods (T, fn); %! txt = fileread (fn); %! assert_equal (! isempty (strfind (txt, '9223372036854775807')), true); %! unwind_protect_cleanup %! if (exist (fn, 'file')) %! delete (fn); %! endif %! end_unwind_protect %!test # the hidden metadata sheet records the variable types and names %! T = table ([1; 2], {'a'; 'b'}, 'VariableNames', {'v', 'g'}); %! fn = [tempname() '.fods']; %! unwind_protect %! table2ods (T, fn); %! txt = fileread (fn); %! assert_equal (! isempty (strfind (txt, '__datatypes_meta__')), true); %! unwind_protect_cleanup %! if (exist (fn, 'file')) %! delete (fn); %! endif %! end_unwind_protect %!test # a compressed '.ods' is a ZIP whose contents round-trip through ods2table %! T = table ([1; 2; 3], datetime (2024, 1, [1; 2; 3]), {'a'; 'b'; 'c'}, ... %! 'VariableNames', {'n', 'd', 'g'}); %! fn = [tempname() '.ods']; %! unwind_protect %! table2ods (T, fn); %! R = ods2table (fn); %! assert_equal (R.Properties.VariableNames, {'n', 'd', 'g'}); %! assert_equal (R.n, [1; 2; 3]); %! assert_equal (R.g, {'a'; 'b'; 'c'}); %! assert_equal (class (R.d), 'datetime'); %! unwind_protect_cleanup %! if (exist (fn, 'file')) %! delete (fn); %! endif %! end_unwind_protect %!test # the ODS package leads with a compliant stored 'mimetype' entry %! T = table ([1; 2; 3], 'VariableNames', {'n'}); %! fn = [tempname() '.ods']; %! unwind_protect %! table2ods (T, fn); %! fid = fopen (fn, 'r'); hdr = fread (fid, 38, 'uint8')'; fclose (fid); %! flag = hdr(7) + hdr(8) * 256; # general-purpose bit flag %! method = hdr(9) + hdr(10) * 256; # compression method %! namelen = hdr(27) + hdr(28) * 256; %! assert_equal (hdr(1:4), [80, 75, 3, 4]); # ZIP magic "PK\x03\x04" %! assert_equal (method, 0); # stored, not deflated %! assert_equal (flag, 0); # no data descriptor / extras %! assert_equal (char (hdr(31:30+namelen)), 'mimetype'); %! unwind_protect_cleanup %! if (exist (fn, 'file')) %! delete (fn); %! endif %! end_unwind_protect ## Incremental 'table2ods': build a multi-sheet workbook one table at a time, ## read back through 'ods2struct' %!test %! A = table ([1; 2; 3], 'VariableNames', {'a'}); %! B = table ({'x'; 'y'}, 'VariableNames', {'b'}); %! C = table ([9; 8], 'VariableNames', {'c'}); %! fn = [tempname() '.ods']; %! unwind_protect %! table2ods (A, fn, 'Sheet', 'first'); %! table2ods (B, fn, 'Sheet', 'second'); %! table2ods (C, fn, 'Sheet', 'third'); %! r = ods2struct (fn); %! assert_equal (fieldnames (r), {'first'; 'second'; 'third'}); %! assert_equal (r.first.a, [1; 2; 3]); %! assert_equal (r.second.b, {'x'; 'y'}); %! assert_equal (r.third.c, [9; 8]); %! unwind_protect_cleanup %! if (exist (fn, 'file')); delete (fn); endif %! end_unwind_protect ## Incremental 'table2ods' 'append' adds rows to a sheet, others preserved %!test %! A = table ([1; 2], 'VariableNames', {'a'}); %! B = table ([3; 4; 5], 'VariableNames', {'b'}); %! more = table ([6; 7], 'VariableNames', {'a'}); %! fn = [tempname() '.fods']; %! unwind_protect %! table2ods (A, fn, 'Sheet', 'first'); %! table2ods (B, fn, 'Sheet', 'second'); %! table2ods (more, fn, 'Sheet', 'first', 'WriteMode', 'append'); %! r = ods2struct (fn); %! assert_equal (r.first.a, [1; 2; 6; 7]); %! assert_equal (r.second.b, [3; 4; 5]); %! unwind_protect_cleanup %! if (exist (fn, 'file')); delete (fn); endif %! end_unwind_protect ## Incremental 'table2ods' 'overwritesheet' replaces one sheet, keeps the rest; ## native types survive the read-modify-rewrite %!test %! A = table (datetime (2024, 1, [1; 2]), 'VariableNames', {'d'}); %! B = table ([3; 4; 5], 'VariableNames', {'b'}); %! repl = table (seconds ([10; 20]), 'VariableNames', {'t'}); %! fn = [tempname() '.ods']; %! unwind_protect %! table2ods (A, fn, 'Sheet', 'first'); %! table2ods (B, fn, 'Sheet', 'second'); %! table2ods (repl, fn, 'Sheet', 'first', 'WriteMode', 'overwritesheet'); %! r = ods2struct (fn); %! assert_equal (class (r.first.t), 'duration'); %! assert_equal (isequaln (seconds (r.first.t), [10; 20]), true); %! assert_equal (r.second.b, [3; 4; 5]); %! unwind_protect_cleanup %! if (exist (fn, 'file')); delete (fn); endif %! end_unwind_protect ## 'table2ods' 'replacefile' discards the existing workbook %!test %! A = table ([1; 2], 'VariableNames', {'a'}); %! B = table ([3; 4], 'VariableNames', {'b'}); %! fn = [tempname() '.ods']; %! unwind_protect %! table2ods (A, fn, 'Sheet', 'first'); %! table2ods (B, fn, 'Sheet', 'second', 'WriteMode', 'replacefile'); %! [~, ~, ~, nm] = __ods2table__ (fn); %! assert_equal (nm(:), {'second'}); %! unwind_protect_cleanup %! if (exist (fn, 'file')); delete (fn); endif %! end_unwind_protect ## Test input validation for 'table2ods' method %!error ... %! table2ods (table (1), [tempname() '.ods'], 'WriteMode', 'bogus'); %!error ... %! table2ods (table (1), [tempname() '.ods'], 'Bogus', 1); %!error ... %! inner = table ([1; 2], 'VariableNames', {'a'}); ... %! T = table (inner, [3; 4], 'VariableNames', {'nested', 'x'}); ... %! table2ods (T, [tempname() '.fods']); %!error ... %! s = struct ('f', {1; 2}); ... %! T = table (s, [3; 4], 'VariableNames', {'st', 'x'}); ... %! table2ods (T, [tempname() '.fods']); %!error ... %! table2ods (table (1), [tempname() '.txt']); ################################################################################ ## ** Save to Files ** ## ################################################################################ ## Available Methods ## ## ## ## 'writetable' ## ## ## ################################################################################ ## Test 'writetable' method (round-trips through 'readtable') %!test # text round-trip: names header, numeric/text detection %! T = table ([1; 2; 3], {'a'; 'b'; 'c'}, [1.5; 2.5; 3.5], ... %! 'VariableNames', {'id', 'name', 'val'}); %! fn = [tempname() '.csv']; %! unwind_protect %! writetable (T, fn); %! R = readtable (fn); %! assert_equal (R.Properties.VariableNames, {'id', 'name', 'val'}); %! assert_equal (R.id, [1; 2; 3]); %! assert_equal (R.name, {'a'; 'b'; 'c'}); %! assert_equal (R.val, [1.5; 2.5; 3.5]); %! unwind_protect_cleanup %! if (exist (fn, 'file')) %! delete (fn); %! endif %! end_unwind_protect %!test # text: no type metadata is written, only a names row then data %! T = table ([1; 2], {'x'; 'y'}, 'VariableNames', {'a', 'b'}); %! fn = [tempname() '.csv']; %! unwind_protect %! writetable (T, fn); %! txt = fileread (fn); %! assert_equal (! isempty (regexp (txt, '^a,b$', 'lineanchors', 'once')), true); %! assert_equal (isempty (strfind (txt, 'varTypes')), true); %! unwind_protect_cleanup %! if (exist (fn, 'file')) %! delete (fn); %! endif %! end_unwind_protect %!test # 'WriteVariableNames' false omits the header row %! T = table ([1; 2], 'VariableNames', {'a'}); %! fn = [tempname() '.csv']; %! unwind_protect %! writetable (T, fn, 'WriteVariableNames', false); %! txt = fileread (fn); %! assert_equal (isempty (strfind (txt, 'a')), true); %! unwind_protect_cleanup %! if (exist (fn, 'file')) %! delete (fn); %! endif %! end_unwind_protect %!test # 'Delimiter' and 'QuoteStrings' control the text output %! T = table ({'x,y'; 'z'}, 'VariableNames', {'s'}); %! fn = [tempname() '.csv']; %! unwind_protect %! writetable (T, fn, 'Delimiter', 'semi', 'QuoteStrings', 'none'); %! txt = fileread (fn); %! assert_equal (! isempty (strfind (txt, 'x,y')), true); %! assert_equal (isempty (strfind (txt, '"')), true); %! unwind_protect_cleanup %! if (exist (fn, 'file')) %! delete (fn); %! endif %! end_unwind_protect %!test # 'WriteRowNames' writes the row names as a leading column %! T = table ([10; 20], 'VariableNames', {'v'}, 'RowNames', {'r1', 'r2'}); %! fn = [tempname() '.csv']; %! unwind_protect %! writetable (T, fn, 'WriteRowNames', true); %! R = readtable (fn, 'ReadRowNames', true); %! assert_equal (R.Properties.RowNames, {'r1'; 'r2'}); %! assert_equal (R.v, [10; 20]); %! unwind_protect_cleanup %! if (exist (fn, 'file')) %! delete (fn); %! endif %! end_unwind_protect %!test # a multicolumn variable is split into name_1, name_2, ... %! T = table ([1 2; 3 4], {'p'; 'q'}, 'VariableNames', {'mat', 'tag'}); %! fn = [tempname() '.csv']; %! unwind_protect %! writetable (T, fn); %! R = readtable (fn); %! assert_equal (R.Properties.VariableNames, {'mat_1', 'mat_2', 'tag'}); %! unwind_protect_cleanup %! if (exist (fn, 'file')) %! delete (fn); %! endif %! end_unwind_protect %!test # spreadsheet round-trip preserves native datetime/duration types %! T = table ([1; 2], datetime (2024, 1, [1; 2]), seconds ([30; 90]), ... %! 'VariableNames', {'a', 'd', 'e'}); %! fn = [tempname() '.ods']; %! unwind_protect %! writetable (T, fn); %! R = readtable (fn); %! assert_equal (R.a, [1; 2]); %! assert_equal (class (R.d), 'datetime'); %! assert_equal (class (R.e), 'duration'); %! unwind_protect_cleanup %! if (exist (fn, 'file')) %! delete (fn); %! endif %! end_unwind_protect %!test # a writetable '.ods' exposes a visible names row (read back as names) %! T = table ([1; 2], {'x'; 'y'}, 'VariableNames', {'aa', 'bb'}); %! fn = [tempname() '.ods']; %! unwind_protect %! writetable (T, fn); %! R = readtable (fn); %! assert_equal (R.Properties.VariableNames, {'aa', 'bb'}); %! assert_equal (R.aa, [1; 2]); %! unwind_protect_cleanup %! if (exist (fn, 'file')) %! delete (fn); %! endif %! end_unwind_protect ## 'writetable' to a named sheet, read back by that name %!test %! T = table ([1; 2; 3], [10; 20; 30], 'VariableNames', {'x', 'y'}); %! fn = [tempname() '.ods']; %! unwind_protect %! writetable (T, fn, 'Sheet', 'MySheet'); %! R = readtable (fn, 'Sheet', 'MySheet'); %! assert_equal (R.Properties.VariableNames, {'x', 'y'}); %! assert_equal (R.x, [1; 2; 3]); %! assert_equal (R.y, [10; 20; 30]); %! unwind_protect_cleanup %! if (exist (fn, 'file')); delete (fn); endif %! end_unwind_protect ## 'writetable' 'Range' anchors the block; readtable auto-trims to it, and an ## explicit read range recovers the same data. %!test %! T = table ([1; 2; 3], [10; 20; 30], 'VariableNames', {'x', 'y'}); %! fn = [tempname() '.ods']; %! unwind_protect %! writetable (T, fn, 'Range', 'C5'); %! R = readtable (fn); %! assert_equal (R.Properties.VariableNames, {'x', 'y'}); %! assert_equal (R.x, [1; 2; 3]); %! R2 = readtable (fn, 'Range', 'C5:D8'); %! assert_equal (R2.x, [1; 2; 3]); %! assert_equal (R2.y, [10; 20; 30]); %! unwind_protect_cleanup %! if (exist (fn, 'file')); delete (fn); endif %! end_unwind_protect ## 'writetable' flat '.fods' with a 'Range' offset round-trips %!test %! T = table ([5; 6], [50; 60], 'VariableNames', {'a', 'b'}); %! fn = [tempname() '.fods']; %! unwind_protect %! writetable (T, fn, 'Range', 'B3'); %! R = readtable (fn); %! assert_equal (R.a, [5; 6]); %! assert_equal (R.b, [50; 60]); %! unwind_protect_cleanup %! if (exist (fn, 'file')); delete (fn); endif %! end_unwind_protect ## text 'WriteMode' 'append' adds rows below without repeating the header %!test %! T = table ([1; 2], [10; 20], 'VariableNames', {'x', 'y'}); %! T2 = table ([3; 4], [30; 40], 'VariableNames', {'x', 'y'}); %! fn = [tempname() '.csv']; %! unwind_protect %! writetable (T, fn); %! writetable (T2, fn, 'WriteMode', 'append'); %! R = readtable (fn); %! assert_equal (R.x, [1; 2; 3; 4]); %! assert_equal (R.y, [10; 20; 30; 40]); %! unwind_protect_cleanup %! if (exist (fn, 'file')); delete (fn); endif %! end_unwind_protect ## 'writetable' to a new sheet of an existing workbook preserves other sheets %!test %! A = table ([1; 2], 'VariableNames', {'a'}); %! B = table ([3; 4; 5], 'VariableNames', {'b'}); %! fn = [tempname() '.ods']; %! unwind_protect %! writetable (A, fn, 'Sheet', 'first'); %! writetable (B, fn, 'Sheet', 'second'); %! assert_equal (readtable (fn, 'Sheet', 'first').a, [1; 2]); %! assert_equal (readtable (fn, 'Sheet', 'second').b, [3; 4; 5]); %! unwind_protect_cleanup %! if (exist (fn, 'file')); delete (fn); endif %! end_unwind_protect ## 'WriteMode' 'append' adds rows to an existing sheet; other sheets untouched %!test %! A = table ([1; 2], 'VariableNames', {'a'}); %! B = table ([3; 4; 5], 'VariableNames', {'b'}); %! more = table ([6; 7], 'VariableNames', {'a'}); %! fn = [tempname() '.ods']; %! unwind_protect %! writetable (A, fn, 'Sheet', 'first'); %! writetable (B, fn, 'Sheet', 'second'); %! writetable (more, fn, 'Sheet', 'first', 'WriteMode', 'append'); %! assert_equal (readtable (fn, 'Sheet', 'first').a, [1; 2; 6; 7]); %! assert_equal (readtable (fn, 'Sheet', 'second').b, [3; 4; 5]); %! unwind_protect_cleanup %! if (exist (fn, 'file')); delete (fn); endif %! end_unwind_protect ## 'WriteMode' 'overwritesheet' replaces one sheet, preserving the others %!test %! A = table ([1; 2], 'VariableNames', {'a'}); %! B = table ([3; 4; 5], 'VariableNames', {'b'}); %! repl = table ([9], 'VariableNames', {'z'}); %! fn = [tempname() '.fods']; %! unwind_protect %! writetable (A, fn, 'Sheet', 'first'); %! writetable (B, fn, 'Sheet', 'second'); %! writetable (repl, fn, 'Sheet', 'first', 'WriteMode', 'overwritesheet'); %! R = readtable (fn, 'Sheet', 'first'); %! assert_equal (R.Properties.VariableNames, {'z'}); %! assert_equal (R.z, 9); %! assert_equal (readtable (fn, 'Sheet', 'second').b, [3; 4; 5]); %! unwind_protect_cleanup %! if (exist (fn, 'file')); delete (fn); endif %! end_unwind_protect ## Writing to an existing workbook with no 'Sheet' targets the first sheet %!test %! A = table ([1; 2], 'VariableNames', {'a'}); %! B = table ([3; 4; 5], 'VariableNames', {'b'}); %! Z = table ([9; 9], 'VariableNames', {'z'}); %! fn = [tempname() '.ods']; %! unwind_protect %! writetable (A, fn, 'Sheet', 'first'); %! writetable (B, fn, 'Sheet', 'second'); %! writetable (Z, fn); # no 'Sheet' -> first sheet %! [~, ~, ~, nm] = __ods2table__ (fn); %! assert_equal (nm(:), {'first'; 'second'}); # no stray 'Sheet1' added %! assert_equal (readtable (fn, 'Sheet', 'first').Properties.VariableNames, {'z'}); %! assert_equal (readtable (fn, 'Sheet', 'second').b, [3; 4; 5]); %! unwind_protect_cleanup %! if (exist (fn, 'file')); delete (fn); endif %! end_unwind_protect ## 'WriteMode' 'replacefile' overwrites the whole workbook %!test %! A = table ([1; 2], 'VariableNames', {'a'}); %! B = table ([3; 4; 5], 'VariableNames', {'b'}); %! fn = [tempname() '.ods']; %! unwind_protect %! writetable (A, fn, 'Sheet', 'first'); %! writetable (B, fn, 'Sheet', 'second', 'WriteMode', 'replacefile'); %! [~, ~, ~, nm] = __ods2table__ (fn); %! assert_equal (nm(:), {'second'}); %! unwind_protect_cleanup %! if (exist (fn, 'file')); delete (fn); endif %! end_unwind_protect ## Test input validation for 'writetable' method %!error ... %! inner = table ([1; 2], 'VariableNames', {'a'}); ... %! T = table (inner, [3; 4], 'VariableNames', {'nested', 'x'}); ... %! writetable (T, [tempname() '.csv']); %!error ... %! writetable (table (1), [tempname() '.xls']); %!error ... %! writetable (table (1), [tempname() '.csv'], 'Sheet', 'S1'); %!error ... %! writetable (table (1), [tempname() '.csv'], 'Range', 'A1'); %!error ... %! writetable (table (1), [tempname() '.csv'], 'WriteMode', 'inplace'); %!error ... %! writetable (table (1), [tempname() '.ods'], 'WriteMode', 'bogus'); %!error ... %! writetable (table (1), [tempname() '.ods'], 'WriteMode', 'append', 'Range', 'B2'); %!error ... %! writetable (table (1), [tempname() '.csv'], 'Bogus', 1); ## '.xlsx' write into an existing workbook adds/preserves sheets and appends %!test %! A = table ([1; 2], 'VariableNames', {'a'}); %! B = table ([3; 4; 5], 'VariableNames', {'b'}); %! more = table ([6; 7], 'VariableNames', {'a'}); %! fn = [tempname() '.xlsx']; %! unwind_protect %! writetable (A, fn, 'Sheet', 'first'); %! writetable (B, fn, 'Sheet', 'second'); %! writetable (more, fn, 'Sheet', 'first', 'WriteMode', 'append'); %! r = xlsx2struct (fn); %! assert_equal (fieldnames (r), {'first'; 'second'}); %! assert_equal (r.first.a, [1; 2; 6; 7]); %! assert_equal (r.second.b, [3; 4; 5]); %! unwind_protect_cleanup %! if (exist (fn, 'file')); delete (fn); endif %! end_unwind_protect ## 'replacefile' overwrites the whole Excel workbook %!test %! A = table ([1; 2], 'VariableNames', {'a'}); %! B = table ([3; 4], 'VariableNames', {'b'}); %! fn = [tempname() '.xlsx']; %! unwind_protect %! writetable (A, fn, 'Sheet', 'first'); %! writetable (B, fn, 'Sheet', 'second', 'WriteMode', 'replacefile'); %! [~, ~, ~, nm] = __xlsx2table__ (fn); %! assert_equal (nm(:), {'second'}); %! unwind_protect_cleanup %! if (exist (fn, 'file')); delete (fn); endif %! end_unwind_protect ################################################################################ ## ** Summary Information ** ## ################################################################################ ## Available Methods ## ## ## ## 'summary' 'height' 'width' 'head' ## ## 'tail' ## ## ## ################################################################################ %!shared LastName, Age, Smoker, Height, Weight, BloodPressure, T, tblA %! LastName = {"Sanchez"; "Johnson"; "Li"; "Diaz"; "Brown"}; %! Age = [38; 43; 38; 40; 49]; %! Smoker = logical ([1; 0; 1; 0; 1]); %! Height = [71; 69; 64; 67; 64]; %! Weight = [176; 163; 131; 133; 119]; %! BloodPressure = [124, 93; 109, 77; 125, 83; 117, 75; 122, 80]; %! T = table (Age, Smoker, Height, Weight, BloodPressure); ## Test 'summary' returns a struct with the expected per-variable fields %!test %! tt = table (Age, Smoker, BloodPressure); %! ss = summary (tt); %! assert_equal (isstruct (ss), true); %! assert_equal (fieldnames (ss), {'Age'; 'Smoker'; 'BloodPressure'}); %! assert_equal (ss.Age.Size, [5, 1]); %! assert_equal (ss.Age.Type, 'double'); %! assert_equal ([ss.Age.Min, ss.Age.Median, ss.Age.Max], [38, 40, 49]); %! assert_equal (ss.Age.NumMissing, 0); %! assert_equal (ss.Smoker.Size, [5, 1]); %! assert_equal (ss.Smoker.True, 3); %! assert_equal (ss.Smoker.False, 2); %! assert_equal (ss.BloodPressure.Size, [5, 2]); ## Test 'summary' numeric NaN handling and per-column statistics %!test %! ss = summary (table ([1; 2; NaN; 4], [NaN, 2; 3, NaN; 5, 6; 7, 8], ... %! 'VariableNames', {'a', 'b'})); %! assert_equal ([ss.a.Min, ss.a.Median, ss.a.Max], [1, 2, 4]); %! assert_equal (ss.a.NumMissing, 1); %! assert_equal (ss.b.Min, [3, 2]); %! assert_equal (ss.b.Median, [5, 6]); %! assert_equal (ss.b.Max, [7, 8]); %! assert_equal (ss.b.NumMissing, [1, 1]); ## Test 'summary' of logical, string, cell, and categorical variables %!test %! ss = summary (table (logical ([1; 1; 0]), string ({'a'; 'b'; 'c'}), ... %! {'x'; 'y'; 'z'}, categorical ({'p'; 'q'; 'p'}), ... %! 'VariableNames', {'L', 'S', 'C', 'Cat'})); %! assert_equal (ss.L.True, 2); %! assert_equal (ss.L.False, 1); %! assert_equal (isfield (ss.S, 'Min'), false); %! assert_equal (isfield (ss.C, 'Min'), false); %! assert_equal (isfield (ss.Cat, 'Min'), false); %! assert_equal (ss.Cat.Type, 'categorical'); ## Test 'summary' of a duration variable (typed stats, no TimeStep field) %!test %! Dv = duration ([1; 2; 3], [0; 30; 0], [0; 0; 0]); %! ss = summary (table (Dv, 'VariableNames', {'D'})); %! assert_equal (ss.D.Type, 'duration'); %! assert_equal (class (ss.D.Min), 'duration'); %! assert_equal (seconds (ss.D.Min), 3600); %! assert_equal (seconds (ss.D.Median), 9000); %! assert_equal (seconds (ss.D.Max), 10800); %! assert_equal (ss.D.NumMissing, 0); %! assert_equal (isfield (ss.D, 'TimeStep'), false); ## Test 'summary' duration omits NaN from statistics and counts missing %!test %! Dv = duration ([1; NaN; 3], [0; 0; 0], [0; 0; 0]); %! ss = summary (table (Dv, 'VariableNames', {'D'})); %! assert_equal (seconds (ss.D.Min), 3600); %! assert_equal (seconds (ss.D.Max), 10800); %! assert_equal (seconds (ss.D.Median), 7200); %! assert_equal (ss.D.NumMissing, 1); ## Test 'summary' of a datetime variable (typed stats, no TimeStep field) %!test %! Tv = datetime ([2020; 2021; 2022], 1, 1); %! ss = summary (table (Tv, 'VariableNames', {'T'})); %! assert_equal (ss.T.Type, 'datetime'); %! assert_equal (class (ss.T.Min), 'datetime'); %! assert_equal (datevec (ss.T.Min), [2020, 1, 1, 0, 0, 0]); %! assert_equal (datevec (ss.T.Median), [2021, 1, 1, 0, 0, 0]); %! assert_equal (datevec (ss.T.Max), [2022, 1, 1, 0, 0, 0]); %! assert_equal (ss.T.NumMissing, 0); %! assert_equal (isfield (ss.T, 'TimeStep'), false); ## Test 'summary' datetime excludes NaT and counts it as missing %!test %! Tv = datetime ([2020; NaN; 2022], 1, 1); %! ss = summary (table (Tv, 'VariableNames', {'T'})); %! assert_equal (datevec (ss.T.Min), [2020, 1, 1, 0, 0, 0]); %! assert_equal (datevec (ss.T.Max), [2022, 1, 1, 0, 0, 0]); %! assert_equal (ss.T.NumMissing, 1); ## Test 'summary' of calendarDuration reports only Size, Type, and NumMissing %!test %! Cv = calendarDuration ([1; 2; 3], [0; 1; 2], [0; 0; 0]); %! ss = summary (table (Cv, 'VariableNames', {'C'})); %! assert_equal (ss.C.Type, 'calendarDuration'); %! assert_equal (ss.C.Size, [3, 1]); %! assert_equal (isfield (ss.C, 'Min'), false); %! assert_equal (ss.C.NumMissing, 0); %! ss = summary (table (calendarDuration ([1; NaN], 0, 0), ... %! 'VariableNames', {'C'})); %! assert_equal (ss.C.NumMissing, 1); ## Test 'summary' captures variable Description and Units in the struct %!test %! tt = table ([1; 2; 3], 'VariableNames', {'v'}); %! tt.Properties.VariableUnits = {'kg'}; %! tt.Properties.VariableDescriptions = {'a weight'}; %! ss = summary (tt); %! assert_equal (ss.v.Units, 'kg'); %! assert_equal (ss.v.Description, 'a weight'); ## Test 'summary' of an empty table does not error %!test %! et = table ('Size', [0, 2], 'VariableTypes', {'double', 'double'}, ... %! 'VariableNames', {'a', 'b'}); %! ss = summary (et); %! assert_equal (fieldnames (ss), {'a'; 'b'}); %! str = evalc ("summary (et);"); %! assert_equal (! isempty (strfind (str, 'Variables:')), true); ## Test 'summary' printed output for numeric, logical, and missing values %!test %! str = evalc (["summary (table ([1; 2; NaN], logical ([1; 0; 1]), ", ... %! "'VariableNames', {'a', 'b'}));"]); %! assert_equal (! isempty (strfind (str, 'Variables:')), true); %! assert_equal (! isempty (strfind (str, 'Median')), true); %! assert_equal (! isempty (strfind (str, 'NumMissing 1')), true); %! assert_equal (! isempty (strfind (str, 'True')), true); %! assert_equal (! isempty (strfind (str, 'False')), true); ## Test 'summary' printed output shows table and variable Description %!test %! tt = table ([1; 2; 3], 'VariableNames', {'v'}); %! tt.Properties.Description = 'My table'; %! tt.Properties.VariableDescriptions = {'a weight'}; %! str = evalc ("summary (tt);"); %! assert_equal (! isempty (strfind (str, 'Description: My table')), true); %! assert_equal (! isempty (strfind (str, 'Description: a weight')), true); ## Test 'summary' printed output for datetime and duration variables %!test %! sd = evalc (["summary (table (duration ([1; 2; 3], 0, 0), ", ... %! "'VariableNames', {'D'}));"]); %! assert_equal (! isempty (strfind (sd, 'Min 01:00:00')), true); %! assert_equal (! isempty (strfind (sd, 'Max 03:00:00')), true); %! st = evalc (["summary (table (datetime (2020, 1, [1; 2; 3]), ", ... %! "'VariableNames', {'T'}));"]); %! assert_equal (! isempty (strfind (st, 'Min 01-Jan-2020')), true); %! assert_equal (! isempty (strfind (st, 'Max 03-Jan-2020')), true); ## Test 'summary' printed output for calendarDuration shows only NumMissing %!test %! sc = evalc (["summary (table (calendarDuration ([1; NaN], 0, 0), ", ... %! "'VariableNames', {'C'}));"]); %! assert_equal (! isempty (strfind (sc, 'NumMissing 1')), true); %! assert_equal (isempty (strfind (sc, 'Min')), true); ## Test 'height': number of rows, equivalent to size (TBL, 1) %!assert_equal (height (T), 5); %!assert_equal (height (T), size (T, 1)); %!assert_equal (class (height (T)), 'double'); ## Test 'height' of a single-row table %!assert_equal (height (table (1, 2, 3)), 1); ## Test 'height' of an empty table with no variables %!assert_equal (height (table ()), 0); ## Test 'height' of a table with variables but zero rows %!assert_equal (height (table ('Size', [0, 3], ... %! 'VariableTypes', {'double', 'double', 'double'})), 0); ## Test 'height' of a preallocated table reports the requested row count %!assert_equal (height (table ('Size', [7, 2], ... %! 'VariableTypes', {'double', 'double'})), 7); ## Test 'height' is unaffected by multicolumn variables %!assert_equal (height (table (Age, BloodPressure)), 5); ## Test 'height' counts rows regardless of row names %!assert_equal (height (table (Age, Smoker, 'RowNames', LastName)), 5); ## Test 'width': number of variables, equivalent to size (TBL, 2) %!assert_equal (width (T), 5); %!assert_equal (width (T), size (T, 2)); %!assert_equal (class (width (T)), 'double'); ## Test 'width' counts variables, not the total number of columns %!assert_equal (width (T), 5); %!assert_equal (size (T.BloodPressure, 2), 2); ## Test 'width' of a single-variable table %!assert_equal (width (table (Age)), 1); ## Test 'width' of an empty table with no variables %!assert_equal (width (table ()), 0); ## Test 'width' of a table with zero rows still counts its variables %!assert_equal (width (table ('Size', [0, 3], ... %! 'VariableTypes', {'double', 'double', 'double'})), 3); ## Test 'head' returns the first K rows in a new table %!test %! h = head (T, 3); %! assert_equal (height (h), 3); %! assert_equal (h.Age, Age(1:3)); %! assert_equal (class (h), 'table'); ## Test 'head' default count is eight rows %!assert_equal (height (head (T)), 5); %!test %! big = table ((1:20)'); %! assert_equal (height (head (big)), 8); %! h = head (big); %! assert_equal (h.Var1, (1:8)'); ## Test 'head' with K equal to the number of rows returns the whole table %!assert_equal (height (head (T, 5)), 5); ## Test 'head' with K greater than the number of rows returns all rows %!assert_equal (height (head (T, 99)), 5); ## Test 'head' with K of one returns just the first row %!test %! h1 = head (T, 1); %! assert_equal (height (h1), 1); %! assert_equal (h1.Age, Age(1)); ## Test 'head' of an empty (zero-row) table returns an empty table %!test %! e = table ('Size', [0, 2], 'VariableTypes', {'double', 'double'}); %! assert_equal (size (head (e, 3)), [0, 2]); ## Test 'head' preserves variable names, row names, and properties %!test %! R = table (Age, Smoker, 'RowNames', LastName); %! R.Properties.VariableUnits = {'Yrs', ''}; %! R.Properties.Description = 'demo'; %! h = head (R, 2); %! assert_equal (h.Properties.VariableNames, {'Age', 'Smoker'}); %! assert_equal (h.Properties.RowNames, LastName(1:2)); %! assert_equal (h.Properties.VariableUnits, {'Yrs', ''}); %! assert_equal (h.Properties.Description, 'demo'); ## Test 'head' preserves multicolumn variables %!test %! h = head (table (Age, BloodPressure), 2); %! assert_equal (h.BloodPressure, BloodPressure(1:2, :)); ## Test 'head' preserves special data type variables %!test %! du = duration ((1:5)', 0, 0); %! ca = categorical ({'a'; 'b'; 'c'; 'd'; 'e'}); %! h = head (table (du, ca), 2); %! assert_equal (class (h.du), 'duration'); %! assert_equal (class (h.ca), 'categorical'); %! assert_equal (seconds (h.du), [3600; 7200]); %! assert_equal (cellstr (h.ca), {'a'; 'b'}); ## Test 'head' with no output argument displays the first K rows %!test %! R = table (Age, Smoker, 'RowNames', LastName); %! str = evalc ('head (R, 2);'); %! assert_equal (! isempty (strfind (str, 'Sanchez')), true); %! assert_equal (! isempty (strfind (str, 'Johnson')), true); %! assert_equal (isempty (strfind (str, 'Li')), true); ## Test input validation for 'head' %!error ... %! head (T, 1.5); %!error ... %! head (T, 0); %!error ... %! head (T, -2); %!error ... %! head (T, [1, 2]); %!error ... %! [out1, out2] = head (T, 2); ## Test 'tail' returns the last K rows in a new table %!test %! t = tail (T, 3); %! assert_equal (height (t), 3); %! assert_equal (t.Age, Age(3:5)); %! assert_equal (class (t), 'table'); ## Test 'tail' default count is eight rows %!assert_equal (height (tail (T)), 5); %!test %! big = table ((1:20)'); %! assert_equal (height (tail (big)), 8); %! t = tail (big); %! assert_equal (t.Var1, (13:20)'); ## Test 'tail' with K equal to the number of rows returns the whole table %!assert_equal (height (tail (T, 5)), 5); ## Test 'tail' preserves the original row order %!test %! t = tail (T, 5); %! assert_equal (t.Age, Age); ## Test 'tail' with K greater than the number of rows returns all rows %!assert_equal (height (tail (T, 99)), 5); ## Test 'tail' with K of one returns just the last row %!test %! t1 = tail (T, 1); %! assert_equal (height (t1), 1); %! assert_equal (t1.Age, Age(5)); ## Test 'tail' of an empty (zero-row) table returns an empty table %!test %! e = table ('Size', [0, 2], 'VariableTypes', {'double', 'double'}); %! assert_equal (size (tail (e, 3)), [0, 2]); ## Test 'tail' preserves variable names, row names, and properties %!test %! R = table (Age, Smoker, 'RowNames', LastName); %! R.Properties.VariableUnits = {'Yrs', ''}; %! R.Properties.Description = 'demo'; %! t = tail (R, 2); %! assert_equal (t.Properties.VariableNames, {'Age', 'Smoker'}); %! assert_equal (t.Properties.RowNames, LastName(4:5)); %! assert_equal (t.Properties.VariableUnits, {'Yrs', ''}); %! assert_equal (t.Properties.Description, 'demo'); ## Test 'tail' preserves multicolumn variables %!test %! t = tail (table (Age, BloodPressure), 2); %! assert_equal (t.BloodPressure, BloodPressure(4:5, :)); ## Test 'tail' preserves special data type variables %!test %! du = duration ((1:5)', 0, 0); %! ca = categorical ({'a'; 'b'; 'c'; 'd'; 'e'}); %! t = tail (table (du, ca), 2); %! assert_equal (class (t.du), 'duration'); %! assert_equal (class (t.ca), 'categorical'); %! assert_equal (seconds (t.du), [14400; 18000]); %! assert_equal (cellstr (t.ca), {'d'; 'e'}); ## Test 'tail' with no output argument displays the last K rows %!test %! R = table (Age, Smoker, 'RowNames', LastName); %! str = evalc ('tail (R, 2);'); %! assert_equal (! isempty (strfind (str, 'Diaz')), true); %! assert_equal (! isempty (strfind (str, 'Brown')), true); %! assert_equal (isempty (strfind (str, 'Sanchez')), true); ## Test input validation for 'tail' %!error ... %! tail (T, 1.5); %!error ... %! tail (T, 0); %!error ... %! tail (T, -2); %!error ... %! tail (T, [1, 2]); %!error ... %! [out1, out2] = tail (T, 2); ################################################################################ ## ** Sort, Filter, and Rearrange ** ## ################################################################################ ## Available Methods ## ## ## ## 'sortrows' 'unique' 'issortedrows' 'topkrows' ## ## 'addvars' 'renamevars' 'movevars' 'removevars' ## ## 'splitvars' 'mergevars' 'convertvars' 'rows2vars' ## ## 'stack' 'unstack' 'inner2outer' 'addprop' ## ## 'rmprop' ## ## ## ################################################################################ %!shared LastName, Age, Smoker, Height, Weight, BloodPressure, T, tblA %! LastName = {"Sanchez"; "Johnson"; "Li"; "Diaz"; "Brown"}; %! Age = [38; 43; 38; 40; 49]; %! Smoker = logical ([1; 0; 1; 0; 1]); %! Height = [71; 69; 64; 67; 64]; %! Weight = [176; 163; 131; 133; 119]; %! BloodPressure = [124, 93; 109, 77; 125, 83; 117, 75; 122, 80]; %! T = table (Age, Smoker, Height, Weight, BloodPressure); ## Test 'sortrows' and 'issortedrows' methods %!test %! tblA = table (Age, Height, Weight, BloodPressure, 'RowNames', LastName); %! assert_equal (issortedrows (tblA, "RowNames"), false); %! [sorted_tblA, index] = sortrows (tblA, "RowNames"); %! assert_equal (index, [5; 4; 2; 3; 1]); %! assert_equal (issortedrows (sorted_tblA, "RowNames"), true); %! [sorted_tblA, index] = sortrows (tblA, "RowNames", "descend"); %! assert_equal (index, [1; 3; 2; 4; 5]); %! assert_equal (issortedrows (sorted_tblA, "RowNames", "descend"), true); %!test # duration variable (regression: 'days' is a method, not a property) %! A = table (hours ([3;1;2]), {'a';'b';'c'}, 'VariableNames', {'D', 'S'}); %! [S, index] = sortrows (A); %! assert_equal (hours (S.D), [1;2;3]); %! assert_equal (S.S, {'b';'c';'a'}); %! assert_equal (index, [2;3;1]); %!test %! tblA.Properties.DimensionNames(1) = "Patients"; %! assert_equal (issortedrows (tblA, "Patients"), false); %! [sorted_tblA, index] = sortrows (tblA, "Patients"); %! assert_equal (index, [5; 4; 2; 3; 1]); %! assert_equal (issortedrows (sorted_tblA, "Patients"), true); %! [sorted_tblA, index] = sortrows (tblA, "Patients", "descend"); %! assert_equal (index, [1; 3; 2; 4; 5]); %! assert_equal (issortedrows (sorted_tblA, "Patients", "descend"), true); %!test %! [sorted_tblA, index] = sortrows (tblA); %! assert_equal (index, [3; 1; 4; 2; 5]); %! assert_equal (issortedrows (sorted_tblA), true); %! [sorted_tblA, index] = sortrows (tblA, [], "ascend"); %! assert_equal (index, [3; 1; 4; 2; 5]); %! assert_equal (issortedrows (sorted_tblA, [], "ascend"), true); %! [sorted_tblA, index] = sortrows (tblA, [], "descend"); %! assert_equal (index, [5; 2; 4; 1; 3]); %! assert_equal (issortedrows (sorted_tblA, [], "descend"), true); %!test %! [tblB, index] = sortrows (tblA, "Height"); %! assert_equal (index, [3; 5; 4; 2; 1]); %! assert_equal (tblB.Properties.RowNames, ... %! {"Li"; "Brown"; "Diaz"; "Johnson"; "Sanchez"}) %! assert_equal (issortedrows (tblB, "Height"), true); %!test %! LastName = {"Sweet"; "Jacobson"; "Wang"; "Joiner"; "Berger"}; %! tblA = table (Age, Height, Weight, BloodPressure, 'RowNames', LastName); %! [tblB, index] = sortrows (tblA, {"Height", "Weight"}, {"ascend", "descend"}); %! assert_equal (index, [3; 5; 4; 2; 1]); %! assert_equal (tblB.Properties.RowNames, ... %! {"Wang"; "Berger"; "Joiner"; "Jacobson"; "Sweet"}) %! assert_equal (issortedrows (tblB, {"Height", "Weight"}, ... %! {"ascend", "descend"}), true); %!test %! [tblB, index] = sortrows (tblA, {"BloodPressure"}, {"ascend"}); %! assert_equal (index, [2; 4; 5; 1; 3]); %! assert_equal (tblB.BloodPressure, sortrows (BloodPressure)); %! assert_equal (issortedrows (tblB, {"BloodPressure"}, {"ascend"}), true); %! [tblB, index] = sortrows (tblA, {"BloodPressure"}, {"descend"}); %! assert_equal (index, flip ([2; 4; 5; 1; 3])); %! assert_equal (tblB.BloodPressure, sortrows (BloodPressure, -1)); %! assert_equal (issortedrows (tblB, "BloodPressure", "descend"), true); %!test %! [tblB, index] = sortrows (tblA, {"Height", "RowNames"}, {"ascend"}); %! assert_equal (index, [5; 3; 4; 2; 1]); %! assert_equal (tblB.Properties.RowNames, ... %! {"Berger"; "Wang"; "Joiner"; "Jacobson"; "Sweet"}) %! assert_equal (tblB.Height, sortrows (Height)); %! assert_equal (issortedrows (tblB, {"Height", "RowNames"}, {"ascend"}), true); %! [tblB, index] = sortrows (tblA, {"Height", "RowNames"}, ... %! {"ascend", "descend"}); %! assert_equal (index, [3; 5; 4; 2; 1]); %! assert_equal (tblB.Properties.RowNames, ... %! {"Wang"; "Berger"; "Joiner"; "Jacobson"; "Sweet"}) %! assert_equal (issortedrows (tblB, {"Height", "RowNames"}, ... %! {"ascend", "descend"}), true); %!test %! [tblB, index] = sortrows (tblA, [true, true, false, false]); %! assert_equal (index, [3; 1; 4; 2; 5]); %! assert_equal (issortedrows (tblB, [true, true, false, false]), true); %! [tblB, index] = sortrows (tblA, [true, true, false, false], "ascend"); %! assert_equal (index, [3; 1; 4; 2; 5]); %! assert_equal (issortedrows (tblB, [true, true, false, false], "ascend"), true); %! [tblB, index] = sortrows (tblA, [true, true, false, false], "descend"); %! assert_equal (index, flip ([3; 1; 4; 2; 5])); %! assert_equal (issortedrows (tblB, [true, true, false, false], "descend"), true); %! [tblB, index] = sortrows (tblA, [true, true, false, false], ... %! {"ascend", "descend"}); %! assert_equal (index, [1; 3; 4; 2; 5]); %! assert_equal (issortedrows (tblB, [true, true, false, false], ... %! {"ascend", "descend"}), true); %!test %! [sorted_tblA, indexN] = sortrows (tblA, 1); %! assert_equal (issortedrows (sorted_tblA, 1), true); %! [sorted_tblA, indexV] = sortrows (tblA, "Age"); %! assert_equal (issortedrows (sorted_tblA, "Age"), true); %! assert_equal (indexN, indexV); %! [sorted_tblA, indexN] = sortrows (tblA, -1); %! assert_equal (issortedrows (sorted_tblA, -1), true); %! [sorted_tblA, indexV] = sortrows (tblA, "Age", "descend"); %! assert_equal (issortedrows (sorted_tblA, "Age", "descend"), true); %! assert_equal (indexN, indexV); %! [sorted_tblA, indexN] = sortrows (tblA, -1, "ascend"); %! assert_equal (issortedrows (sorted_tblA, -1, "ascend"), true); %! [~, indexV] = sortrows (tblA, "Age"); %! assert_equal (indexN, indexV); %! [sorted_tblA, indexN] = sortrows (tblA, [-1, 2]); %! assert_equal (issortedrows (sorted_tblA, [-1, 2]), true); %! [~, indexV] = sortrows (tblA, {"Age", "Height"}, {"descend", "ascend"}); %! assert_equal (indexN, indexV); %!test %! TT = table (Age, table (Age, Height), BloodPressure, 'RowNames', LastName); %! [sorted_TT, index] = sortrows (TT); %! assert_equal (index, [3; 1; 4; 2; 5]); %! assert_equal (issortedrows (sorted_TT), true); %! [sorted_TT, index] = sortrows (TT, [1, 2]); %! assert_equal (index, [3; 1; 4; 2; 5]); %! assert_equal (issortedrows (sorted_TT, [1, 2]), true); %! [sorted_TT, index] = sortrows (TT, [1, -2]); %! assert_equal (index, [1; 3; 4; 2; 5]); %! assert_equal (issortedrows (sorted_TT, [1, -2]), true); %!test %! Weights = [176;NaN;131;133;NaN]; %! tblN = table (Age, Height, Weights, BloodPressure, 'RowNames', LastName); %! [tblB, index] = sortrows (tblN, "Weights", "MissingPlacement", "first"); %! assert_equal (index, [2; 5; 3; 4; 1]); %! assert_equal (issortedrows (tblB, "Weights", "MissingPlacement", "first"), true); %! [tblB, index] = sortrows (tblN, "Weights"); %! assert_equal (index, [3; 4; 1; 2; 5]); %! assert_equal (issortedrows (tblB, "Weights"), true); %!test %! tblN = table (Age, Height, Weight, BloodPressure, LastName); %! [tblB, index] = sortrows (tblN, vartype ("numeric")); %! assert_equal (index, [3; 1; 4; 2; 5]); %! assert_equal (issortedrows (tblB, vartype ("numeric")), true); %! [tblB, index] = sortrows (tblN, vartype ("cellstr")); %! assert_equal (index, [5; 2; 4; 1; 3]); %! assert_equal (issortedrows (tblB, vartype ("cellstr")), true); %!test # special-type sort keys (datetime / categorical / string / calendarDuration) %! S = {'b'; 'a'; 'c'; 'a'}; %! dt = datetime (2020, [3; 1; 2; 1], 1); %! A = table (dt, S, 'VariableNames', {'D', 'S'}); %! [tblB, index] = sortrows (A, 'D'); %! assert_equal (index, [2; 4; 3; 1]); %! assert_equal (issortedrows (tblB, 'D'), true); %! [tblB, index] = sortrows (A, 'D', 'descend'); %! assert_equal (index, [1; 3; 2; 4]); %! assert_equal (issortedrows (tblB, 'D', 'descend'), true); %! c = categorical ({'mid'; 'low'; 'high'; 'low'}, {'low', 'mid', 'high'}, ... %! 'Ordinal', true); %! [tblB, index] = sortrows (table (c, 'VariableNames', {'C'}), 'C'); %! assert_equal (index, [2; 4; 1; 3]); %! assert_equal (issortedrows (tblB, 'C'), true); %! st = string ({'banana'; 'apple'; 'cherry'; 'apple'}); %! [tblB, index] = sortrows (table (st, 'VariableNames', {'St'})); %! assert_equal (index, [2; 4; 1; 3]); %! assert_equal (issortedrows (tblB), true); %! cd = calendarDuration ([0; 2; 1; 0], [3; 1; 2; 1], [0; 0; 0; 0]); %! [tblB, index] = sortrows (table (cd, 'VariableNames', {'CD'})); %! assert_equal (index, [4; 1; 3; 2]); %! assert_equal (issortedrows (tblB), true); %!test # 'ComparisonMethod' with complex data ('abs' and 'real') %! z = [3+4i; -1+0i; 0+2i; -5+0i]; %! A = table (z, 'VariableNames', {'Z'}); %! [tblB, index] = sortrows (A, 'Z', 'ComparisonMethod', 'abs'); %! assert_equal (index, [2; 3; 1; 4]); %! [tblB, index] = sortrows (A, 'Z', 'ComparisonMethod', 'real'); %! assert_equal (index, [4; 2; 3; 1]); %!test # 'MissingPlacement' 'last' and 'auto' %! W = [176; NaN; 131; 133; NaN]; %! tblN = table (Age, W, 'VariableNames', {'Age', 'W'}); %! [tblB, index] = sortrows (tblN, 'W', 'MissingPlacement', 'last'); %! assert_equal (index, [3; 4; 1; 2; 5]); %! assert_equal (issortedrows (tblN, 'W', 'MissingPlacement', 'last'), false); %! [tblB, index] = sortrows (tblN, 'W', 'MissingPlacement', 'auto'); %! assert_equal (index, [3; 4; 1; 2; 5]); ## Test input validation for 'sortrows' method %!error ... %! sortrows (tblA, "MissingPlacement", "param"); %!error ... %! sortrows (tblA, "ComparisonMethod", "param"); %!error ... %! sortrows (tblA, [], "ascend", "param"); %!error ... %! sortrows (tblA, [], "aaascend"); %!error ... %! sortrows (tblA, "RowNames", {"ascend", "ascend"}); %!error ... %! sortrows (tblA, "Row", {"ascend", "ascend"}); %!error ... %! sortrows (tblA, [true, true, false]); %!error ... %! sortrows (tblA, "Age", {"ascend", "ascend"}); %!error ... %! sortrows (tblA, [1, 0, 3]); %!error ... %! sortrows (tblA, [1, 6, 3]); %!error ... %! sortrows (tblA, [1, 3, 2], {"ascend", "ascend"}); %!error ... %! sortrows (tblA, {"Age", "whatever"}); %!error ... %! sortrows (tblA, {"Age", "Height", "Weight"}, {"ascend", "ascend"}); %!error ... %! sortrows (tblA, vartype ("numeric"), {"ascend", "ascend"}); %!error ... %! sortrows (table ({5; 4; 3})); %!error ... %! sortrows (table ([struct("s", 4); struct("s", 5); struct("s", 6)])); %!error ... %! sortrows (table (Age, table (Age, LastName), "RowNames", LastName)) ## Test 'unique' method %!test %! Name = {'Fred'; 'Betty'; 'Bob'; 'George'; 'Jane'}; %! Age = [38; 43; 38; 40; 38]; %! Height = [71; 69; 64; 67; 64]; %! Weight = [176; 163; 131; 185; 131]; %! A = table (Age, Height, Weight, 'RowNames', Name); %! [C, ia, ic] = unique (A); %! assert_equal (ia, [3; 1; 4; 2]); %! assert_equal (ic, [2; 4; 1; 3; 1]); %! assert_equal (C.Age, [38; 38; 40; 43]); %! assert_equal (C.Height, [64; 71; 67; 69]); %! assert_equal (C.Weight, [131; 176; 185; 163]); %! assert_equal (C.Properties.RowNames, {'Bob'; 'Fred'; 'George'; 'Betty'}); %!test # duration variable (regression: 'days' is a method, not a property) %! A = table (hours ([2;1;2;3]), {'a';'b';'c';'a'}, 'VariableNames', {'D','S'}); %! C = unique (A); %! assert_equal (hours (C.D), [1;2;2;3]); %! assert_equal (C.S, {'b';'a';'c';'a'}); %!test %! Name = {'Fred'; 'Betty'; 'Bob'; 'George'; 'Jane'}; %! Age = [38; 43; 38; 40; 38]; %! Height = [71; 69; 64; 67; 64]; %! Weight = [176; 163; 131; 185; 131]; %! A = table (Age, Height, Weight, 'RowNames', Name); %! [C, ia, ic] = unique (A, "sorted"); %! assert_equal (ia, [3; 1; 4; 2]); %! assert_equal (ic, [2; 4; 1; 3; 1]); %! assert_equal (C.Age, [38; 38; 40; 43]); %! assert_equal (C.Height, [64; 71; 67; 69]); %! assert_equal (C.Weight, [131; 176; 185; 163]); %! assert_equal (C.Properties.RowNames, {'Bob'; 'Fred'; 'George'; 'Betty'}); %!test %! Name = {'Fred'; 'Betty'; 'Bob'; 'George'; 'Jane'}; %! Age = [38; 43; 38; 40; 38]; %! Height = [71; 69; 64; 67; 64]; %! Weight = [176; 163; 131; 185; 131]; %! A = table (Age, Height, Weight, 'RowNames', Name); %! [C, ia, ic] = unique (A, "stable"); %! assert_equal (ia, [1; 2; 3; 4]); %! assert_equal (ic, [1; 2; 3; 4; 3]); %! assert_equal (C.Age, [38; 43; 38; 40]); %! assert_equal (C.Height, [71; 69; 64; 67]); %! assert_equal (C.Weight, [176; 163; 131; 185]); %! assert_equal (C.Properties.RowNames, {'Fred'; 'Betty'; 'Bob'; 'George'}); %!test %! Name = {'Fred'; 'Betty'; 'Bob'; 'George'; 'Jane'}; %! Age = [38; 43; 38; 40; 38]; %! Height = [71; 69; 64; 67; 64]; %! Weight = [176; 163; 131; 185; 131]; %! A = table (Age, Height, Weight, 'RowNames', Name); %! [C, ia, ic] = unique (A, "first"); %! assert_equal (ia, [3; 1; 4; 2]); %! assert_equal (ic, [2; 4; 1; 3; 1]); %! assert_equal (C.Age, [38; 38; 40; 43]); %! assert_equal (C.Height, [64; 71; 67; 69]); %! assert_equal (C.Weight, [131; 176; 185; 163]); %! assert_equal (C.Properties.RowNames, {'Bob'; 'Fred'; 'George'; 'Betty'}); %!test %! Name = {'Fred'; 'Betty'; 'Bob'; 'George'; 'Jane'}; %! Age = [38; 43; 38; 40; 38]; %! Height = [71; 69; 64; 67; 64]; %! Weight = [176; 163; 131; 185; 131]; %! A = table (Age, Height, Weight, 'RowNames', Name); %! [C, ia, ic] = unique (A, "last"); %! assert_equal (ia, [5; 1; 4; 2]); %! assert_equal (ic, [2; 4; 1; 3; 1]); %! assert_equal (C.Age, [38; 38; 40; 43]); %! assert_equal (C.Height, [64; 71; 67; 69]); %! assert_equal (C.Weight, [131; 176; 185; 163]); %! assert_equal (C.Properties.RowNames, {'Jane'; 'Fred'; 'George'; 'Betty'}); %!test # special-type variables (datetime / categorical / calendarDuration) %! dt = datetime (2020, [2; 1; 2; 3], 1); %! [C, ia, ic] = unique (table (dt, 'VariableNames', {'D'})); %! assert_equal (ia, [2; 1; 4]); %! assert_equal (ic, [2; 1; 2; 3]); %! c = categorical ({'mid'; 'low'; 'mid'; 'high'}, {'low', 'mid', 'high'}, ... %! 'Ordinal', true); %! [C, ia, ic] = unique (table (c, 'VariableNames', {'C'})); %! assert_equal (ia, [2; 1; 4]); %! assert_equal (ic, [2; 1; 2; 3]); %! cd = calendarDuration ([0; 1; 0; 2], [3; 0; 3; 0], [0; 0; 0; 0]); %! [C, ia, ic] = unique (table (cd, 'VariableNames', {'CD'})); %! assert_equal (ia, [1; 2; 4]); %! assert_equal (ic, [1; 2; 1; 3]); %!test # text variables sort lexicographically, 'stable' keeps encounter order %! S = {'banana'; 'apple'; 'banana'; 'cherry'}; %! [C, ia, ic] = unique (table (S, 'VariableNames', {'S'})); %! assert_equal (C.S, {'apple'; 'banana'; 'cherry'}); %! assert_equal (ia, [2; 1; 4]); %! assert_equal (ic, [2; 1; 2; 3]); %! C = unique (table (S, 'VariableNames', {'S'}), 'stable'); %! assert_equal (C.S, {'banana'; 'apple'; 'cherry'}); %! St = string ({'banana'; 'apple'; 'banana'; 'cherry'}); %! C = unique (table (St, 'VariableNames', {'St'})); %! assert_equal (cellstr (C.St), {'apple'; 'banana'; 'cherry'}); %!test # nested numeric table variable (regression: brace-indexed extraction) %! Ag = [38; 43; 38]; Hi = [71; 69; 71]; %! [C, ia, ic] = unique (table (table (Ag, Hi), 'VariableNames', {'N'})); %! assert_equal (ia, [1; 2]); %! assert_equal (ic, [1; 2; 1]); ## Test input validation for 'unique' method %!error unique (tblA, 1, 2); %!error unique (tblA, "some"); %!error ... %! unique (table ({2; 2; 3}), "sorted"); %!error ... %! unique (table (struct ("a", {2; 2; 3})), "sorted"); %!error ... %! unique (table (table (Age, LastName)), "sorted"); ## Test 'topkrows' method %!test %! LastName = {"Sanchez"; "Johnson"; "Li"; "Diaz"; "Brown"}; %! Age = [38; 43; 38; 40; 49]; %! Smoker = logical ([1; 0; 1; 0; 1]); %! Height = [71; 69; 64; 67; 64]; %! Weight = [176; 163; 131; 133; 119]; %! BloodPressure = [124, 93; 109, 77; 125, 83; 117, 75; 122, 80]; %! tblA = table (Age, Height, Weight, BloodPressure, 'RowNames', LastName); %! [sorted_tblA, index] = topkrows (tblA, 3, "RowNames"); %! assert_equal (index, [1; 3; 2]); %! assert_equal (issortedrows (sorted_tblA, "RowNames", "descend"), true); %! [sorted_tblA, index] = topkrows (tblA, 4, "RowNames", "descend"); %! assert_equal (index, [1; 3; 2; 4]); %! assert_equal (issortedrows (sorted_tblA, "RowNames", "descend"), true); %!test %! tblA.Properties.DimensionNames(1) = "Patients"; %! assert_equal (issortedrows (tblA, "Patients"), false); %! [sorted_tblA, index] = topkrows (tblA, 2, "Patients"); %! assert_equal (index, [1; 3]); %! assert_equal (issortedrows (sorted_tblA, "Patients", "descend"), true); %! [sorted_tblA, index] = topkrows (tblA, 5, "Patients", "descend"); %! assert_equal (index, [1; 3; 2; 4; 5]); %! assert_equal (issortedrows (sorted_tblA, "Patients", "descend"), true); %!test %! [sorted_tblA, index] = topkrows (tblA, 3); %! assert_equal (index, [5; 2; 4]); %! assert_equal (issortedrows (sorted_tblA, [], "descend"), true); %! [sorted_tblA, index] = topkrows (tblA, 3, ":", "ascend"); %! assert_equal (index, [3; 1; 4]); %! assert_equal (issortedrows (sorted_tblA, [], "ascend"), true); %! [sorted_tblA, index] = topkrows (tblA, 3, [], "descend"); %! assert_equal (index, [5; 2; 4]); %! assert_equal (issortedrows (sorted_tblA, [], "descend"), true); %!test %! [tblB, index] = topkrows (tblA, 3, "Height"); %! assert_equal (index, [1; 2; 4]); %! assert_equal (tblB.Properties.RowNames, {"Sanchez"; "Johnson"; "Diaz"}); %! assert_equal (issortedrows (tblB, "Height", "descend"), true); %!test %! LastName = {"Sweet"; "Jacobson"; "Wang"; "Joiner"; "Berger"}; %! tblA = table (Age, Height, Weight, BloodPressure, 'RowNames', LastName); %! [tblB, index] = topkrows (tblA, 2, {"Height", "Weight"}, ... %! {"ascend", "descend"}); %! assert_equal (index, [3; 5]); %! assert_equal (tblB.Properties.RowNames, {"Wang"; "Berger"}); %! assert_equal (issortedrows (tblB, {"Height", "Weight"}, ... %! {"ascend", "descend"}), true); %!test %! [tblB, index] = topkrows (tblA, 4, {"BloodPressure"}, {"ascend"}); %! assert_equal (index, [2; 4; 5; 1]); %! assert_equal (issortedrows (tblB, {"BloodPressure"}, {"ascend"}), true); %! [tblB, index] = topkrows (tblA, 3, {"BloodPressure"}, {"descend"}); %! assert_equal (index, [3; 1; 5]); %! assert_equal (issortedrows (tblB, "BloodPressure", "descend"), true); %!test %! [tblB, index] = topkrows (tblA, 3, {"Height", "RowNames"}, {"ascend"}); %! assert_equal (index, [5; 3; 4]); %! assert_equal (tblB.Properties.RowNames, {"Berger"; "Wang"; "Joiner"}) %! assert_equal (issortedrows (tblB, {"Height", "RowNames"}, {"ascend"}), true); %! [tblB, index] = topkrows (tblA, 4, {"Height", "RowNames"}, ... %! {"ascend", "descend"}); %! assert_equal (index, [3; 5; 4; 2]); %! assert_equal (tblB.Properties.RowNames, {"Wang"; "Berger"; "Joiner"; "Jacobson"}); %! assert_equal (issortedrows (tblB, {"Height", "RowNames"}, ... %! {"ascend", "descend"}), true); %!test %! [tblB, index] = topkrows (tblA, 3, [true, true, false, false]); %! assert_equal (index, [5; 2; 4]); %! assert_equal (issortedrows (tblB, [true, true, false, false], "descend"), ... %! true); %! [tblB, index] = topkrows (tblA, 2, [true, true, false, false], "ascend"); %! assert_equal (index, [3; 1]); %! assert_equal (issortedrows (tblB, [true, true, false, false], "ascend"), true); %! [tblB, index] = topkrows (tblA, 5, [true, true, false, false], "descend"); %! assert_equal (index, flip ([3; 1; 4; 2; 5])); %! assert_equal (issortedrows (tblB, [true, true, false, false], "descend"), true); %! [tblB, index] = topkrows (tblA, 6, [true, true, false, false], ... %! {"ascend", "descend"}); %! assert_equal (index, [1; 3; 4; 2; 5]); %! assert_equal (issortedrows (tblB, [true, true, false, false], ... %! {"ascend", "descend"}), true); %!test %! ## A positive numeric index sorts descending (opposite of sortrows), so a %! ## positive index matches the descending default of a named variable. %! [sorted_tblA, indexN] = topkrows (tblA, 2, 1); %! assert_equal (issortedrows (sorted_tblA, "Age", "descend"), true); %! [sorted_tblA, indexV] = topkrows (tblA, 2, "Age"); %! assert_equal (issortedrows (sorted_tblA, "Age", "descend"), true); %! assert_equal (indexN, indexV); %! assert_equal (indexN, [5; 2]); %! ## A negative numeric index sorts ascending. %! [sorted_tblA, indexN] = topkrows (tblA, 3, -1); %! assert_equal (issortedrows (sorted_tblA, "Age", "ascend"), true); %! [sorted_tblA, indexV] = topkrows (tblA, 3, "Age", "ascend"); %! assert_equal (indexN, indexV); %! assert_equal (indexN, [1; 3; 4]); %! ## An explicit DIRECTION ignores the sign of a numeric index. %! [sorted_tblA, indexN] = topkrows (tblA, 5, -1, "ascend"); %! [~, indexV] = topkrows (tblA, 5, "Age", "ascend"); %! assert_equal (indexN, indexV); %! [sorted_tblA, indexN] = topkrows (tblA, 3, [-1, 2]); %! [~, indexV] = topkrows (tblA, 3, {"Age", "Height"}, {"ascend", "descend"}); %! assert_equal (indexN, indexV); %! assert_equal (indexN, [1; 3; 4]); %!test %! TT = table (Age, table (Age, Height), BloodPressure, 'RowNames', LastName); %! [sorted_TT, index] = topkrows (TT, 4); %! assert_equal (index, [5; 2; 4; 1]); %! assert_equal (issortedrows (sorted_TT, [], "descend"), true); %! [sorted_TT, index] = topkrows (TT, 3, [1, 2]); %! assert_equal (index, [5; 2; 4]); %! assert_equal (issortedrows (sorted_TT, [-1, -2]), true); %! [sorted_TT, index] = topkrows (TT, 2, [1, -2]); %! assert_equal (index, [5; 2]); %! assert_equal (issortedrows (sorted_TT, [-1, 2]), true); %!test %! Weights = [176;NaN;131;133;NaN]; %! tblN = table (Age, Height, Weights, BloodPressure, 'RowNames', LastName); %! [tblB, index] = topkrows (tblN, 4, "Weights", "MissingPlacement", "first"); %! assert_equal (index, [2; 5; 1; 4]); %! assert_equal (issortedrows (tblB, "Weights", "descend", ... %! "MissingPlacement", "first"), true); %! [tblB, index] = topkrows (tblN, 3, "Weights"); %! assert_equal (index, [2; 5; 1]); %! assert_equal (issortedrows (tblB, "Weights", "descend"), true); %!test %! tblN = table (Age, Height, Weight, BloodPressure, LastName); %! [tblB, index] = topkrows (tblN, 3, vartype ("numeric")); %! assert_equal (index, [5; 2; 4]); %! assert_equal (issortedrows (tblB, vartype ("numeric"), "descend"), true); %! [tblB, index] = topkrows (tblN, 3, vartype ("cellstr")); %! assert_equal (index, [3; 1; 4]); %! assert_equal (issortedrows (tblB, vartype ("cellstr"), "descend"), true); %!test # special-type sort keys (datetime / categorical / string / calendarDuration) %! dt = datetime (2020, [3; 1; 2; 1], 1); %! [tblB, index] = topkrows (table (dt, 'VariableNames', {'D'}), 2); %! assert_equal (index, [1; 3]); %! assert_equal (issortedrows (tblB, 'D', 'descend'), true); %! c = categorical ({'mid'; 'low'; 'high'}, {'low', 'mid', 'high'}, ... %! 'Ordinal', true); %! [tblB, index] = topkrows (table (c, 'VariableNames', {'C'}), 2); %! assert_equal (index, [3; 1]); %! st = string ({'b'; 'a'; 'c'}); %! [tblB, index] = topkrows (table (st, 'VariableNames', {'S'}), 2); %! assert_equal (index, [3; 1]); %! cd = calendarDuration ([0; 2; 1], [3; 1; 2], [0; 0; 0]); %! [tblB, index] = topkrows (table (cd, 'VariableNames', {'CD'}), 2); %! assert_equal (index, [2; 3]); %!test # K edge cases: zero, equal to height, larger than height %! Ag = [38; 43; 38]; %! A = table (Ag, 'VariableNames', {'A'}); %! tblB = topkrows (A, 0); %! assert_equal (height (tblB), 0); %! assert_equal (width (tblB), 1); %! [tblB, index] = topkrows (A, 3); %! assert_equal (index, [2; 1; 3]); %! [tblB, index] = topkrows (A, 10); %! assert_equal (index, [2; 1; 3]); %! assert_equal (height (tblB), 3); %!test # 'ComparisonMethod' passes through to sortrows %! z = [3+4i; -1+0i; 0+2i; -5+0i]; %! [tblB, index] = topkrows (table (z, 'VariableNames', {'Z'}), 2, 'Z', ... %! 'ComparisonMethod', 'abs'); %! assert_equal (index, [4; 1]); ## Test input validation for 'topkrows' method %!error ... %! topkrows (tblA, -1) %!error ... %! topkrows (tblA, 2.5) ## Test 'addvars' method %!test %! load patients %! T1 = table (Age, Height, Weight); %! T2 = addvars (T1, ones (size (Age))); %! assert_equal (size (head (T2, 3)), [3, 4]); %! assert_equal (T2.Properties.VariableNames, {'Age', 'Height', 'Weight', 'Var4'}); %!test %! load patients %! T1 = table (LastName, Age, Height, Weight); %! assert_equal (size (head (T1, 3)), [3, 4]); %! assert_equal (T1.Properties.VariableNames, {'LastName', 'Age', 'Height', 'Weight'}); %! T2 = addvars (T1, Gender, Smoker); %! assert_equal (size (head (T2, 3)), [3, 6]); %! assert_equal (T2.Properties.VariableNames, ... %! {'LastName', 'Age', 'Height', 'Weight', 'Gender', 'Smoker'}); %!test %! load patients %! T1 = table (LastName, Gender); %! assert_equal (size (head (T1, 3)), [3, 2]); %! assert_equal (T1.Properties.VariableNames, {'LastName', 'Gender'}); %! T2 = addvars (T1, Age, 'Before', 'Gender'); %! assert_equal (size (head (T2, 5)), [5, 3]); %! assert_equal (T2.Properties.VariableNames, {'LastName', 'Age', 'Gender'}); %!test %! load patients %! T1 = table (LastName, Age, Gender); %! assert_equal (size (head (T1, 4)), [4, 3]); %! assert_equal (T1.Properties.VariableNames, {'LastName', 'Age', 'Gender'}); %! T2 = addvars (T1, Height, Weight, 'After', 'Age'); %! assert_equal (size (head (T2, 4)), [4, 5]); %! assert_equal (T2.Properties.VariableNames, ... %! {'LastName', 'Age', 'Height', 'Weight', 'Gender'}); %!test %! load patients %! T1 = table (LastName, Age, Height, Weight, Gender); %! assert_equal (size (head (T1, 3)), [3, 5]); %! assert_equal (T1.Properties.VariableNames, ... %! {'LastName', 'Age', 'Height', 'Weight', 'Gender'}); %! T2 = addvars (T1, Smoker, 'After', 1); %! assert_equal (size (head (T2, 3)), [3, 6]); %! assert_equal (T2.Properties.VariableNames, ... %! {'LastName', 'Smoker', 'Age', 'Height', 'Weight', 'Gender'}); %!test %! load patients %! T1 = table (LastName, Age, Gender, Smoker); %! assert_equal (size (head (T1, 3)), [3, 4]); %! assert_equal (T1.Properties.VariableNames, {'LastName', 'Age', 'Gender', 'Smoker'}); %! T2 = addvars (T1, [Diastolic Systolic], 'NewVariableNames', 'BloodPressure'); %! assert_equal (size (head (T2, 3)), [3, 5]); %! assert_equal (T2.Properties.VariableNames, ... %! {'LastName', 'Age', 'Gender', 'Smoker', 'BloodPressure'}); %!test %! load patients %! T1 = table (LastName, Age, Gender, Smoker); %! assert_equal (size (head (T1, 3)), [3, 4]); %! assert_equal (T1.Properties.VariableNames, {'LastName', 'Age', 'Gender', 'Smoker'}); %! T2 = addvars (T1, Height, Weight, 'Before', 'Smoker', ... %! 'NewVariableNames', {'Inches', 'Pounds'}); %! assert_equal (size (head (T2, 3)), [3, 6]); %! assert_equal (T2.Properties.VariableNames, ... %! {'LastName', 'Age', 'Gender', 'Inches', 'Pounds', 'Smoker'}); %!test # 'Before'/'After' with logical-vector and numeric LOCATION %! T = table ([1;2], [3;4], [5;6], 'VariableNames', {'A', 'B', 'C'}); %! nv = [7; 8]; %! R = addvars (T, nv, 'Before', [false, true, false], ... %! 'NewVariableNames', 'X'); %! assert_equal (R.Properties.VariableNames, {'A', 'X', 'B', 'C'}); %! R = addvars (T, nv, 'After', [false, true, false], ... %! 'NewVariableNames', 'X'); %! assert_equal (R.Properties.VariableNames, {'A', 'B', 'X', 'C'}); %! R = addvars (T, nv, 'Before', 2, 'NewVariableNames', 'X'); %! assert_equal (R.Properties.VariableNames, {'A', 'X', 'B', 'C'}); %! R = addvars (T, nv, 'Before', 1, 'NewVariableNames', 'X'); %! assert_equal (R.Properties.VariableNames, {'X', 'A', 'B', 'C'}); %! R = addvars (T, nv, 'After', 3, 'NewVariableNames', 'X'); %! assert_equal (R.Properties.VariableNames, {'A', 'B', 'C', 'X'}); %! R = addvars (T, nv, 'After', string ('B'), 'NewVariableNames', 'X'); %! assert_equal (R.Properties.VariableNames, {'A', 'B', 'X', 'C'}); %! R = addvars (T, nv, 'Before', string ('B'), 'NewVariableNames', 'X'); %! assert_equal (R.Properties.VariableNames, {'A', 'X', 'B', 'C'}); %!test # nested-table variable and string-array NewVariableNames %! T = table ([1;2], [3;4], 'VariableNames', {'A', 'B'}); %! inner = table ([7;8], [9;10], 'VariableNames', {'P', 'Q'}); %! R = addvars (T, inner, 'NewVariableNames', 'N'); %! assert_equal (R.Properties.VariableNames, {'A', 'B', 'N'}); %! assert_equal (istable (R.N), true); %! assert_equal (R.N.P, [7; 8]); %! R = addvars (T, [11;12], [13;14], 'NewVariableNames', string ({'X', 'Y'})); %! assert_equal (R.Properties.VariableNames, {'A', 'B', 'X', 'Y'}); ## Test input validation for 'addvars' method %!error ... %! addvars (tblA, Smoker, 'After', 1, 'Before', 2); %!error ... %! addvars (tblA, Smoker, 'After', [true, false, false, true]); %!error ... %! addvars (tblA, Smoker, 'Before', [true, false, false, true]); %!error ... %! addvars (tblA, Smoker, 'After', [1, 2]); %!error ... %! addvars (tblA, Smoker, 'Before', [1, 2]); %!error ... %! addvars (tblA, Smoker, 'NewVariableNames', {'A', 'B'}); %!error ... %! addvars (tblA, Smoker, Smoker, 'NewVariableNames', {'A', 'A'}); %!error ... %! addvars (tblA, Smoker, 'NewVariableNames', {'Height'}); %!error ... %! addvars (tblA, Smoker, Smoker, 'NewVariableNames', {'Height', 'Weight'}); ## Test 'rename' method %!test %! load patients %! T1 = table (LastName, Age, Gender, Smoker); %! assert_equal (T1.Properties.VariableNames, {'LastName', 'Age', 'Gender', 'Smoker'}); %! T2 = renamevars (T1, {'LastName', 'Gender'}, {'Name', 'Sex'}); %! assert_equal (T2.Properties.VariableNames, {'Name', 'Age', 'Sex', 'Smoker'}); %!test %! T1 = array2table (rand (5, 3)); %! assert_equal (T1.Properties.VariableNames, {'Var1', 'Var2', 'Var3'}); %! allVars = 1:width (T1); %! newNames = cellfun (@(x) sprintf ("Rand%i",x), num2cell (allVars), ... %! "UniformOutput", false); %! T2 = renamevars (T1, allVars, newNames); %! assert_equal (T2.Properties.VariableNames, {'Rand1', 'Rand2', 'Rand3'}); %!test # VARS forms: char, string array, logical vector, vartype %! T = table ([1;2], [3;4], [5;6], 'VariableNames', {'A', 'B', 'C'}); %! R = renamevars (T, 'B', 'X'); %! assert_equal (R.Properties.VariableNames, {'A', 'X', 'C'}); %! R = renamevars (T, string ({'A', 'C'}), string ({'X', 'Z'})); %! assert_equal (R.Properties.VariableNames, {'X', 'B', 'Z'}); %! R = renamevars (T, [true, false, true], {'X', 'Z'}); %! assert_equal (R.Properties.VariableNames, {'X', 'B', 'Z'}); %! R = renamevars (T, vartype ('numeric'), {'X', 'Y', 'Z'}); %! assert_equal (R.Properties.VariableNames, {'X', 'Y', 'Z'}); %!test # renaming preserves data and per-variable metadata %! T = table ([1;2], [3;4], 'VariableNames', {'A', 'B'}); %! T.Properties.VariableDescriptions = {'da', 'db'}; %! T.Properties.VariableUnits = {'ua', 'ub'}; %! R = renamevars (T, 'A', 'X'); %! assert_equal (R.X, [1; 2]); %! assert_equal (R.Properties.VariableDescriptions, {'da', 'db'}); %! assert_equal (R.Properties.VariableUnits, {'ua', 'ub'}); ## Test input validation for 'renamevars' method %!error ... %! renamevars (tblA, {"Age", "Smoker"}); %!error ... %! renamevars (tblA, {"Age"}, {}); %!error ... %! renamevars (tblA, {}, {"NewName"}); %!error ... %! renamevars (tblA, {"Age", "Smoker"}, {"Age", 4}); %!error ... %! renamevars (tblA, {"Age", "Smoker"}, {"Age", "Age"}); %!error ... %! renamevars (tblA, {"Age", "Smoker"}, {"Age", "User"}); %!error ... %! renamevars (tblA, {"Age", "Height"}, {"Age", "Smoker", "Height"}); %!error ... %! renamevars (tblA, {"Age", "Height"}, {"Age", "Weight"}); ## Test 'movevars' method %!test %! load patients %! T1 = table (LastName, Age, Gender, Smoker); %! assert_equal (T1.Properties.VariableNames, {'LastName', 'Age', 'Gender', 'Smoker'}); %! T2 = movevars (T1, {'LastName', 'Gender'}); %! assert_equal (T2.Properties.VariableNames, {'Age', 'Smoker', 'LastName', 'Gender'}); %!test %! load patients %! T1 = table (LastName, Age, Gender, Smoker); %! T2 = movevars (T1, vartype ("cellstr")); %! assert_equal (T2.Properties.VariableNames, {'Age', 'Smoker', 'LastName', 'Gender'}); %!test %! load patients %! T1 = table (LastName, Age, Gender, Smoker); %! T2 = movevars (T1, vartype ("numeric")); %! assert_equal (T2.Properties.VariableNames, {'LastName', 'Gender', 'Smoker', 'Age'}); %!test %! load patients %! T1 = table (LastName, Age, Gender, Smoker); %! T2 = movevars (T1, {'LastName', 'Gender'}, 'After', 'Age'); %! assert_equal (T2.Properties.VariableNames, {'Age', 'LastName', 'Gender', 'Smoker'}); %!test %! load patients %! T1 = table (LastName, Age, Gender, Smoker); %! T2 = movevars (T1, [true, false, true, false], 'After', 'Age'); %! assert_equal (T2.Properties.VariableNames, {'Age', 'LastName', 'Gender', 'Smoker'}); %!test %! load patients %! T1 = table (LastName, Age, Gender, Smoker); %! T2 = movevars (T1, [3, 4], 'Before', 'Age'); %! assert_equal (T2.Properties.VariableNames, {'LastName', 'Gender', 'Smoker', 'Age'}); %!test %! load patients %! T1 = table (LastName, Age, Gender, Smoker); %! T2 = movevars (T1, [3, 4], 'Before', 2); %! assert_equal (T2.Properties.VariableNames, {'LastName', 'Gender', 'Smoker', 'Age'}); %!test %! load patients %! T1 = table (LastName, Age, Gender, Smoker); %! T2 = movevars (T1, [3, 4], 'Before', [false, true, false, false]); %! assert_equal (T2.Properties.VariableNames, {'LastName', 'Gender', 'Smoker', 'Age'}); %!test # VARS forms (string array, char single) and LOCATION edges %! T = table ([1;2], [3;4], [5;6], [7;8], 'VariableNames', {'A', 'B', 'C', 'D'}); %! R = movevars (T, string ({'A', 'C'})); %! assert_equal (R.Properties.VariableNames, {'B', 'D', 'A', 'C'}); %! R = movevars (T, 'A'); %! assert_equal (R.Properties.VariableNames, {'B', 'C', 'D', 'A'}); %! R = movevars (T, {'C', 'D'}, 'Before', 1); %! assert_equal (R.Properties.VariableNames, {'C', 'D', 'A', 'B'}); %! R = movevars (T, 'C', 'Before', 'A'); %! assert_equal (R.Properties.VariableNames, {'C', 'A', 'B', 'D'}); %! R = movevars (T, 'A', 'After', 4); %! assert_equal (R.Properties.VariableNames, {'B', 'C', 'D', 'A'}); %! R = movevars (T, 'A', 'After', string ('C')); %! assert_equal (R.Properties.VariableNames, {'B', 'C', 'A', 'D'}); %! R = movevars (T, 'D', 'Before', string ('B')); %! assert_equal (R.Properties.VariableNames, {'A', 'D', 'B', 'C'}); ## Test input validation for 'movevars' method %!error movevars (tblA); %!error movevars (tblA, {}); %!error ... %! movevars (tblA, 1, "After", "Height", "Before", "Weight"); %!error ... %! movevars (tblA, 1, "After", [true, true, false, false]); %!error ... %! movevars (tblA, 1, "After", [1, 2]); %!error ... %! movevars (tblA, 1, "After", {"Height"}); %!error ... %! movevars (tblA, 1, "After", "Smoker"); %!error ... %! movevars (tblA, "Smoker", "After", 2); %!error ... %! movevars (tblA, {"Age", "Height"}, "After", 2); %!error ... %! movevars (tblA, {"Height", "Weight"}, "Before", "Height"); ## Test 'removevars' method %!test %! load patients %! T1 = table (LastName, Age, Gender, Smoker); %! T2 = removevars (T1, {'LastName', 'Gender'}); %! assert_equal (T2.Properties.VariableNames, {'Age', 'Smoker'}); %!test %! load patients %! T1 = table (LastName, Age, Gender, Smoker); %! T2 = removevars (T1, [true, false, true, false]); %! assert_equal (T2.Properties.VariableNames, {'Age', 'Smoker'}); %!test %! load patients %! T1 = table (LastName, Age, Gender, Smoker); %! T2 = removevars (T1, [1, 3]); %! assert_equal (T2.Properties.VariableNames, {'Age', 'Smoker'}); %!test %! load patients %! T1 = table (LastName, Age, Gender, Smoker); %! T2 = removevars (T1, vartype ("numeric")); %! assert_equal (T2.Properties.VariableNames, {'LastName', 'Gender', 'Smoker'}); %!test # VARS forms (string array, single char) and removing all variables %! T = table ([1;2], [3;4], [5;6], 'VariableNames', {'A', 'B', 'C'}); %! R = removevars (T, string ({'A', 'C'})); %! assert_equal (R.Properties.VariableNames, {'B'}); %! R = removevars (T, 'B'); %! assert_equal (R.Properties.VariableNames, {'A', 'C'}); %! R = removevars (T, {'A', 'B', 'C'}); %! assert_equal (width (R), 0); %!test # custom variable properties shrink with the removed variables %! T = table ([1;2], [3;4], [5;6], 'VariableNames', {'A', 'B', 'C'}); %! T = addprop (T, 'Note', 'variable'); %! T.Properties.CustomProperties.Note = {'na', 'nb', 'nc'}; %! R = removevars (T, 'B'); %! assert_equal (R.Properties.VariableNames, {'A', 'C'}); %! assert_equal (R.Properties.CustomProperties.Note, {'na', 'nc'}); %! R = removevars (T, [1, 3]); %! assert_equal (R.Properties.CustomProperties.Note, {'nb'}); ## Test input validation for 'removevars' method %!error removevars (tblA); %!error removevars (tblA, ones (2)); %!error ... %! removevars (tblA, [true, true, false]); %!error ... %! removevars (tblA, [1, 5]); %!error ... %! removevars (tblA, [1, 2, 7]); %!error ... %! removevars (tblA, "Smoker"); %!error ... %! removevars (tblA, {"Smoker", "Health"}); %!error ... %! removevars (tblA, {1}); %!error ... %! removevars (tblA, struct ("A", 1)); ## Test 'splitvars' method %!test %! A = [1:3]'; %! B = rand (3); %! C = {'a', 'XX'; 'b', 'YY'; 'c', 'ZZ'}; %! D = {"A"; "B"; "C"}; %! T1 = table (A, B, C, D); %! T2 = splitvars (T1); %! assert_equal (T2.Properties.VariableNames, ... %! {'A', 'B_1', 'B_2', 'B_3', 'C_1', 'C_2', 'D'}); %!test %! A = [1:3]'; %! B = rand (3); %! C = {'a', 'XX'; 'b', 'YY'; 'c', 'ZZ'}; %! D = {"A"; "B"; "C"}; %! T1 = table (A, B, C, D); %! T2 = addvars (T1, table ([5; 5; 5])); %! assert_equal (size (T2.Var5), [3, 1]); %! T3 = splitvars (T2); %! assert_equal (T3.Properties.VariableNames, ... %! {'A', 'B_1', 'B_2', 'B_3', 'C_1', 'C_2', 'D', 'Var1'}); %! assert_equal (size (T3.Var1), [3, 1]); %!test %! A = [1:3]'; %! B = rand (3); %! C = {'a', 'XX'; 'b', 'YY'; 'c', 'ZZ'}; %! D = {"A"; "B"; "C"}; %! T1 = table (A, B, C, D); %! T2 = addvars (T1, table ([5, 6; 5, 6; 5, 6])); %! assert_equal (size (T2.Var5), [3, 1]); %! T3 = splitvars (T2); %! assert_equal (T3.Properties.VariableNames, ... %! {'A', 'B_1', 'B_2', 'B_3', 'C_1', 'C_2', 'D', 'Var1'}); %! assert_equal (size (T3.Var1), [3, 2]); %!test %! A = [1:3]'; %! B = rand (3); %! C = {'a', 'XX'; 'b', 'YY'; 'c', 'ZZ'}; %! D = {"A"; "B"; "C"}; %! T1 = table (A, B, C, D); %! A = [5; 5; 5]; %! T2 = addvars (T1, table (A)); %! T3 = splitvars (T2); %! assert_equal (T3.Properties.VariableNames, ... %! {'A', 'B_1', 'B_2', 'B_3', 'C_1', 'C_2', 'D', 'Var5_A'}); %!test %! A = [1:3]'; %! B = rand (3); %! C = {'a', 'XX'; 'b', 'YY'; 'c', 'ZZ'}; %! D = {"A"; "B"; "C"}; %! T1 = table (A, B, C, D); %! A = [5; 5; 5]; %! T2 = addvars (T1, table ([5, 6; 5, 6; 5, 6]), table (A)); %! T3 = splitvars (T2); %! assert_equal (T3.Properties.VariableNames, ... %! {'A', 'B_1', 'B_2', 'B_3', 'C_1', 'C_2', 'D', 'Var1', 'Var6_A'}); %!test %! load patients %! Personal_Data = table (Gender, Age); %! BMI_Data = table (Height, Weight); %! BloodPressure = table (Systolic, Diastolic); %! T1 = table (LastName, Personal_Data, BMI_Data, BloodPressure); %! T2 = splitvars (T1, 'BloodPressure'); %! assert_equal (T2.Properties.VariableNames, ... %! {'LastName', 'Personal_Data', 'BMI_Data', 'Systolic', 'Diastolic'}); %!test %! load patients %! Personal_Data = table (Gender, Age); %! BMI_Data = table (Height, Weight); %! BloodPressure = table (Systolic, Diastolic); %! T1 = table (LastName, Personal_Data, BMI_Data, BloodPressure); %! T2 = splitvars (T1, {'BMI_Data', 'BloodPressure'}); %! assert_equal (T2.Properties.VariableNames, {'LastName', 'Personal_Data', ... %! 'Height', 'Weight', 'Systolic', 'Diastolic'}); %!test %! load patients %! Personal_Data = table (Gender, Age); %! BMI_Data = table (Height, Weight); %! BloodPressure = table (Systolic, Diastolic); %! T1 = table (LastName, Personal_Data, BMI_Data, BloodPressure); %! T2 = splitvars (T1, [2, 4]); %! assert_equal (T2.Properties.VariableNames, {'LastName', 'Gender', 'Age', ... %! 'BMI_Data', 'Systolic', 'Diastolic'}); %!test %! load patients %! Personal_Data = table (Gender, Age); %! BMI_Data = table (Height, Weight); %! BloodPressure = table (Systolic, Diastolic); %! T1 = table (LastName, Personal_Data, BMI_Data, BloodPressure); %! T2 = splitvars (T1, [false, true, false, true]); %! assert_equal (T2.Properties.VariableNames, {'LastName', 'Gender', 'Age', ... %! 'BMI_Data', 'Systolic', 'Diastolic'}); %!test %! load patients %! Personal_Data = [Age, Height, Weight]; %! BloodPressure = [Systolic, Diastolic]; %! T1 = table (LastName, Gender, Personal_Data, BloodPressure); %! T2 = splitvars (T1, 'BloodPressure', ... %! 'NewVariableNames', {'Systolic','Diastolic'}); %! assert_equal (T2.Properties.VariableNames, ... %! {'LastName', 'Gender', 'Personal_Data', 'Systolic', 'Diastolic'}); %!test %! load patients %! Personal_Data = [Age, Height, Weight]; %! BloodPressure = [Systolic, Diastolic]; %! T1 = table (LastName, Gender, Personal_Data, BloodPressure); %! T2 = splitvars (T1, {'Personal_Data', 'BloodPressure'}, ... %! 'NewVariableNames', {{'Age', 'Height', 'Weight'}, ... %! {'Systolic', 'Diastolic'}}); %! assert_equal (T2.Properties.VariableNames, {'LastName', 'Gender', ... %! 'Age', 'Height', 'Weight', 'Systolic', 'Diastolic'}); %!test %! load patients %! Personal_Data = [Age, Height, Weight]; %! BloodPressure = [Systolic, Diastolic]; %! T1 = table (LastName, Gender, Personal_Data, BloodPressure); %! new_PD = string ({'Age', 'Height', 'Weight'}); %! T2 = splitvars (T1, {'Personal_Data', 'BloodPressure'}, ... %! 'NewVariableNames', {new_PD, {'Systolic', 'Diastolic'}}); %! assert_equal (T2.Properties.VariableNames, {'LastName', 'Gender', ... %! 'Age', 'Height', 'Weight', 'Systolic', 'Diastolic'}); %!test # duplicate inner names across nested tables get disambiguated %! n1 = table ([1;2], [3;4], 'VariableNames', {'X', 'Y'}); %! n2 = table ([5;6], [7;8], 'VariableNames', {'X', 'Y'}); %! T = table (n1, n2, 'VariableNames', {'P', 'Q'}); %! R = splitvars (T); %! assert_equal (R.Properties.VariableNames, {'P_X', 'P_Y', 'Q_X', 'Q_Y'}); %! assert_equal (R.P_X, [1; 2]); %! assert_equal (R.Q_Y, [7; 8]); %!test # splitting a multicolumn special-type variable preserves its class %! d = hours ([1, 2; 3, 4; 5, 6]); %! R = splitvars (table (d, 'VariableNames', {'D'})); %! assert_equal (R.Properties.VariableNames, {'D_1', 'D_2'}); %! assert_equal (class (R.D_1), 'duration'); %! assert_equal (hours (R.D_1), [1; 3; 5]); %! assert_equal (hours (R.D_2), [2; 4; 6]); %!test # string-array VARS, single-column var ignored, data preserved %! n = table ([1;2], [3;4], 'VariableNames', {'X', 'Y'}); %! T = table ([9;9], n, 'VariableNames', {'A', 'N'}); %! R = splitvars (T, string ('N')); %! assert_equal (R.Properties.VariableNames, {'A', 'X', 'Y'}); %! T = table ([1;2], [3, 4; 5, 6], 'VariableNames', {'S', 'M'}); %! R = splitvars (T, 'S'); %! assert_equal (R.Properties.VariableNames, {'S', 'M'}); %! R = splitvars (table ([10, 20; 30, 40], 'VariableNames', {'M'})); %! assert_equal (R.M_1, [10; 30]); %! assert_equal (R.M_2, [20; 40]); ## Test input validation for 'splitvars' method %!error splitvars (tblA, 1, 2, 3, 4); %!error ... %! splitvars (table ([5, 6; 5, 6; 5, 6]), 1, 'NewVariableNames', {1}) %!error ... %! splitvars (table ([5, 6; 5, 6; 5, 6]), 1, 'NewVariableNames', 1) %!error ... %! splitvars (table ([5, 6; 5, 6; 5, 6]), 1, 'NewVariableNames', "new_name") %!error ... %! splitvars (table ([5, 6; 5, 6; 5, 6], ones (3, 2)), ":", ... %! 'NewVariableNames', {{"A", "B"}, {"new_name", 1}}) %!error ... %! splitvars (table ([5, 6; 5, 6; 5, 6], ones (3, 2)), ":", ... %! 'NewVariableNames', {{"A", "B"}, {"C", "D", "E"}}) ## Test 'mergevars' method %!test %! A = [1:3]'; %! B = [5; 11; 12]; %! C = [3.14; 2.72; 1.37]; %! D = string ({"a"; "b"; "c"}); %! T1 = table (A, B, C, D); %! T2 = mergevars (T1, [2, 3]); %! assert_equal (T2.Properties.VariableNames, {'A', 'Var2', 'D'}); %! assert_equal (isequal (T2.Var2, cat (2, B, C)), true); %!test %! A = [1:3]'; %! B = [5; 11; 12]; %! C = [3.14; 2.72; 1.37]; %! D = string ({"a"; "b"; "c"}); %! T1 = table (A, B, C, D); %! T2 = mergevars (T1, [2, 3], 'NewVariableName', 'Merged'); %! assert_equal (T2.Properties.VariableNames, {'A', 'Merged', 'D'}); %! assert_equal (isequal (T2.Merged, cat (2, B, C)), true); %!test %! A = [1:3]'; %! B = [5; 11; 12]; %! C = [3.14; 2.72; 1.37]; %! D = string ({"a"; "b"; "c"}); %! T1 = table (A, B, C, D); %! T2 = mergevars (T1, [2, 3], 'MergeAsTable', true); %! assert_equal (T2.Properties.VariableNames, {'A', 'Var2', 'D'}); %! assert_equal (isequal (T2.Var2, table (B, C)), true); %!test %! A = [1:3]'; %! B = [5; 11; 12]; %! C = [3.14; 2.72; 1.37]; %! D = string ({"a"; "b"; "c"}); %! T1 = table (A, B, C, D); %! T2 = mergevars (T1, [2, 3], 'NewVariableName', 'Merged', 'MergeAsTable', 1); %! assert_equal (T2.Properties.VariableNames, {'A', 'Merged', 'D'}); %! assert_equal (isequal (T2.Merged, table (B, C)), true); %!test %! A = [1:3]'; %! B = [5; 11; 12]; %! C = [3.14; 2.72; 1.37]; %! D = string ({"a"; "b"; "c"}); %! T1 = table (A, B, C, D); %! T2 = mergevars (T1, {"A", "B"}); %! assert_equal (T2.Properties.VariableNames, {'Var1', 'C', 'D'}); %! assert_equal (isequal (T2.Var1, cat (2, A, B)), true); %!test %! A = [1:3]'; %! B = [5; 11; 12]; %! C = [3.14; 2.72; 1.37]; %! D = string ({"a"; "b"; "c"}); %! T1 = table (A, B, C, D); %! T2 = mergevars (T1, {"A", "B"}, 'NewVariableName', 'Merged'); %! assert_equal (T2.Properties.VariableNames, {'Merged', 'C', 'D'}); %! assert_equal (isequal (T2.Merged, cat (2, A, B)), true); %!test %! A = [1:3]'; %! B = [5; 11; 12]; %! C = [3.14; 2.72; 1.37]; %! D = string ({"a"; "b"; "c"}); %! T1 = table (A, B, C, D); %! T2 = mergevars (T1, {"A", "B"}, 'MergeAsTable', true); %! assert_equal (T2.Properties.VariableNames, {'Var1', 'C', 'D'}); %! assert_equal (isequal (T2.Var1, table (A, B)), true); %!test %! A = [1:3]'; %! B = [5; 11; 12]; %! C = [3.14; 2.72; 1.37]; %! D = string ({"a"; "b"; "c"}); %! T1 = table (A, B, C, D); %! T2 = mergevars (T1, {"A", "B"}, 'NewVariableName', 'Merged', ... %! 'MergeAsTable', true); %! assert_equal (T2.Properties.VariableNames, {'Merged', 'C', 'D'}); %! assert_equal (isequal (T2.Merged, table (A, B)), true); %!test # string-array VARS and string-scalar NewVariableName %! T = table ([1;2], [3;4], [5;6], [7;8], 'VariableNames', {'A', 'B', 'C', 'D'}); %! R = mergevars (T, string ({'B', 'C'}), 'NewVariableName', string ('M')); %! assert_equal (R.Properties.VariableNames, {'A', 'M', 'D'}); %! assert_equal (R.M, [3, 5; 4, 6]); %!test # logical and vartype VARS, and a single-variable merge is a no-op %! T = table ([1;2], [3;4], [5;6], [7;8], 'VariableNames', {'A', 'B', 'C', 'D'}); %! R = mergevars (T, [false, true, true, false]); %! assert_equal (R.Properties.VariableNames, {'A', 'Var2', 'D'}); %! R = mergevars (T, vartype ('numeric')); %! assert_equal (R.Properties.VariableNames, {'Var1'}); %! R = mergevars (T, 2); %! assert_equal (R.Properties.VariableNames, {'A', 'B', 'C', 'D'}); %!test # non-contiguous VARS merge keeps the first-merged position %! T = table ([1;2], [3;4], [5;6], [7;8], 'VariableNames', {'A', 'B', 'C', 'D'}); %! R = mergevars (T, [2, 4]); %! assert_equal (R.Properties.VariableNames, {'A', 'Var2', 'C'}); %! assert_equal (R.Var2, [3, 7; 4, 8]); %!test # merging special-type variables preserves their class %! T = table (hours ([1;2]), hours ([3;4]), 'VariableNames', {'P', 'Q'}); %! R = mergevars (T, [1, 2]); %! assert_equal (class (R.Var1), 'duration'); %! assert_equal (hours (R.Var1), [1, 3; 2, 4]); %!test # custom properties of the first merged variable are preserved %! T = table ([1;2], [3;4], [5;6], [7;8], 'VariableNames', {'A', 'B', 'C', 'D'}); %! T = addprop (T, 'Note', 'variable'); %! T.Properties.CustomProperties.Note = {'nA', 'nB', 'nC', 'nD'}; %! R = mergevars (T, [2, 3]); %! assert_equal (R.Properties.VariableNames, {'A', 'Var2', 'D'}); %! assert_equal (R.Properties.CustomProperties.Note, {'nA', 'nB', 'nD'}); ## Test input validation for 'mergevars' method %!error mergevars (tblA); %!error ... %! mergevars (tblA, {'Height', 'Weight'}, 'MergeAsTable', 'on'); %!error ... %! mergevars (tblA, {'Height', 'Weight'}, 'MergeAsTable', 2); %!error ... %! mergevars (tblA, {'Height', 'Weight'}, 'MergeAsTable', [true, true]); %!error ... %! mergevars (tblA, {'Height', 'Weight'}, 'NewVariableName', 2) %!error ... %! mergevars (tblA, {'Height', 'Weight'}, 'NewVariableName', 'Age'); %!error ... %! LastName = {"Sanchez"; "Johnson"; "Li"; "Diaz"; "Brown"}; %! mergevars (addvars (tblA, LastName), {'Height', 'LastName'}); ## Test 'convertvars' method %!test %! A = [1:3]'; %! B = [5; 11; 12]; %! C = [3.14; 2.72; 1.37]; %! D = string ({"a"; "b"; "c"}); %! T1 = table (A, B, C, D); %! assert_equal (isa (T1.A, "numeric"), true); %! assert_equal (isa (T1.B, "numeric"), true); %! T2 = convertvars (T1, {"A", "B"}, "string"); %! assert_equal (isa (T2.A, "string"), true); %! assert_equal (isa (T2.B, "string"), true); %!test %! A = [1:3]'; %! B = [5; 11; 12]; %! C = [3.14; 2.72; 1.37]; %! D = string ({"a"; "b"; "c"}); %! T1 = table (A, B, C, D); %! assert_equal (isa (T1.D, "string"), true); %! T2 = convertvars (T1, 4, "cellstr"); %! assert_equal (iscellstr (T2.D), true); %!test %! A = [1:3]'; %! B = [5; 11; 12]; %! C = [3.14; 2.72; 1.37]; %! D = string ({"a"; "b"; "c"}); %! T1 = table (A, B, C, D); %! assert_equal (isa (T1.D, "string"), true); %! T2 = convertvars (T1, [1:3], @(x) sqrt (x)); %! assert_equal (isequal (T2.A, sqrt (A)), true); %! assert_equal (isequal (T2.B, sqrt (B)), true); %! assert_equal (isequal (T2.C, sqrt (C)), true); %!test %! A = [1:3]'; %! B = [5; 11; 12]; %! C = [3.14; 2.72; 1.37]; %! D = string ({"a"; "b"; "c"}); %! T1 = table (A, B, C, D); %! assert_equal (isa (T1.D, "string"), true); %! T2 = convertvars (T1, [1:3], @(x) [x, x, x]); %! assert_equal (size (T2.A, 2), 3); %! assert_equal (size (T2.B, 2), 3); %! assert_equal (size (T2.C, 2), 3); %!test %! A = [1:3]'; %! B = [5; 11; 12]; %! C = [3.14; 2.72; 1.37]; %! D = {"a"; "b"; "c"}; %! T1 = table (A, B, C, D); %! T2 = convertvars (T1, "D", @(x) cellstr (upper (char (x)))); %! assert_equal (T2.D, {"A"; "B"; "C"}); %!test # VARS forms: string array, logical vector, vartype %! T = table ([1;2;3], [4;5;6], {'x'; 'y'; 'z'}, 'VariableNames', {'A', 'B', 'C'}); %! R = convertvars (T, string ({'A', 'B'}), 'string'); %! assert_equal (isa (R.A, 'string'), true); %! assert_equal (isa (R.B, 'string'), true); %! R = convertvars (T, [true, false, false], 'single'); %! assert_equal (class (R.A), 'single'); %! R = convertvars (T, vartype ('numeric'), 'int32'); %! assert_equal (class (R.A), 'int32'); %! assert_equal (class (R.B), 'int32'); %!test # conversion to special data types (categorical, duration) %! T = table ([1;2;3], {'x'; 'y'; 'z'}, 'VariableNames', {'A', 'C'}); %! R = convertvars (T, 'C', 'categorical'); %! assert_equal (class (R.C), 'categorical'); %! R = convertvars (T, 'A', @hours); %! assert_equal (class (R.A), 'duration'); %! assert_equal (hours (R.A), [1; 2; 3]); ## Test input validation for 'convertvars' method %!error convertvars (tblA); %!error ... %! convertvars (tblA, "Height"); %!error ... %! convertvars (tblA, "Height", ["c","h";"a","r"]); %!error ... %! convertvars (tblA, "Height", {"char"}); %!error ... %! convertvars (tblA, "Height", 25); %!error ... %! convertvars (tblA, "Height", "struct"); %!error ... %! convertvars (tblA, "Height", @(x) sum (x)); ## Test 'rows2vars' method %!test %! load patients %! T1 = table (LastName, Gender, Age, Height, Weight); %! T2 = rows2vars (T1); %! assert_equal (size (T1, 2), size (T2, 1)); %! assert_equal (size (T1, 1) + 1, size (T2, 2)); %! assert_equal (T2.OriginalVariableNames, ... %! {"LastName"; "Gender"; "Age"; "Height"; "Weight"}); %! assert_equal (T2.Var1, {"Smith"; "Male"; 38; 71; 176}); %!test %! load patients %! T1 = table (Gender, Age, Height, Weight, "RowNames", LastName); %! T2 = rows2vars (T1); %! assert_equal (T2.OriginalVariableNames, {"Gender"; "Age"; "Height"; "Weight"}); %! assert_equal (T2.Smith, {"Male"; 38; 71; 176}); %! assert_equal (T2.Johnson, {"Male"; 43; 69; 163}); %! assert_equal (T2.Williams, {"Female"; 38; 64; 131}); %!test %! load patients %! T1 = table (LastName, Gender, Age, Height, Weight); %! T2 = rows2vars (T1, "VariableNamesSource", "LastName"); %! assert_equal (T2.OriginalVariableNames, {"Gender"; "Age"; "Height"; "Weight"}); %! assert_equal (T2.Smith, {"Male"; 38; 71; 176}); %! assert_equal (T2.Johnson, {"Male"; 43; 69; 163}); %! assert_equal (T2.Williams, {"Female"; 38; 64; 131}) %!test %! load patients %! T1 = table (LastName, Gender, Age, Height, Weight); %! T2 = rows2vars (T1, "DataVariables", {"LastName", "Gender", "Age"}); %! assert_equal (size (T2, 1), 3); %! assert_equal (size (T1, 1) + 1, size (T2, 2)); %! assert_equal (T2.OriginalVariableNames, {"LastName"; "Gender"; "Age"}); %! assert_equal (T2.Var1, {"Smith"; "Male"; 38}); %!test %! A = [1:3]'; %! B = [5; 11; 12]; %! C = [3.14; 2.72; 1.37]; %! T1 = table (A, B, C); %! T2 = rows2vars (T1); %! assert_equal (class (T2.Var1), "double"); %!test %! A = [1:3]'; %! B = [5; 11; 12]; %! C = [3.14; 2.72; 1.37]; %! D = {"a"; "b"; "c"}; %! T1 = table (A, B, C, D); %! T2 = rows2vars (T1); %! assert_equal (class (T2.Var1), "cell"); %!test %! A = [1:3]'; %! B = [5; 11; 12]; %! C = [3.14; 2.72; 1.37]; %! T1 = table (A, B, C); %! T2 = rows2vars (T1, "VariableNamesSource", "A"); %! assert_equal (size (T2), [2, 4]); %! assert_equal (T2.Properties.VariableNames(1), {"OriginalVariableNames"}); %! assert_equal (T2.Properties.VariableNames(2:4), {"x1", "x2", "x3"}); %! assert_equal (T2.x1, [5; 3.14]); %!test %! A = [1:3]'; %! B = [5; 11; 12]; %! C = [3.14; 2.72; 1.37]; %! T1 = table (A, B, C); %! T2 = rows2vars (T1, "VariableNamesSource", "A", ... %! "VariableNamingRule", "preserve"); %! assert_equal (size (T2), [2, 4]); %! assert_equal (T2.Properties.VariableNames(1), {"OriginalVariableNames"}); %! assert_equal (T2.Properties.VariableNames(2:4), {"1", "2", "3"}); %! assert_equal (T2.('1'), [5; 3.14]); %!test # DataVariables forms: numeric, logical, string array, vartype %! T = table ([1;2;3], [4;5;6], [7;8;9], 'VariableNames', {'A', 'B', 'C'}); %! R = rows2vars (T, 'DataVariables', [1, 2]); %! assert_equal (R.OriginalVariableNames, {'A'; 'B'}); %! R = rows2vars (T, 'DataVariables', [true, false, true]); %! assert_equal (R.OriginalVariableNames, {'A'; 'C'}); %! R = rows2vars (T, 'DataVariables', string ({'A', 'C'})); %! assert_equal (R.OriginalVariableNames, {'A'; 'C'}); %! R = rows2vars (T, 'DataVariables', vartype ('numeric')); %! assert_equal (R.OriginalVariableNames, {'A'; 'B'; 'C'}); %!test # VariableNamesSource by numeric index and from a string variable %! T = table ([1;2;3], [4;5;6], 'VariableNames', {'A', 'B'}); %! R = rows2vars (T, 'VariableNamesSource', 1); %! assert_equal (R.Properties.VariableNames, ... %! {'OriginalVariableNames', 'x1', 'x2', 'x3'}); %! assert_equal (R.OriginalVariableNames, {'B'}); %! T2 = table (string ({'p'; 'q'; 'r'}), [4;5;6], 'VariableNames', {'S', 'B'}); %! R = rows2vars (T2, 'VariableNamesSource', 'S'); %! assert_equal (R.Properties.VariableNames, ... %! {'OriginalVariableNames', 'p', 'q', 'r'}); %! assert_equal (R.OriginalVariableNames, {'B'}); ## Test input validation for 'rows2vars' method %!error ... %! rows2vars (tblA, "DataVariables", "Some"); %!error ... %! rows2vars (tblA, "VariableNamesSource", {"LastName", "Age"}); %!error ... %! rows2vars (tblA, "VariableNamesSource", {"Some"}); %!error ... %! rows2vars (tblA, "VariableNamesSource", {"Age"}); %!error ... %! LastName = {"Sanchez"; "Johnson"; "Li"; "Diaz"; "Brown"}; %! Age = [38; 43; 38; 40; 49]; %! rows2vars (table (LastName, Age), "DataVariables", "LastName", "VariableNamesSource", "LastName"); %!error ... %! rows2vars (tblA, "VariableNamingRule", "somerule"); %!error ... %! LastName = {"Sanchez"; "Johnson"; "Li"; "Diaz"; "Brown"}; %! rows2vars (table (tblA, LastName)); %!error ... %! rows2vars (tblA); ## Test 'stack' method %!test %! Test1 = [93; 57; 87; 89]; %! Test2 = [89; 77; 92; 86]; %! Test3 = [95; 62; 89; 91]; %! U = table (Test1, Test2, Test3); %! S = stack (U, 1:3); %! assert_equal (size (S), [12, 2]); %! assert_equal (class (S.Test1_Test2_Test3_Indicator), "categorical"); %! assert_equal (cellstr (S.Test1_Test2_Test3_Indicator), ... %! repmat ({"Test1"; "Test2"; "Test3"}, 4, 1)); %! assert_equal (S.Test1_Test2_Test3, [Test1';Test2';Test3'](:)); %!test %! Test1 = [93; 57; 87; 89]; %! Test2 = [89; 77; 92; 86]; %! Test3 = [95; 62; 89; 91]; %! U = table (Test1, Test2, Test3); %! [S, idx] = stack (U, 1:2); %! assert_equal (idx, [1; 1; 2; 2; 3; 3; 4; 4]); %! assert_equal (cellstr (S.Test1_Test2_Indicator), ... %! repmat ({"Test1"; "Test2"}, 4, 1)); %! assert_equal (S.Test3, repelem (Test3, 2, 1)); %!test %! Test1 = [93; 57; 87; 89]; %! Test2 = [89; 77; 92; 86]; %! Test3 = [95; 62; 89; 91]; %! U = table (Test1, Test2, Test3); %! S = stack (U, 1:3, 'NewDataVariableName', 'NewData', ... %! 'IndexVariableName', 'Index'); %! assert_equal (size (S), [12, 2]); %! assert_equal (class (S.Index), "categorical"); %! assert_equal (cellstr (S.Index), repmat ({"Test1"; "Test2"; "Test3"}, 4, 1)); %! assert_equal (S.NewData, [Test1';Test2';Test3'](:)); %!test %! Test1 = [93; 57; 87; 89]; %! Test2 = [89; 77; 92; 86]; %! Test3 = [95; 62; 89; 91]; %! Test4 = [95; 62; 89; 91]; %! U = table (Test1, Test2, Test3, Test4); %! S = stack (U, 1:2, 'ConstantVariables', "Test4"); %! assert_equal (S.Properties.VariableNames, ... %! {"Test4", "Test1_Test2_Indicator", "Test1_Test2"}); %!test %! Test1 = [93; 57; 87; 89]; %! Test2 = [89; 77; 92; 86]; %! Test3 = [95; 62; 89; 91]; %! Test4 = [95; 62; 89; 91]; %! U = table (Test1, Test2, Test3, Test4); %! S = stack (U, {"Test1", "Test4"}, 'ConstantVariables', "Test2"); %! assert_equal (S.Properties.VariableNames, ... %! {"Test2", "Test1_Test4_Indicator", "Test1_Test4"}); %!test %! Test1 = [93; 57; 87; 89]; %! Test2 = [89; 77; 92; 86]; %! Test3 = [95; 62; 89; 91]; %! Test4 = [95; 62; 89; 91]; %! U = table (Test1, Test2, Test3, Test4); %! S = stack (U, {"Test1", "Test4"}, 'ConstantVariables', "Test2", ... %! 'NewDataVariableName', 'NewData', 'IndexVariableName', 'Index'); %! assert_equal (S.Properties.VariableNames, ... %! {"Test2", "Index", "NewData"}); %!test # units and descriptions are inherited from the first stacked variable %! Test1 = [93; 57]; %! Test2 = [89; 77]; %! U = table (Test1, Test2); %! U.Properties.VariableUnits = {'cm', 'kg'}; %! U.Properties.VariableDescriptions = {'height', 'weight'}; %! S = stack (U, 1:2); %! assert_equal (S.Properties.VariableUnits, {'', 'cm'}); %! assert_equal (S.Properties.VariableDescriptions, {'Data indicator', 'height'}); %!test # multiple variable groups stack into one data variable each %! T1 = [1; 2; 3]; %! T2 = [4; 5; 6]; %! T3 = [7; 8; 9]; %! T4 = [10; 11; 12]; %! U = table (T1, T2, T3, T4); %! [S, idx] = stack (U, {{'T1', 'T2'}, {'T3', 'T4'}}); %! assert_equal (S.Properties.VariableNames, {'Indicator', 'T1_T2', 'T3_T4'}); %! assert_equal (class (S.Indicator), 'double'); %! assert_equal (S.Indicator, [1; 2; 1; 2; 1; 2]); %! assert_equal (S.T1_T2, [1; 4; 2; 5; 3; 6]); %! assert_equal (S.T3_T4, [7; 10; 8; 11; 9; 12]); %! assert_equal (idx, [1; 1; 2; 2; 3; 3]); %!test # multi-group inherits per-group metadata; indicator stays generic %! T1 = [1; 2]; %! T2 = [3; 4]; %! T3 = [5; 6]; %! T4 = [7; 8]; %! U = table (T1, T2, T3, T4); %! U.Properties.VariableUnits = {'u1', 'u2', 'u3', 'u4'}; %! U.Properties.VariableDescriptions = {'d1', 'd2', 'd3', 'd4'}; %! S = stack (U, {{'T1', 'T2'}, {'T3', 'T4'}}); %! assert_equal (S.Properties.VariableUnits, {'', 'u1', 'u3'}); %! assert_equal (S.Properties.VariableDescriptions, ... %! {'Data indicator', 'd1', 'd3'}); %!test # multi-group accepts one new data name per group and an index name %! T1 = [1; 2]; %! T2 = [3; 4]; %! T3 = [5; 6]; %! T4 = [7; 8]; %! U = table (T1, T2, T3, T4); %! S = stack (U, {{'T1', 'T2'}, {'T3', 'T4'}}, ... %! 'NewDataVariableName', {'A', 'B'}, 'IndexVariableName', 'Idx'); %! assert_equal (S.Properties.VariableNames, {'Idx', 'A', 'B'}); ## Test input validation for 'stack' method %!error stack (tblA); %!error ... %! stack (tblA, "Some"); %!error ... %! stack (tblA, {{'Height', 'Weight'}, {'Age'}}); %!error ... %! stack (tblA, 1:3, "ConstantVariables", "Some"); %!error ... %! stack (tblA, 1:3, "ConstantVariables", "Weight"); %!error ... %! stack (tblA, 1:3, "NewDataVariableName", 3); %!error ... %! stack (tblA, 1:2, "NewDataVariableName", {'A', 'B'}); %!error ... %! stack (tblA, 1:3, "IndexVariableName", 3); ## Test 'unstack' method %!test # a single variable is unstacked by a categorical indicator %! Storm = [3; 3; 1; 3; 1; 1; 4; 2; 4; 2; 4; 2]; %! Town = categorical ({'Natick'; 'Worcester'; 'Natick'; 'Boston'; ... %! 'Boston'; 'Worcester'; 'Boston'; 'Natick'; ... %! 'Worcester'; 'Worcester'; 'Natick'; 'Boston'}); %! Snowfall = [0; 3; 5; 5; 9; 10; 12; 13; 15; 16; 17; 21]; %! S = table (Storm, Town, Snowfall); %! [U, idx] = unstack (S, 'Snowfall', 'Town'); %! assert_equal (U.Properties.VariableNames, ... %! {'Storm', 'Boston', 'Natick', 'Worcester'}); %! assert_equal (U.Storm, [3; 1; 4; 2]); %! assert_equal (U.Boston, [5; 9; 12; 21]); %! assert_equal (U.Natick, [0; 5; 17; 13]); %! assert_equal (U.Worcester, [3; 10; 15; 16]); %! assert_equal (idx, [1; 3; 7; 8]); %!test # duplicate group/indicator pairs are aggregated by @sum (numeric default) %! Storm = [3; 3]; %! Town = categorical ({'Boston'; 'Boston'}); %! Snowfall = [5; 100]; %! U = unstack (table (Storm, Town, Snowfall), 'Snowfall', 'Town'); %! assert_equal (U.Boston, 105); %!test # a custom AggregationFunction overrides the default %! G = [1; 1; 1; 2]; %! T = categorical ({'a'; 'a'; 'b'; 'a'}); %! V = [3; 9; 5; 7]; %! U = unstack (table (G, T, V), 'V', 'T', 'AggregationFunction', @max); %! assert_equal (U.a, [9; 7]); %! assert_equal (U.b, [5; NaN]); %!test # a numeric indicator derives valid variable names %! G = [1; 1; 2; 2]; %! T = [10; 20; 10; 20]; %! V = [1; 2; 3; 4]; %! U = unstack (table (G, T, V), 'V', 'T'); %! assert_equal (U.Properties.VariableNames, {'G', 'x10', 'x20'}); %! assert_equal (U.x10, [1; 3]); %! assert_equal (U.x20, [2; 4]); %!test # ConstantVariables take their value from the first row of each group %! G = [1; 1; 2; 2]; %! T = categorical ({'a'; 'b'; 'a'; 'b'}); %! V = [1; 2; 3; 4]; %! C = [100; 101; 200; 201]; %! U = unstack (table (G, T, V, C), 'V', 'T', 'ConstantVariables', 'C'); %! assert_equal (U.Properties.VariableNames, {'G', 'C', 'a', 'b'}); %! assert_equal (U.C, [100; 200]); %!test # explicit GroupingVariables restrict the grouping set %! G = [1; 1; 2; 2]; %! T = categorical ({'a'; 'b'; 'a'; 'b'}); %! V = [1; 2; 3; 4]; %! C = [100; 101; 200; 201]; %! U = unstack (table (G, T, V, C), 'V', 'T', 'GroupingVariables', 'G'); %! assert_equal (U.Properties.VariableNames, {'G', 'a', 'b'}); %!test # NewDataVariableNames rename the unstacked variables %! G = [1; 1; 2; 2]; %! T = categorical ({'a'; 'b'; 'a'; 'b'}); %! V = [1; 2; 3; 4]; %! U = unstack (table (G, T, V), 'V', 'T', 'NewDataVariableNames', {'AA', 'BB'}); %! assert_equal (U.Properties.VariableNames, {'G', 'AA', 'BB'}); %!test # VariableNamingRule 'preserve' keeps invalid identifiers verbatim %! G = [1; 1; 2; 2]; %! T = categorical ({'New York'; 'New York'; 'Los Angeles'; 'Los Angeles'}); %! V = [1; 2; 3; 4]; %! U = unstack (table (G, T, V), 'V', 'T', 'VariableNamingRule', 'preserve'); %! assert_equal (U.Properties.VariableNames, {'G', 'Los Angeles', 'New York'}); %!test # the default 'modify' rule produces valid identifiers %! G = [1; 1; 2; 2]; %! T = categorical ({'New York'; 'New York'; 'Los Angeles'; 'Los Angeles'}); %! V = [1; 2; 3; 4]; %! U = unstack (table (G, T, V), 'V', 'T'); %! assert_equal (U.Properties.VariableNames, {'G', 'LosAngeles', 'NewYork'}); %!test # rows with missing grouping values are excluded from unstacking %! Storm = [3; 3; 1; 3; 1; 1; 4; 2; 4; 2; 4; 2]; %! Town = categorical ({'Natick'; 'Worcester'; 'Natick'; 'Boston'; ... %! 'Boston'; 'Worcester'; 'Boston'; 'Natick'; ... %! 'Worcester'; 'Worcester'; 'Natick'; 'Boston'}); %! Snowfall = [0; 3; 5; 5; 9; 10; 12; 13; 15; 16; 17; 21]; %! Storm(1) = NaN; %! S = table (Storm, Town, Snowfall); %! [U, idx] = unstack (S, 'Snowfall', 'Town'); %! assert_equal (U.Storm, [3; 1; 4; 2]); %! assert_equal (U.Natick, [NaN; 5; 17; 13]); %! assert_equal (U.Boston, [5; 9; 12; 21]); %! assert_equal (idx, [2; 3; 7; 8]); %!test # with no grouping variables every row collapses to a single output row %! Town = categorical ({'Boston'; 'Natick'; 'Worcester'; 'Boston'}); %! Snowfall = [5; 10; 15; 20]; %! [U, idx] = unstack (table (Town, Snowfall), 'Snowfall', 'Town'); %! assert_equal (size (U), [1, 3]); %! assert_equal (U.Boston, 25); %! assert_equal (idx, 1); %!test # categorical data are unstacked (default @unique, no conflict) %! G = [1; 1; 2; 2]; %! T = categorical ({'a'; 'b'; 'a'; 'b'}); %! V = categorical ({'p'; 'q'; 'r'; 's'}); %! U = unstack (table (G, T, V), 'V', 'T'); %! assert_equal (cellstr (U.a), {'p'; 'r'}); %! assert_equal (cellstr (U.b), {'q'; 's'}); %!test # logical data are unstacked (regression: default no longer crashes) %! G = [1; 1; 2; 2]; %! T = categorical ({'a'; 'b'; 'a'; 'b'}); %! B = logical ([1; 0; 1; 0]); %! U = unstack (table (G, T, B), 'B', 'T'); %! assert_equal (U.a, [true; true]); %! assert_equal (U.b, [false; false]); %!test # string data are unstacked (regression: default no longer crashes) %! G = [1; 1; 2; 2]; %! T = categorical ({'a'; 'b'; 'a'; 'b'}); %! V = string ({'p'; 'q'; 'r'; 's'}); %! U = unstack (table (G, T, V), 'V', 'T'); %! assert_equal (cellstr (U.a), {'p'; 'r'}); %! assert_equal (cellstr (U.b), {'q'; 's'}); %!test # cellstr data are unstacked (regression: default no longer crashes) %! G = [1; 1; 2; 2]; %! T = categorical ({'a'; 'b'; 'a'; 'b'}); %! V = {'p'; 'q'; 'r'; 's'}; %! U = unstack (table (G, T, V), 'V', 'T'); %! assert_equal (U.a, {'p'; 'r'}); %! assert_equal (U.b, {'q'; 's'}); %!test # duration data are unstacked (default @unique, no conflict) %! G = [1; 1; 2; 2]; %! T = categorical ({'a'; 'b'; 'a'; 'b'}); %! V = hours ([1; 2; 3; 4]); %! U = unstack (table (G, T, V), 'V', 'T'); %! assert_equal (hours (U.a), [1; 3]); %! assert_equal (hours (U.b), [2; 4]); %!test # missing duration cells fill with zero, identical conflicts collapse %! G = [1; 1; 1; 2]; %! T = categorical ({'a'; 'a'; 'b'; 'b'}); %! V = hours ([2; 2; 3; 4]); %! U = unstack (table (G, T, V), 'V', 'T'); %! assert_equal (hours (U.a), [2; 0]); %! assert_equal (hours (U.b), [3; 4]); %!test # calendarDuration data are unstacked (default @unique, no conflict) %! G = [1; 1; 2; 2]; %! T = categorical ({'a'; 'b'; 'a'; 'b'}); %! V = calmonths ([1; 2; 3; 4]); %! U = unstack (table (G, T, V), 'V', 'T'); %! assert_equal (calmonths (U.a), [1; 3]); %! assert_equal (calmonths (U.b), [2; 4]); %!test # missing calendarDuration cells fill with zero, identical conflicts collapse %! G = [1; 1; 1; 2]; %! T = categorical ({'a'; 'a'; 'b'; 'b'}); %! V = calmonths ([1; 1; 2; 3]); %! U = unstack (table (G, T, V), 'V', 'T'); %! assert_equal (calmonths (U.a), [1; 0]); %! assert_equal (calmonths (U.b), [2; 3]); %!test # multiple variables are unstacked with composite names %! G = [1; 1; 2; 2]; %! T = categorical ({'a'; 'b'; 'a'; 'b'}); %! N = [10; 20; 30; 40]; %! M = [1; 2; 3; 4]; %! U = unstack (table (G, T, N, M), {'N', 'M'}, 'T'); %! assert_equal (U.Properties.VariableNames, {'G', 'N_a', 'N_b', 'M_a', 'M_b'}); %! assert_equal (U.N_a, [10; 30]); %! assert_equal (U.N_b, [20; 40]); %! assert_equal (U.M_a, [1; 3]); %! assert_equal (U.M_b, [2; 4]); %!test # multiple variables of different types are unstacked together %! G = [1; 1; 2; 2]; %! T = categorical ({'a'; 'b'; 'a'; 'b'}); %! N = [10; 20; 30; 40]; %! C = categorical ({'p'; 'q'; 'r'; 's'}); %! U = unstack (table (G, T, N, C), {'N', 'C'}, 'T'); %! assert_equal (U.N_a, [10; 30]); %! assert_equal (cellstr (U.C_a), {'p'; 'r'}); ## Test input validation for 'unstack' method %!error unstack (tblA); %!error unstack (tblA, 'Height'); %!error unstack (tblA, [], 'Height'); %!error ... %! unstack (tblA, 'some', 'Height'); %!error ... %! Age = [38; 43; 38; 40; 49]; %! Smoker = logical ([1; 0; 1; 0; 1]); %! Height = [71; 69; 64; 67; 64]; %! unstack (table (table (Age), Smoker, Height), 'Var1', 'Height'); %!error ... %! unstack (tblA, 'Height', {'Smoker', 'Age'}); %!error ... %! unstack (tblA, 'Height', 'some'); %!error ... %! unstack (tblA, 'Height', 'BloodPressure'); %!error ... %! unstack (tblA, 'Height', 'Height'); %!error ... %! Smoker = logical ([1; 0; 1; 0; 1]); %! Height = [71; 69; 64; 67; 64]; %! unstack (table ({4;5;6;7;8}, Smoker, Height), 'Height', 'Var1'); %!error ... %! unstack (tblA, 'Height', 'Age', 'ConstantVariables', 'some'); %!error ... %! unstack (tblA, 'Height', 'Age', 'ConstantVariables', 'Height'); %!error ... %! unstack (tblA, 'Height', 'Age', 'ConstantVariables', 'Age'); %!error ... %! unstack (tblA, 'Height', 'Age', 'GroupingVariables', 'some'); %!error ... %! unstack (tblA, 'Height', 'Age', 'GroupingVariables', 'Height'); %!error ... %! unstack (tblA, 'Height', 'Age', 'GroupingVariables', 'Age'); %!warning ... %! Smoker = logical ([1; 0; 1; 0; 1]); %! Height = [71; 69; 64; 67; 64]; %! Age = [38; 43; 38; 40; 49]; %! unstack (table ({4;5;6;7;8}, Smoker, Height, Age), 'Height', 'Age', 'GroupingVariables', 'Var1'); %!error ... %! unstack (tblA, 'BloodPressure', 'Height', 'ConstantVariables', 'Age', 'GroupingVariables', 'Age'); %!error ... %! unstack (tblA, 'Height', 'Age', 'NewDataVariableNames', [1:5]); %!error ... %! unstack (tblA, 'Height', 'Age', 'NewDataVariableNames', {"A","B","C"}); %!error ... %! unstack (tblA, 'Height', 'Age', 'AggregationFunction', 1); %!error ... %! unstack (tblA, 'Height', 'Age', 'AggregationFunction', 'isnumeric'); %!error ... %! unstack (table ([1; 1; 2], categorical ({'a'; 'a'; 'b'}), ... %! categorical ({'x'; 'y'; 'z'})), 'Var3', 'Var2'); %!error ... %! unstack (table ([1; 1; 2], categorical ({'a'; 'a'; 'b'}), ... %! hours ([1; 2; 3])), 'Var3', 'Var2'); %!error ... %! unstack (tblA, {'Weight', 'Height'}, 'Age', 'VariableNamingRule', 'somerule'); ## Test 'inner2outer' method %!test # basic transpose: two nested tables sharing inner names %! A = table ([1;2;3], [4;5;6], 'VariableNames', {'X', 'Y'}); %! B = table ([7;8;9], [10;11;12], 'VariableNames', {'X', 'Y'}); %! T = inner2outer (table (A, B, 'VariableNames', {'A', 'B'})); %! assert_equal (T.Properties.VariableNames, {'X', 'Y'}); %! assert_equal (T.X.Properties.VariableNames, {'A', 'B'}); %! assert_equal (T.X.A, [1; 2; 3]); %! assert_equal (T.X.B, [7; 8; 9]); %! assert_equal (T.Y.A, [4; 5; 6]); %! assert_equal (T.Y.B, [10; 11; 12]); %!test # non-nested variables keep their relative position %! A = table ([1;2;3], [4;5;6], 'VariableNames', {'X', 'Y'}); %! B = table ([7;8;9], [10;11;12], 'VariableNames', {'X', 'Y'}); %! C = [100; 200; 300]; %! T = inner2outer (table (C, A, B, 'VariableNames', {'C', 'A', 'B'})); %! assert_equal (T.Properties.VariableNames, {'C', 'X', 'Y'}); %! assert_equal (T.C, [100; 200; 300]); %! T = inner2outer (table (A, C, B, 'VariableNames', {'A', 'C', 'B'})); %! assert_equal (T.Properties.VariableNames, {'X', 'Y', 'C'}); %!test # inner names are regrouped by name regardless of their order %! A = table ([1;2;3], [4;5;6], 'VariableNames', {'X', 'Y'}); %! Br = table ([10;11;12], [7;8;9], 'VariableNames', {'Y', 'X'}); %! T = inner2outer (table (A, Br, 'VariableNames', {'A', 'B'})); %! assert_equal (T.Properties.VariableNames, {'X', 'Y'}); %! assert_equal (T.X.A, [1; 2; 3]); %! assert_equal (T.X.B, [7; 8; 9]); %! assert_equal (T.Y.A, [4; 5; 6]); %! assert_equal (T.Y.B, [10; 11; 12]); %!test # mismatched inner names: union, single-source names become plain vars %! A = table ([1;2;3], [4;5;6], 'VariableNames', {'X', 'Y'}); %! Bz = table ([7;8;9], [10;11;12], 'VariableNames', {'X', 'Z'}); %! T = inner2outer (table (A, Bz, 'VariableNames', {'A', 'B'})); %! assert_equal (T.Properties.VariableNames, {'X', 'Y', 'Z'}); %! assert_equal (istable (T.X), true); %! assert_equal (T.X.Properties.VariableNames, {'A', 'B'}); %! assert_equal (istable (T.Y), false); %! assert_equal (T.Y, [4; 5; 6]); %! assert_equal (istable (T.Z), false); %! assert_equal (T.Z, [10; 11; 12]); %!test # three nested tables %! A = table ([1;2;3], [4;5;6], 'VariableNames', {'X', 'Y'}); %! B = table ([7;8;9], [10;11;12], 'VariableNames', {'X', 'Y'}); %! D = table ([13;14;15], [16;17;18], 'VariableNames', {'X', 'Y'}); %! T = inner2outer (table (A, B, D, 'VariableNames', {'A', 'B', 'D'})); %! assert_equal (T.X.Properties.VariableNames, {'A', 'B', 'D'}); %! assert_equal (T.X.D, [13; 14; 15]); %!test # inner variable metadata is inherited; parent row names are preserved %! A = table ([1;2;3], [4;5;6], 'VariableNames', {'X', 'Y'}); %! A.Properties.VariableUnits = {'kg', 'm'}; %! A.Properties.VariableDescriptions = {'mass', 'length'}; %! B = table ([7;8;9], [10;11;12], 'VariableNames', {'X', 'Y'}); %! B.Properties.VariableUnits = {'kg', 'm'}; %! B.Properties.VariableDescriptions = {'mass', 'length'}; %! T = inner2outer (table (A, B, 'VariableNames', {'A', 'B'}, ... %! 'RowNames', {'r1', 'r2', 'r3'})); %! assert_equal (T.Properties.RowNames, {'r1'; 'r2'; 'r3'}); %! assert_equal (T.X.Properties.VariableDescriptions, {'mass', 'mass'}); %! assert_equal (T.X.Properties.VariableUnits, {'kg', 'kg'}); %! assert_equal (T.Y.Properties.VariableDescriptions, {'length', 'length'}); %! assert_equal (T.Y.Properties.VariableUnits, {'m', 'm'}); %! assert_equal (isempty (T.X.Properties.RowNames), true); ## Test input validation for 'inner2outer' method %!error ... %! inner2outer (table ([1; 2])); %!error ... %! inner2outer (table (table ([1;2], 'VariableNames', {'A'}), [3;4], ... %! 'VariableNames', {'N', 'A'})); ## Test 'addprop' method %!test # custom properties of both kinds are added %! T = table ([1; 2], {'a'; 'b'}, 'VariableNames', {'x', 'y'}); %! T = addprop (T, {'Note', 'Flags'}, {'table', 'variable'}); %! assert_equal (fieldnames (T.Properties.CustomProperties), {'Note'; 'Flags'}); %!test # a single property is added with character-vector inputs %! T = table ([1; 2], 'VariableNames', {'x'}); %! T = addprop (T, 'Note', 'table'); %! assert_equal (fieldnames (T.Properties.CustomProperties), {'Note'}); %!test # values are assigned to a 'table' custom property with dot syntax %! T = table ([1; 2], 'VariableNames', {'x'}); %! T = addprop (T, 'Note', 'table'); %! T.Properties.CustomProperties.Note = 'hello'; %! assert_equal (T.Properties.CustomProperties.Note, 'hello'); %!test # a 'variable' custom property holds one value per variable %! T = table ([1; 2], {'a'; 'b'}, 'VariableNames', {'x', 'y'}); %! T = addprop (T, 'Flags', 'variable'); %! T.Properties.CustomProperties.Flags = {'f1', 'f2'}; %! assert_equal (T.Properties.CustomProperties.Flags, {'f1', 'f2'}); %!test # properties can be appended to a table that already has some %! T = table ([1; 2], 'VariableNames', {'x'}); %! T = addprop (T, 'Note', 'table'); %! T = addprop (T, 'Flags', 'variable'); %! assert_equal (fieldnames (T.Properties.CustomProperties), {'Note'; 'Flags'}); %!test # property names and types may be given as a string array %! T = table ([1; 2], {'a'; 'b'}, 'VariableNames', {'x', 'y'}); %! T = addprop (T, string ({'Note', 'Flags'}), string ({'table', 'variable'})); %! assert_equal (fieldnames (T.Properties.CustomProperties), {'Note'; 'Flags'}); ## Test input validation for 'addprop' method %!error ... %! addprop (table (1), {'a'}); %!error ... %! addprop (table (1), 5, 'table'); %!error ... %! addprop (table (1), 'a', 5); %!error ... %! addprop (table (1), {'a', 'b'}, {'table'}); %!error ... %! addprop (table (1), {'a', 'a'}, {'table', 'variable'}); %!error ... %! addprop (addprop (table (1), 'Note', 'table'), 'Note', 'table'); %!error ... %! addprop (addprop (table (1), 'Flags', 'table'), {'New', 'Flags'}, {'table', 'table'}); %!error ... %! addprop (table (1), '1bad', 'table'); %!error ... %! addprop (table (1), 'a', 'foo'); ## Custom-property merge semantics across multi-table operations %!test # horzcat: table-scoped first-wins, variable-scoped concatenate %! A = table ([1; 2], [3; 4], 'VariableNames', {'k', 'a'}); %! A = addprop (A, 'ts', 'table'); A.Properties.CustomProperties.ts = 'A'; %! A = addprop (A, 'vs', 'variable'); A.Properties.CustomProperties.vs = [10 11]; %! B = table ([7; 8], 'VariableNames', {'b'}); %! B = addprop (B, 'ts', 'table'); B.Properties.CustomProperties.ts = 'B'; %! B = addprop (B, 'vs', 'variable'); B.Properties.CustomProperties.vs = [20]; %! H = [A, B]; %! assert_equal (H.Properties.CustomProperties.ts, 'A'); %! assert_equal (H.Properties.CustomProperties.vs, [10 11 20]); %!test # horzcat: a table lacking a variable-scoped property NaN-fills its block %! A = table ([1; 2], [3; 4], 'VariableNames', {'k', 'a'}); %! A = addprop (A, 'vs', 'variable'); A.Properties.CustomProperties.vs = [10 11]; %! C = table ([7; 8], 'VariableNames', {'c'}); %! H = [A, C]; %! assert_equal (H.Properties.CustomProperties.vs, [10 11 NaN]); %!test # horzcat: disjoint table-scoped props are unioned, table before variable %! A = table ([1; 2], [3; 4], 'VariableNames', {'k', 'a'}); %! A = addprop (A, 'ts', 'table'); A.Properties.CustomProperties.ts = 'A'; %! A = addprop (A, 'vs', 'variable'); A.Properties.CustomProperties.vs = [10 11]; %! D = table ([7; 8], 'VariableNames', {'d'}); %! D = addprop (D, 'other', 'table'); D.Properties.CustomProperties.other = 'D'; %! H = [A, D]; %! assert_equal (fieldnames (H.Properties.CustomProperties), {'ts'; 'other'; 'vs'}); %! assert_equal (H.Properties.CustomProperties.other, 'D'); %! assert_equal (H.Properties.CustomProperties.vs, [10 11 NaN]); %!test # vertcat keeps the first table's custom properties (identical variables) %! A = table ([1; 2], [3; 4], 'VariableNames', {'k', 'a'}); %! A = addprop (A, 'ts', 'table'); A.Properties.CustomProperties.ts = 'A'; %! A = addprop (A, 'vs', 'variable'); A.Properties.CustomProperties.vs = [10 11]; %! A2 = table ([5; 6], [7; 8], 'VariableNames', {'k', 'a'}); %! A2 = addprop (A2, 'ts', 'table'); A2.Properties.CustomProperties.ts = 'A2'; %! A2 = addprop (A2, 'vs', 'variable'); A2.Properties.CustomProperties.vs = [30 31]; %! V = [A; A2]; %! assert_equal (V.Properties.CustomProperties.ts, 'A'); %! assert_equal (V.Properties.CustomProperties.vs, [10 11]); %!test # splitvars replicates a split variable's variable-scoped entry %! S = table ([1; 2], [3 4; 5 6], 'VariableNames', {'x', 'm'}); %! S = addprop (S, 'vs', 'variable'); S.Properties.CustomProperties.vs = [100 200]; %! SS = splitvars (S, 'm'); %! assert_equal (SS.Properties.CustomProperties.vs, [100 200 200]); %!test # join maps variable-scoped props per source (key+left from left, right from right) %! L = table ([1; 2], [3; 4], 'VariableNames', {'k', 'a'}); %! L = addprop (L, 'ts', 'table'); L.Properties.CustomProperties.ts = 'L'; %! L = addprop (L, 'vs', 'variable'); L.Properties.CustomProperties.vs = [10 11]; %! R = table ([1; 2], [5; 6], 'VariableNames', {'k', 'b'}); %! R = addprop (R, 'ts', 'table'); R.Properties.CustomProperties.ts = 'R'; %! R = addprop (R, 'vs', 'variable'); R.Properties.CustomProperties.vs = [20 21]; %! J = innerjoin (L, R); %! assert_equal (J.Properties.CustomProperties.ts, 'L'); %! assert_equal (J.Properties.CustomProperties.vs, [10 11 21]); %!test # unstack replicates the unstacked variable's entry onto its new columns %! T = table ([1; 1; 2; 2], categorical ({'x'; 'y'; 'x'; 'y'}), [10; 20; 30; 40], ... %! 'VariableNames', {'g', 'k', 'v'}); %! T = addprop (T, 'ts', 'table'); T.Properties.CustomProperties.ts = 'T'; %! T = addprop (T, 'vs', 'variable'); T.Properties.CustomProperties.vs = [1 2 3]; %! U = unstack (T, 'v', 'k'); %! assert_equal (U.Properties.VariableNames, {'g', 'x', 'y'}); %! assert_equal (U.Properties.CustomProperties.ts, 'T'); %! assert_equal (U.Properties.CustomProperties.vs, [1 3 3]); ## Test 'rmprop' method %!test # a custom property is removed, others are kept %! T = table ([1; 2], 'VariableNames', {'x'}); %! T = addprop (T, {'Note', 'Flags'}, {'table', 'variable'}); %! T = rmprop (T, 'Note'); %! assert_equal (fieldnames (T.Properties.CustomProperties), {'Flags'}); %!test # several custom properties are removed in one call %! T = table ([1; 2], 'VariableNames', {'x'}); %! T = addprop (T, {'Note', 'Flags', 'Tag'}, {'table', 'variable', 'table'}); %! T = rmprop (T, {'Note', 'Tag'}); %! assert_equal (fieldnames (T.Properties.CustomProperties), {'Flags'}); %!test # property names may be given as a string array %! T = table ([1; 2], 'VariableNames', {'x'}); %! T = addprop (T, {'Note', 'Flags'}, {'table', 'variable'}); %! T = rmprop (T, string ({'Note', 'Flags'})); %! assert_equal (fieldnames (T.Properties.CustomProperties), cell (0, 1)); %!test # duplicate names are tolerated (the property is removed once) %! T = table ([1; 2], 'VariableNames', {'x'}); %! T = addprop (T, {'Note', 'Flags'}, {'table', 'variable'}); %! T = rmprop (T, {'Note', 'Note'}); %! assert_equal (fieldnames (T.Properties.CustomProperties), {'Flags'}); %!test # names that do not match an existing property are silently ignored %! T = table ([1; 2], 'VariableNames', {'x'}); %! T = addprop (T, {'Note', 'Flags'}, {'table', 'variable'}); %! T = rmprop (T, {'Note', 'Missing'}); %! assert_equal (fieldnames (T.Properties.CustomProperties), {'Flags'}); %!test # removing from a table without custom properties is a no-op %! T = table ([1; 2], 'VariableNames', {'x'}); %! T = rmprop (T, 'Missing'); %! assert_equal (T.Properties.CustomProperties, []); ## Test input validation for 'rmprop' method %!error ... %! rmprop (table (1)); %!error ... %! rmprop (table (1), 5); ################################################################################ ## ** Join and Set Operations ** ## ################################################################################ ## Available Methods ## ## ## ## 'join' 'innerjoin' 'outerjoin' 'union' ## ## 'intersect' 'ismember' 'setdiff' 'setxor' ## ## ## ################################################################################ ## Test 'join' method %!test %! L = table ([3;1;2;1], {'a';'b';'c';'d'}, 'VariableNames', {'Key', 'L'}, ... %! 'RowNames', {'r1', 'r2', 'r3', 'r4'}); %! R = table ([1;2;3], {'x';'y';'z'}, 'VariableNames', {'Key', 'R'}); %! [T, ixR] = join (L, R); %! assert_equal (T.Properties.VariableNames, {'Key', 'L', 'R'}); %! assert_equal (T.Key, [3;1;2;1]); %! assert_equal (T.R, {'z';'x';'y';'x'}); %! assert_equal (ixR, [3;1;2;1]); %! assert_equal (T.Properties.RowNames, {'r1';'r2';'r3';'r4'}); ## 'join' with 'LeftKeys'/'RightKeys' %!test %! L = table ([1;2], (10:11)', 'VariableNames', {'KL', 'A'}); %! R = table ([2;1], {'x';'y'}, 'VariableNames', {'KR', 'B'}); %! T = join (L, R, 'LeftKeys', 'KL', 'RightKeys', 'KR'); %! assert_equal (T.Properties.VariableNames, {'KL', 'A', 'B'}); %! assert_equal (T.A, [10;11]); %! assert_equal (T.B, {'y';'x'}); ## 'join' with 'Keys' and 'RightVariables' %!test %! L = table ([1;2;3], (1:3)', 'VariableNames', {'K', 'A'}); %! R = table ([3;2;1], {'x';'y';'z'}, (4:6)', 'VariableNames', {'K', 'B', 'C'}); %! T = join (L, R, 'Keys', 'K', 'RightVariables', 'B'); %! assert_equal (T.Properties.VariableNames, {'K', 'A', 'B'}); %! assert_equal (T.B, {'z';'y';'x'}); ## 'join' suffixes shared non-key names with the input argument names %!test %! L = table ([1;2], {'a';'b'}, 'VariableNames', {'KeyL', 'V'}); %! R = table ([1;2], {'p';'q'}, 'VariableNames', {'KeyR', 'V'}); %! T = join (L, R, 'LeftKeys', 'KeyL', 'RightKeys', 'KeyR'); %! assert_equal (T.Properties.VariableNames, {'KeyL', 'V_L', 'V_R'}); ## 'join' with 'KeepOneCopy' drops the right copy of the named variable %!test %! L = table ([1;2], {'a';'b'}, 'VariableNames', {'KeyL', 'V'}); %! R = table ([1;2], {'p';'q'}, 'VariableNames', {'KeyR', 'V'}); %! T = join (L, R, 'LeftKeys', 'KeyL', 'RightKeys', 'KeyR', 'KeepOneCopy', 'V'); %! assert_equal (T.Properties.VariableNames, {'KeyL', 'V'}); %! assert_equal (T.V, {'a';'b'}); ## 'join' on a duration key variable %!test %! L = table (hours ([3;1;2]), {'a';'b';'c'}, 'VariableNames', {'K', 'L'}); %! R = table (hours ([1;2;3]), {'x';'y';'z'}, 'VariableNames', {'K', 'R'}); %! [T, ixR] = join (L, R); %! assert_equal (hours (T.K), [3;1;2]); %! assert_equal (T.R, {'z';'x';'y'}); %! assert_equal (ixR, [3;1;2]); ## 'join' carries a categorical key and a calendarDuration variable %!test %! L = table (categorical ({'a';'b'}), calmonths ([5;6]), ... %! 'VariableNames', {'K', 'M'}); %! R = table (categorical ({'b';'a'}), {'x';'y'}, 'VariableNames', {'K', 'R'}); %! T = join (L, R); %! assert_equal (cellstr (T.K), {'a';'b'}); %! assert_equal (calmonths (T.M), [5;6]); %! assert_equal (T.R, {'y';'x'}); ## 'join' on a datetime key variable %!test %! L = table (datetime (2020, 1, [3;1;2]), {'a';'b';'c'}, ... %! 'VariableNames', {'K', 'L'}); %! R = table (datetime (2020, 1, [1;2;3]), {'x';'y';'z'}, ... %! 'VariableNames', {'K', 'R'}); %! [T, ixR] = join (L, R); %! assert_equal (datevec (T.K)(:,3), [3;1;2]); %! assert_equal (T.R, {'z';'x';'y'}); %! assert_equal (ixR, [3;1;2]); ## 'join' with 'LeftVariables' restricts the left columns kept %!test %! L = table ([1;2;3], (10:12)', {'p';'q';'r'}, ... %! 'VariableNames', {'K', 'A', 'C'}); %! R = table ([3;2;1], {'x';'y';'z'}, 'VariableNames', {'K', 'B'}); %! T = join (L, R, 'LeftVariables', {'K', 'A'}); %! assert_equal (T.Properties.VariableNames, {'K', 'A', 'B'}); %! assert_equal (T.A, [10;11;12]); %! assert_equal (T.B, {'z';'y';'x'}); ## 'join' suffixes anonymous inputs with '_left'/'_right' %!test %! T = join (table ([1;2], {'a';'b'}, 'VariableNames', {'KeyL', 'V'}), ... %! table ([1;2], {'p';'q'}, 'VariableNames', {'KeyR', 'V'}), ... %! 'LeftKeys', 'KeyL', 'RightKeys', 'KeyR'); %! assert_equal (T.Properties.VariableNames, {'KeyL', 'V_left', 'V_right'}); %!error ... %! join (table ([1;2])); %!error ... %! join (table ([1;2]), 5); %!error ... %! join (table (1), table (1), 'Keys', 1, 'LeftKeys', 1); %!error ... %! join (table (1, 'VariableNames', {'a'}), ... %! table (1, 'VariableNames', {'b'}), 'LeftKeys', 'a'); %!error ... %! join (table ([1;2], [1;2], 'VariableNames', {'a', 'b'}), ... %! table ([1;2], 'VariableNames', {'c'}), ... %! 'LeftKeys', {'a', 'b'}, 'RightKeys', 'c'); %!error ... %! join (table ((1:3)', 'VariableNames', {'AA'}), ... %! table ((1:3)', 'VariableNames', {'BB'})); %!error ... %! join (table ([1;2], 'VariableNames', {'K'}), ... %! table ([1;2], 'VariableNames', {'K'}), 'BadOption', 1); %!error ... %! join (table ([1;2], 'VariableNames', {'K'}), ... %! table ([1;1;2], {'x';'y';'z'}, 'VariableNames', {'K', 'R'})); %!error ... %! join (table ([1;9], 'VariableNames', {'K'}), ... %! table ([1;2], {'x';'y'}, 'VariableNames', {'K', 'R'})); ## Test 'innerjoin' method %!test %! L = table ([1;2;3;2], {'a';'b';'c';'d'}, 'VariableNames', {'Key', 'L'}); %! R = table ([2;3;5], {'x';'y';'z'}, 'VariableNames', {'Key', 'R'}); %! [T, ixL, ixR] = innerjoin (L, R); %! assert_equal (T.Properties.VariableNames, {'Key', 'L', 'R'}); %! assert_equal (T.Key, [2;2;3]); %! assert_equal (T.L, {'b';'d';'c'}); %! assert_equal (T.R, {'x';'x';'y'}); %! assert_equal (ixL, [2;4;3]); %! assert_equal (ixR, [1;1;2]); ## Inner join drops row names %!test %! L = table ([1;2], {'a';'b'}, 'VariableNames', {'Key', 'L'}, ... %! 'RowNames', {'r1', 'r2'}); %! R = table ([2;1], {'x';'y'}, 'VariableNames', {'Key', 'R'}); %! T = innerjoin (L, R); %! assert_equal (isempty (T.Properties.RowNames), true); ## Cartesian product on repeated keys (m*n rows) %!test %! L = table ([1;1], {'a';'b'}, 'VariableNames', {'K', 'L'}); %! R = table ([1;1], {'x';'y'}, 'VariableNames', {'K', 'R'}); %! [T, ixL, ixR] = innerjoin (L, R); %! assert_equal (height (T), 4); %! assert_equal (T.L, {'a';'a';'b';'b'}); %! assert_equal (T.R, {'x';'y';'x';'y'}); %! assert_equal (ixL, [1;1;2;2]); %! assert_equal (ixR, [1;2;1;2]); ## No matching keys yields an empty result with the correct variables %!test %! L = table ([1;2], {'a';'b'}, 'VariableNames', {'K', 'L'}); %! R = table ([8;9], {'x';'y'}, 'VariableNames', {'K', 'R'}); %! T = innerjoin (L, R); %! assert_equal (height (T), 0); %! assert_equal (T.Properties.VariableNames, {'K', 'L', 'R'}); ## Cellstr key variable %!test %! L = table ({'a';'b';'c'}, (1:3)', 'VariableNames', {'K', 'N'}); %! R = table ({'b';'a'}, {'Y';'X'}, 'VariableNames', {'K', 'M'}); %! T = innerjoin (L, R); %! assert_equal (T.K, {'a';'b'}); %! assert_equal (T.N, [1;2]); %! assert_equal (T.M, {'X';'Y'}); ## String key variable %!test %! L = table (string ({'a';'b'}), (1:2)', 'VariableNames', {'K', 'N'}); %! R = table (string ({'b'}), {'Z'}, 'VariableNames', {'K', 'M'}); %! T = innerjoin (L, R); %! assert_equal (cellstr (T.K), {'b'}); %! assert_equal (T.N, 2); %! assert_equal (T.M, {'Z'}); ## Categorical key variable, sorted by category code %!test %! L = table (categorical ({'a';'b';'c'}), (1:3)', 'VariableNames', {'K', 'N'}); %! R = table (categorical ({'c';'a'}), {'P';'Q'}, 'VariableNames', {'K', 'M'}); %! T = innerjoin (L, R); %! assert_equal (cellstr (T.K), {'a';'c'}); %! assert_equal (T.N, [1;3]); %! assert_equal (T.M, {'Q';'P'}); ## 'Keys' option selects the key explicitly %!test %! L = table ([1;2;3], (1:3)', 'VariableNames', {'K', 'A'}); %! R = table ([2;3], {'x';'y'}, 'VariableNames', {'K', 'B'}); %! T = innerjoin (L, R, 'Keys', 'K'); %! assert_equal (T.Properties.VariableNames, {'K', 'A', 'B'}); %! assert_equal (T.K, [2;3]); ## 'LeftKeys'/'RightKeys' for differently-named keys %!test %! L = table ([1;2;3], (10:12)', 'VariableNames', {'KL', 'A'}); %! R = table ([2;3], {'x';'y'}, 'VariableNames', {'KR', 'B'}); %! [T, ixL, ixR] = innerjoin (L, R, 'LeftKeys', 'KL', 'RightKeys', 'KR'); %! assert_equal (T.Properties.VariableNames, {'KL', 'A', 'B'}); %! assert_equal (T.KL, [2;3]); %! assert_equal (T.A, [11;12]); %! assert_equal (T.B, {'x';'y'}); %! assert_equal (ixL, [2;3]); %! assert_equal (ixR, [1;2]); ## 'LeftVariables' and 'RightVariables' restrict the output columns %!test %! L = table ([1;2;3], (10:12)', {'p';'q';'r'}, ... %! 'VariableNames', {'K', 'A', 'C'}); %! R = table ([2;3], {'x';'y'}, (5:6)', 'VariableNames', {'K', 'B', 'D'}); %! T = innerjoin (L, R, 'LeftVariables', 'A', 'RightVariables', 'B'); %! assert_equal (T.Properties.VariableNames, {'A', 'B'}); %! assert_equal (T.A, [11;12]); %! assert_equal (T.B, {'x';'y'}); ## Shared non-key variable names get '_Tleft'/'_Tright' suffixes %!test %! L = table ([1;2], {'a';'b'}, 'VariableNames', {'KeyL', 'V'}); %! R = table ([2;1], {'p';'q'}, 'VariableNames', {'KeyR', 'V'}); %! T = innerjoin (L, R, 'LeftKeys', 'KeyL', 'RightKeys', 'KeyR'); %! assert_equal (T.Properties.VariableNames, {'KeyL', 'V_L', 'V_R'}); %! assert_equal (T.V_L, {'a';'b'}); %! assert_equal (T.V_R, {'q';'p'}); ## Multiple key variables %!test %! L = table ([1;1;2], {'a';'b';'a'}, (1:3)', ... %! 'VariableNames', {'K1', 'K2', 'N'}); %! R = table ([1;2], {'b';'a'}, {'x';'y'}, ... %! 'VariableNames', {'K1', 'K2', 'M'}); %! T = innerjoin (L, R); %! assert_equal (T.Properties.VariableNames, {'K1', 'K2', 'N', 'M'}); %! assert_equal (T.K1, [1;2]); %! assert_equal (T.K2, {'b';'a'}); %! assert_equal (T.N, [2;3]); %! assert_equal (T.M, {'x';'y'}); ## 'innerjoin' on a duration key variable %!test %! L = table (hours ([3;1;2]), {'a';'b';'c'}, 'VariableNames', {'K', 'L'}); %! R = table (hours ([1;2;3]), {'x';'y';'z'}, 'VariableNames', {'K', 'R'}); %! [T, ixL, ixR] = innerjoin (L, R); %! assert_equal (hours (T.K), [1;2;3]); %! assert_equal (T.R, {'x';'y';'z'}); %! assert_equal (ixL, [2;3;1]); %! assert_equal (ixR, [1;2;3]); ## 'innerjoin' on a calendarDuration key variable %!test %! L = table (calmonths ([3;1;2]), {'a';'b';'c'}, 'VariableNames', {'K', 'L'}); %! R = table (calmonths ([2;1]), {'x';'y'}, 'VariableNames', {'K', 'R'}); %! [T, ixL, ixR] = innerjoin (L, R); %! assert_equal (calmonths (T.K), [1;2]); %! assert_equal (T.L, {'b';'c'}); %! assert_equal (T.R, {'y';'x'}); %! assert_equal (ixL, [2;3]); %! assert_equal (ixR, [2;1]); ## 'innerjoin' on a datetime key variable, sorted by key %!test %! L = table (datetime (2020, 1, [3;1;2]), {'a';'b';'c'}, ... %! 'VariableNames', {'K', 'L'}); %! R = table (datetime (2020, 1, [1;2;3]), {'x';'y';'z'}, ... %! 'VariableNames', {'K', 'R'}); %! [T, ixL, ixR] = innerjoin (L, R); %! assert_equal (datevec (T.K)(:,3), [1;2;3]); %! assert_equal (T.R, {'x';'y';'z'}); %! assert_equal (ixL, [2;3;1]); %! assert_equal (ixR, [1;2;3]); ## 'innerjoin' suffixes anonymous inputs with '_left'/'_right' %!test %! T = innerjoin (table ([1;2], {'a';'b'}, 'VariableNames', {'KeyL', 'V'}), ... %! table ([2;1], {'p';'q'}, 'VariableNames', {'KeyR', 'V'}), ... %! 'LeftKeys', 'KeyL', 'RightKeys', 'KeyR'); %! assert_equal (T.Properties.VariableNames, {'KeyL', 'V_left', 'V_right'}); %!error ... %! innerjoin (table ([1;2])); %!error ... %! innerjoin (table ([1;2]), 5); %!error ... %! innerjoin (table (1), table (1), 'Keys', 1, 'LeftKeys', 1); %!error ... %! innerjoin (table (1, 'VariableNames', {'a'}), ... %! table (1, 'VariableNames', {'b'}), 'LeftKeys', 'a'); %!error ... %! innerjoin (table ([1;2], [1;2], 'VariableNames', {'a', 'b'}), ... %! table ([1;2], 'VariableNames', {'c'}), ... %! 'LeftKeys', {'a', 'b'}, 'RightKeys', 'c'); %!error ... %! innerjoin (table ((1:3)', 'VariableNames', {'AA'}), ... %! table ((1:3)', 'VariableNames', {'BB'})); %!error ... %! innerjoin (table ([1;2], 'VariableNames', {'K'}), ... %! table ([1;2], 'VariableNames', {'K'}), 'BadOption', 1); %!error ... %! innerjoin (table ([1;2], 'VariableNames', {'K'}), ... %! table ({'1';'2'}, 'VariableNames', {'K'})); ## Test 'outerjoin' method %!test %! L = table ([1;2;3], {'a';'b';'c'}, 'VariableNames', {'Key', 'L'}); %! R = table ([2;3;5], {'x';'y';'z'}, 'VariableNames', {'Key', 'R'}); %! [T, ixL, ixR] = outerjoin (L, R); %! assert_equal (T.Properties.VariableNames, {'Key_L', 'L', 'Key_R', 'R'}); %! assert_equal (height (T), 4); %! assert_equal (T.Key_L, [1;2;3;NaN]); %! assert_equal (T.Key_R, [NaN;2;3;5]); %! assert_equal (T.L, {'a';'b';'c';''}); %! assert_equal (T.R, {'';'x';'y';'z'}); %! assert_equal (ixL, [1;2;3;0]); %! assert_equal (ixR, [0;1;2;3]); ## Outer join drops row names %!test %! L = table ([1;2], {'a';'b'}, 'VariableNames', {'Key', 'L'}, ... %! 'RowNames', {'r1', 'r2'}); %! R = table ([2;1], {'x';'y'}, 'VariableNames', {'Key', 'R'}); %! T = outerjoin (L, R); %! assert_equal (isempty (T.Properties.RowNames), true); ## 'Type' 'left' keeps all left rows and only matching right rows %!test %! L = table ([1;2;3], {'a';'b';'c'}, 'VariableNames', {'Key', 'L'}); %! R = table ([2;3;5], {'x';'y';'z'}, 'VariableNames', {'Key', 'R'}); %! [T, ixL, ixR] = outerjoin (L, R, 'Type', 'left'); %! assert_equal (height (T), 3); %! assert_equal (T.Key_L, [1;2;3]); %! assert_equal (ixL, [1;2;3]); %! assert_equal (ixR, [0;1;2]); ## 'Type' 'right' keeps all right rows and only matching left rows %!test %! L = table ([1;2;3], {'a';'b';'c'}, 'VariableNames', {'Key', 'L'}); %! R = table ([2;3;5], {'x';'y';'z'}, 'VariableNames', {'Key', 'R'}); %! [T, ixL, ixR] = outerjoin (L, R, 'Type', 'right'); %! assert_equal (height (T), 3); %! assert_equal (T.Key_R, [2;3;5]); %! assert_equal (ixL, [2;3;0]); %! assert_equal (ixR, [1;2;3]); ## 'MergeKeys' merges the key pair into a single column %!test %! L = table ([1;2;3], {'a';'b';'c'}, 'VariableNames', {'Key', 'L'}); %! R = table ([2;3;5], {'x';'y';'z'}, 'VariableNames', {'Key', 'R'}); %! T = outerjoin (L, R, 'MergeKeys', true); %! assert_equal (T.Properties.VariableNames, {'Key', 'L', 'R'}); %! assert_equal (T.Key, [1;2;3;5]); %! assert_equal (T.L, {'a';'b';'c';''}); %! assert_equal (T.R, {'';'x';'y';'z'}); ## 'Keys' with multiple key variables, merged %!test %! L = table ([1;1;2], {'a';'b';'a'}, (1:3)', ... %! 'VariableNames', {'K1', 'K2', 'N'}); %! R = table ([1;2], {'b';'a'}, {'x';'y'}, ... %! 'VariableNames', {'K1', 'K2', 'M'}); %! T = outerjoin (L, R, 'Keys', {'K1', 'K2'}, 'MergeKeys', true); %! assert_equal (T.Properties.VariableNames, {'K1', 'K2', 'N', 'M'}); %! assert_equal (height (T), 3); %! assert_equal (T.K1, [1;1;2]); %! assert_equal (T.K2, {'a';'b';'a'}); ## 'MergeKeys' with differently-named keys uses the left key name %!test %! L = table ([1;2], (10:11)', 'VariableNames', {'KL', 'A'}); %! R = table ([2;3], {'x';'y'}, 'VariableNames', {'KR', 'B'}); %! T = outerjoin (L, R, 'LeftKeys', 'KL', 'RightKeys', 'KR', ... %! 'MergeKeys', true); %! assert_equal (T.Properties.VariableNames, {'KL_KR', 'A', 'B'}); %! assert_equal (T.KL_KR, [1;2;3]); %! assert_equal (T.A, [10;11;NaN]); %! assert_equal (T.B, {'';'x';'y'}); ## String variables in unmatched rows are filled with %!test %! L = table ([1;2], string ({'a';'b'}), 'VariableNames', {'K', 'S'}); %! R = table ([2;3], {'p';'q'}, 'VariableNames', {'K', 'R'}); %! T = outerjoin (L, R); %! assert_equal (ismissing (T.S), [false;false;true]); %! assert_equal (T.R, {'';'p';'q'}); ## Categorical variables in unmatched rows are filled with %!test %! L = table ([1;2], {'a';'b'}, 'VariableNames', {'K', 'L'}); %! R = table ([2;3], categorical ({'p';'q'}), 'VariableNames', {'K', 'C'}); %! T = outerjoin (L, R); %! assert_equal (ismissing (T.C), [true;false;false]); ## Cartesian product on repeated keys in a full outer join %!test %! L = table ([1;1;9], {'a';'b';'z'}, 'VariableNames', {'K', 'L'}); %! R = table ([1;1], {'x';'y'}, 'VariableNames', {'K', 'R'}); %! T = outerjoin (L, R); %! assert_equal (height (T), 5); %! assert_equal (T.L, {'a';'a';'b';'b';'z'}); %! assert_equal (T.R, {'x';'y';'x';'y';''}); ## An empty input table yields all-missing fill on that side %!test %! E = table (zeros (0, 1), cell (0, 1), 'VariableNames', {'K', 'L'}); %! R = table ([1;2], {'x';'y'}, 'VariableNames', {'K', 'R'}); %! T = outerjoin (E, R); %! assert_equal (height (T), 2); %! assert_equal (T.K_R, [1;2]); %! assert_equal (T.L, {'';''}); %! assert_equal (T.R, {'x';'y'}); ## 'LeftVariables'/'RightVariables' restrict the output columns %!test %! L = table ([1;2;3], (10:12)', {'p';'q';'r'}, ... %! 'VariableNames', {'K', 'A', 'C'}); %! R = table ([2;3], {'x';'y'}, 'VariableNames', {'K', 'B'}); %! T = outerjoin (L, R, 'LeftVariables', 'A', 'RightVariables', 'B', ... %! 'Type', 'left'); %! assert_equal (T.Properties.VariableNames, {'A', 'B'}); %! assert_equal (T.A, [10;11;12]); %! assert_equal (T.B, {'';'x';'y'}); ## 'outerjoin' on a duration key fills unmatched keys with missing %!test %! L = table (hours ([3;1;2]), {'a';'b';'c'}, 'VariableNames', {'K', 'L'}); %! R = table (hours ([2;5]), {'p';'q'}, 'VariableNames', {'K', 'R'}); %! [T, ixL, ixR] = outerjoin (L, R); %! assert_equal (hours (T.K_L), [1;2;3;NaN]); %! assert_equal (ismissing (T.K_L), [false;false;false;true]); %! assert_equal (T.R, {'';'p';'';'q'}); %! assert_equal (ixL, [2;3;1;0]); %! assert_equal (ixR, [0;1;0;2]); ## 'outerjoin' fills an unmatched duration variable with missing %!test %! L = table ([1;2], hours ([10;20]), 'VariableNames', {'K', 'D'}); %! R = table ([2;3], {'x';'y'}, 'VariableNames', {'K', 'R'}); %! T = outerjoin (L, R); %! assert_equal (ismissing (T.D), [false;false;true]); %! assert_equal (hours (T.D), [10;20;NaN]); ## 'outerjoin' fills an unmatched calendarDuration variable with missing %!test %! L = table ([1;2], calmonths ([5;6]), 'VariableNames', {'K', 'CD'}); %! R = table ([2;3], {'x';'y'}, 'VariableNames', {'K', 'R'}); %! T = outerjoin (L, R); %! assert_equal (ismissing (T.CD), [false;false;true]); ## 'outerjoin' fills an unmatched string variable with %!test %! L = table ([1;2], string ({'a';'b'}), 'VariableNames', {'K', 'S'}); %! R = table ([2;3], {'x';'y'}, 'VariableNames', {'K', 'R'}); %! T = outerjoin (L, R); %! assert_equal (ismissing (T.S), [false;false;true]); ## 'outerjoin' on a datetime key fills unmatched keys with NaT %!test %! L = table (datetime (2020, 1, [3;1;2]), {'a';'b';'c'}, ... %! 'VariableNames', {'K', 'L'}); %! R = table (datetime (2020, 1, [2;5]), {'p';'q'}, 'VariableNames', {'K', 'R'}); %! [T, ixL, ixR] = outerjoin (L, R); %! assert_equal (datevec (T.K_L)(:,3), [1;2;3;NaN]); %! assert_equal (ismissing (T.K_L), [false;false;false;true]); %! assert_equal (T.R, {'';'p';'';'q'}); %! assert_equal (ixL, [2;3;1;0]); %! assert_equal (ixR, [0;1;0;2]); ## 'outerjoin' fills an unmatched datetime variable with NaT %!test %! L = table ([1;2], datetime (2021, 1, [10;20]), 'VariableNames', {'K', 'D'}); %! R = table ([2;3], {'x';'y'}, 'VariableNames', {'K', 'R'}); %! T = outerjoin (L, R); %! assert_equal (ismissing (T.D), [false;false;true]); %!error ... %! outerjoin (table ([1;2])); %!error ... %! outerjoin (table ([1;2]), 5); %!error ... %! outerjoin (table ([1;2], 'VariableNames', {'K'}), ... %! table ([1;2], 'VariableNames', {'K'}), 'Type', 'bogus'); %!error ... %! outerjoin (table ([1;2], 'VariableNames', {'K'}), ... %! table ([1;2], 'VariableNames', {'K'}), 'MergeKeys', 3); %!error ... %! outerjoin (table (1), table (1), 'Keys', 1, 'LeftKeys', 1); %!error ... %! outerjoin (table (1, 'VariableNames', {'a'}), ... %! table (1, 'VariableNames', {'b'}), 'LeftKeys', 'a'); %!error ... %! outerjoin (table ([1;2], [1;2], 'VariableNames', {'a', 'b'}), ... %! table ([1;2], 'VariableNames', {'c'}), ... %! 'LeftKeys', {'a', 'b'}, 'RightKeys', 'c'); %!error ... %! outerjoin (table ((1:3)', 'VariableNames', {'AA'}), ... %! table ((1:3)', 'VariableNames', {'BB'})); %!error ... %! outerjoin (table ([1;2], 'VariableNames', {'K'}), ... %! table ([1;2], 'VariableNames', {'K'}), 'BadOption', 1); %!error ... %! outerjoin (table ([1;2], 'VariableNames', {'K'}), ... %! table ({'1';'2'}, 'VariableNames', {'K'})); ## Test 'union' method %!test %! A = table ([3;1;2;1], {'c';'a';'b';'a'}, 'VariableNames', {'N', 'S'}); %! B = table ([2;5;1], {'b';'e';'a'}, 'VariableNames', {'N', 'S'}); %! [U, ia, ib] = union (A, B); %! assert_equal (U.Properties.VariableNames, {'N', 'S'}); %! assert_equal (U.N, [1;2;3;5]); %! assert_equal (U.S, {'a';'b';'c';'e'}); %! assert_equal (ia, [2;3;1]); %! assert_equal (ib, 2); ## 'union' with 'stable' set order %!test %! A = table ([3;1;2;1], {'c';'a';'b';'a'}, 'VariableNames', {'N', 'S'}); %! B = table ([2;5;1], {'b';'e';'a'}, 'VariableNames', {'N', 'S'}); %! U = union (A, B, 'stable'); %! assert_equal (U.N, [3;1;2;5]); %! assert_equal (U.S, {'c';'a';'b';'e'}); ## 'union' ignores variable order (output keeps the first table's order) %!test %! A = table ([3;1], {'c';'a'}, 'VariableNames', {'N', 'S'}); %! B = table ({'a';'e'}, [1;5], 'VariableNames', {'S', 'N'}); %! U = union (A, B); %! assert_equal (U.Properties.VariableNames, {'N', 'S'}); %! assert_equal (U.N, [1;3;5]); %! assert_equal (U.S, {'a';'c';'e'}); ## 'union' drops row names (rows come from both tables) %!test %! A = table ([3;1], {'c';'a'}, 'VariableNames', {'N', 'S'}, ... %! 'RowNames', {'r1', 'r2'}); %! B = table ([2;5], {'b';'e'}, 'VariableNames', {'N', 'S'}, ... %! 'RowNames', {'q1', 'q2'}); %! U = union (A, B); %! assert_equal (U.N, [1;2;3;5]); %! assert_equal (isempty (U.Properties.RowNames), true); ## 'union' keeps the result's own sorted order when A and B rows interleave %!test %! A = table ([3;1], {'c';'a'}, 'VariableNames', {'N', 'S'}); %! B = table ([4;2], {'d';'b'}, 'VariableNames', {'N', 'S'}); %! U = union (A, B); %! assert_equal (U.N, [1;2;3;4]); %! assert_equal (U.S, {'a';'b';'c';'d'}); ## 'union' 'stable' keeps appearance order when A and B rows interleave %!test %! A = table ([3;1], {'c';'a'}, 'VariableNames', {'N', 'S'}); %! B = table ([4;2], {'d';'b'}, 'VariableNames', {'N', 'S'}); %! U = union (A, B, 'stable'); %! assert_equal (U.N, [3;1;4;2]); %! assert_equal (U.S, {'c';'a';'d';'b'}); ## Test 'intersect' method %!test %! A = table ([3;1;2;1], {'c';'a';'b';'a'}, 'VariableNames', {'N', 'S'}); %! B = table ([2;5;1], {'b';'e';'a'}, 'VariableNames', {'N', 'S'}); %! [I, ia, ib] = intersect (A, B); %! assert_equal (I.N, [1;2]); %! assert_equal (I.S, {'a';'b'}); %! assert_equal (ia, [2;3]); %! assert_equal (ib, [3;1]); ## 'intersect' with no common rows yields an empty table %!test %! A = table ([1;2], {'a';'b'}, 'VariableNames', {'N', 'S'}); %! B = table ([8;9], {'x';'y'}, 'VariableNames', {'N', 'S'}); %! I = intersect (A, B); %! assert_equal (height (I), 0); %! assert_equal (I.Properties.VariableNames, {'N', 'S'}); ## 'intersect' keeps the row names of the matched rows from the first table %!test %! A = table ([3;1;2;1], {'c';'a';'b';'a'}, 'VariableNames', {'N', 'S'}, ... %! 'RowNames', {'r1', 'r2', 'r3', 'r4'}); %! B = table ([2;5;1], {'b';'e';'a'}, 'VariableNames', {'N', 'S'}); %! I = intersect (A, B); %! assert_equal (I.N, [1;2]); %! assert_equal (I.Properties.RowNames, {'r2';'r3'}); ## Test 'setdiff' method %!test %! A = table ([3;1;2;1], {'c';'a';'b';'a'}, 'VariableNames', {'N', 'S'}); %! B = table ([2;5;1], {'b';'e';'a'}, 'VariableNames', {'N', 'S'}); %! [D, ia] = setdiff (A, B); %! assert_equal (D.N, 3); %! assert_equal (D.S, {'c'}); %! assert_equal (ia, 1); ## 'setdiff' with 'stable' set order %!test %! A = table ([5;3;5], {'e';'c';'e'}, 'VariableNames', {'N', 'S'}); %! B = table ([1], {'a'}, 'VariableNames', {'N', 'S'}); %! D = setdiff (A, B, 'stable'); %! assert_equal (D.N, [5;3]); ## 'setdiff' keeps the row names of the retained rows from the first table %!test %! A = table ([3;1;2;1], {'c';'a';'b';'a'}, 'VariableNames', {'N', 'S'}, ... %! 'RowNames', {'r1', 'r2', 'r3', 'r4'}); %! B = table ([2;5;1], {'b';'e';'a'}, 'VariableNames', {'N', 'S'}); %! D = setdiff (A, B); %! assert_equal (D.N, 3); %! assert_equal (D.Properties.RowNames, {'r1'}); ## Test 'setxor' method %!test %! A = table ([3;1;2;1], {'c';'a';'b';'a'}, 'VariableNames', {'N', 'S'}); %! B = table ([2;5;1], {'b';'e';'a'}, 'VariableNames', {'N', 'S'}); %! [X, ia, ib] = setxor (A, B); %! assert_equal (X.N, [3;5]); %! assert_equal (X.S, {'c';'e'}); %! assert_equal (ia, 1); %! assert_equal (ib, 2); ## 'setxor' drops row names (rows come from both tables) %!test %! A = table ([3;1;2;1], {'c';'a';'b';'a'}, 'VariableNames', {'N', 'S'}, ... %! 'RowNames', {'r1', 'r2', 'r3', 'r4'}); %! B = table ([2;5;1], {'b';'e';'a'}, 'VariableNames', {'N', 'S'}, ... %! 'RowNames', {'q1', 'q2', 'q3'}); %! X = setxor (A, B); %! assert_equal (X.N, [3;5]); %! assert_equal (isempty (X.Properties.RowNames), true); ## 'setxor' keeps the result's own sorted order when A and B rows interleave %!test %! A = table ([3;1], {'c';'a'}, 'VariableNames', {'N', 'S'}); %! B = table ([4;2], {'d';'b'}, 'VariableNames', {'N', 'S'}); %! X = setxor (A, B); %! assert_equal (X.N, [1;2;3;4]); %! assert_equal (X.S, {'a';'b';'c';'d'}); ## Test 'ismember' method %!test %! A = table ([3;1;2;1], {'c';'a';'b';'a'}, 'VariableNames', {'N', 'S'}); %! B = table ([2;5;1], {'b';'e';'a'}, 'VariableNames', {'N', 'S'}); %! [TF, ixB] = ismember (A, B); %! assert_equal (TF, [false;true;true;true]); %! assert_equal (ixB, [0;3;1;3]); ## Set operations work on text and numeric keys via the row proxy %!test %! A = table (categorical ({'a';'b';'c'}), [1;2;3], 'VariableNames', {'C', 'N'}); %! B = table (categorical ({'b';'d'}), [2;4], 'VariableNames', {'C', 'N'}); %! I = intersect (A, B); %! assert_equal (cellstr (I.C), {'b'}); %! assert_equal (I.N, 2); ## 'union' over string, categorical, duration and calendarDuration variables %!test %! A = table (string ({'a';'b';'c'}), categorical ({'x';'y';'z'}), ... %! hours ([1;2;3]), calmonths ([1;2;3]), ... %! 'VariableNames', {'S', 'C', 'D', 'M'}); %! B = table (string ({'b';'d'}), categorical ({'y';'w'}), hours ([2;4]), ... %! calmonths ([2;4]), 'VariableNames', {'S', 'C', 'D', 'M'}); %! U = union (A, B); %! assert_equal (cellstr (U.S), {'a';'b';'c';'d'}); %! assert_equal (cellstr (U.C), {'x';'y';'z';'w'}); %! assert_equal (hours (U.D), [1;2;3;4]); %! assert_equal (calmonths (U.M), [1;2;3;4]); ## 'intersect' over string, categorical, duration and calendarDuration variables %!test %! A = table (string ({'a';'b';'c'}), categorical ({'x';'y';'z'}), ... %! hours ([1;2;3]), calmonths ([1;2;3]), ... %! 'VariableNames', {'S', 'C', 'D', 'M'}); %! B = table (string ({'b';'d'}), categorical ({'y';'w'}), hours ([2;4]), ... %! calmonths ([2;4]), 'VariableNames', {'S', 'C', 'D', 'M'}); %! I = intersect (A, B); %! assert_equal (cellstr (I.S), {'b'}); %! assert_equal (cellstr (I.C), {'y'}); %! assert_equal (hours (I.D), 2); %! assert_equal (calmonths (I.M), 2); ## 'setdiff' over string, categorical, duration and calendarDuration variables %!test %! A = table (string ({'a';'b';'c'}), categorical ({'x';'y';'z'}), ... %! hours ([1;2;3]), calmonths ([1;2;3]), ... %! 'VariableNames', {'S', 'C', 'D', 'M'}); %! B = table (string ({'b';'d'}), categorical ({'y';'w'}), hours ([2;4]), ... %! calmonths ([2;4]), 'VariableNames', {'S', 'C', 'D', 'M'}); %! D = setdiff (A, B); %! assert_equal (cellstr (D.S), {'a';'c'}); %! assert_equal (hours (D.D), [1;3]); ## 'setxor' over string, categorical, duration and calendarDuration variables %!test %! A = table (string ({'a';'b';'c'}), categorical ({'x';'y';'z'}), ... %! hours ([1;2;3]), calmonths ([1;2;3]), ... %! 'VariableNames', {'S', 'C', 'D', 'M'}); %! B = table (string ({'b';'d'}), categorical ({'y';'w'}), hours ([2;4]), ... %! calmonths ([2;4]), 'VariableNames', {'S', 'C', 'D', 'M'}); %! X = setxor (A, B); %! assert_equal (cellstr (X.S), {'a';'c';'d'}); %! assert_equal (hours (X.D), [1;3;4]); ## 'ismember' over string, categorical, duration and calendarDuration variables %!test %! A = table (string ({'a';'b';'c'}), categorical ({'x';'y';'z'}), ... %! hours ([1;2;3]), calmonths ([1;2;3]), ... %! 'VariableNames', {'S', 'C', 'D', 'M'}); %! B = table (string ({'b';'d'}), categorical ({'y';'w'}), hours ([2;4]), ... %! calmonths ([2;4]), 'VariableNames', {'S', 'C', 'D', 'M'}); %! [TF, ixB] = ismember (A, B); %! assert_equal (TF, [false;true;false]); %! assert_equal (ixB, [0;1;0]); ## Set operations over a datetime key variable (rowProxies datetime path) %!test %! A = table (datetime (2020, 1, [3;1;2]), {'c';'a';'b'}, ... %! 'VariableNames', {'D', 'S'}); %! B = table (datetime (2020, 1, [2;5]), {'b';'e'}, 'VariableNames', {'D', 'S'}); %! U = union (A, B); %! assert_equal (datevec (U.D)(:,3), [1;2;3;5]); %! I = intersect (A, B); %! assert_equal (I.S, {'b'}); %! D = setdiff (A, B); %! assert_equal (D.S, {'a';'c'}); %! X = setxor (A, B); %! assert_equal (X.S, {'a';'c';'e'}); %! [TF, ixB] = ismember (A, B); %! assert_equal (TF, [false;false;true]); %! assert_equal (ixB, [0;0;1]); %!error ... %! union (table ([1;2])); %!error ... %! union (table ([1;2]), 5); %!error ... %! union (table ([1;2], 'VariableNames', {'N'}), ... %! table ([1;2], 'VariableNames', {'Z'})); %!error ... %! union (table ([1;2], 'VariableNames', {'N'}), ... %! table ([1;2], 'VariableNames', {'N'}), 'bogus'); %!error ... %! union (table ([1;2], 'VariableNames', {'N'}), ... %! table ([1;2], 'VariableNames', {'N'}), 'sorted', 'extra'); %!error ... %! intersect (table ([1;2])); %!error ... %! intersect (table ([1;2]), 5); %!error ... %! intersect (table ([1;2], 'VariableNames', {'N'}), ... %! table ([1;2], 'VariableNames', {'Z'})); %!error ... %! intersect (table ([1;2], 'VariableNames', {'N'}), ... %! table ([1;2], 'VariableNames', {'N'}), 'bogus'); %!error ... %! intersect (table ([1;2], 'VariableNames', {'N'}), ... %! table ([1;2], 'VariableNames', {'N'}), 'sorted', 'extra'); %!error ... %! setdiff (table ([1;2])); %!error ... %! setdiff (table ([1;2]), 5); %!error ... %! setdiff (table ([1;2], 'VariableNames', {'N'}), ... %! table ([1;2], 'VariableNames', {'Z'})); %!error ... %! setdiff (table ([1;2], 'VariableNames', {'N'}), ... %! table ([1;2], 'VariableNames', {'N'}), 'bogus'); %!error ... %! setdiff (table ([1;2], 'VariableNames', {'N'}), ... %! table ([1;2], 'VariableNames', {'N'}), 'sorted', 'extra'); %!error ... %! setxor (table ([1;2])); %!error ... %! setxor (table ([1;2]), 5); %!error ... %! setxor (table ([1;2], 'VariableNames', {'N'}), ... %! table ([1;2], 'VariableNames', {'Z'})); %!error ... %! setxor (table ([1;2], 'VariableNames', {'N'}), ... %! table ([1;2], 'VariableNames', {'N'}), 'bogus'); %!error ... %! setxor (table ([1;2], 'VariableNames', {'N'}), ... %! table ([1;2], 'VariableNames', {'N'}), 'sorted', 'extra'); %!error ... %! ismember (table ([1;2])); %!error ... %! ismember (table ([1;2]), 5); %!error ... %! ismember (table ([1;2], 'VariableNames', {'N'}), ... %! table ([1;2], 'VariableNames', {'Z'})); ################################################################################ ## ** Missing Values ** ## ################################################################################ ## Available Methods ## ## ## ## 'anymissing' 'ismissing' 'rmmissing' 'fillmissing' ## ## 'standardizeMissing' ## ## ## ################################################################################ ## Test 'anymissing' method %!test %! T = table (categorical ({'1';'2';'';'4'})); %! assert_equal (anymissing (T), true); %!test %! T = table (categorical ({'1';'2';'3';'4'})); %! assert_equal (anymissing (T), false); %!test %! T = table (calyears ([1; 2; NaN; 4])); %! assert_equal (anymissing (T), true); %!test %! T = table (calyears ([1; 2; 3; 4])); %! assert_equal (anymissing (T), false); %!test %! T = table (days ([1; 2; NaN; 4])); %! assert_equal (anymissing (T), true); %!test %! T = table (days ([1; 2; 3; 4])); %! assert_equal (anymissing (T), false); %!test %! T = table (string ([1; 2; NaN; 4])); %! assert_equal (anymissing (T), true); %!test %! T = table (string ([1; 2; 3; 4])); %! assert_equal (anymissing (T), false); %!test %! T = table ({'1'; '2'; ''; '4'}); %! assert_equal (anymissing (T), true); %!test %! T = table ({'1'; '2'; 'NaN'; '4'}); %! assert_equal (anymissing (T), false); %!test %! T = table (['1'; '2'; ' '; '4']); %! assert_equal (anymissing (T), true); %!test %! T = table (['1'; '2'; 'NaN'; '4']); %! assert_equal (anymissing (T), false); %!test %! T = table (['1'; '2'; 'N'; '4']); %! assert_equal (anymissing (T), false); ## Test 'ismissing' method %!test %! TF = ismissing (table ([' '; ' 2 '; 'NaN'; '4 ']), ' 2'); %! assert_equal (TF, [false; true; false; false]); %!test %! TF = ismissing (table ([' '; ' 2 '; 'NaN'; '4 ']), '2'); %! assert_equal (TF, [false; false; false; false]); %!test %! TF = ismissing (table ([' '; '2 '; 'NaN'; '4 ']), '2'); %! assert_equal (TF, [false; true; false; false]); %!test %! TF = ismissing (table ([' '; ' 2 '; 'NaN'; '4 '])); %! assert_equal (TF, [true; false; false; false]); %!test %! TF = ismissing (table ({'1'; '2'; ''; '4'})); %! assert_equal (TF, [false; false; true; false]); %!test %! TF = ismissing (table ({'1'; '2'; ''; '4'}), 'OutputFormat', 'tabular'); %! assert_equal (class (TF), 'table'); %! assert_equal (TF.Var1, [false; false; true; false]); %!test %! TF = ismissing (table ({'1'; '2'; ''; '4'}), {'4'}, 'OutputFormat', 'tabular'); %! assert_equal (class (TF), 'table'); %! assert_equal (TF.Var1, [false; false; false; true]); %!test %! TF = ismissing (table ({'1'; '2'; ''; '4'}, string ([1; 2; 3; 4])), {'4', '3'}, 'OutputFormat', 'tabular'); %! assert_equal (class (TF), 'table'); %! assert_equal (TF.Var1, [false; false; false; true]); %! assert_equal (TF.Var2, [false; false; true; true]); %!test %! TF = ismissing (table ({'1'; '2'; ''; '4'}, string ([1; 2; 3; 4])), {'4', '3'}); %! assert_equal (class (TF), 'logical'); %! assert_equal (TF, [false, false; false, false; false, true; true, true]); %!test %! TF = ismissing (table (days ([1; 2; NaN; 4]), calweeks ([1; 2; NaN; 4]))); %! assert_equal (TF, [false, false; false, false; true, true; false, false]); %!test # a scalar duration indicator matches a duration variable %! TF = ismissing (table (hours ([1; 2; 3]), 'VariableNames', {'d'}), hours (2)); %! assert_equal (TF, [false; true; false]); %!test # a NaN indicator also flags genuinely missing duration entries %! TF = ismissing (table (hours ([1; NaN; 3]), 'VariableNames', {'d'}), NaN); %! assert_equal (TF, [false; true; false]); ## Test 'rmmissing' method %!test # removes every row that contains a missing value %! T = table ([1; NaN; 3], {'a'; 'b'; ''}, 'VariableNames', {'x', 'y'}); %! R = rmmissing (T); %! assert_equal (size (R), [1, 2]); %! assert_equal (R.x, 1); %! assert_equal (R.y, {'a'}); %!test # the second output is the removed-row mask %! T = table ([1; NaN; 3], {'a'; 'b'; ''}, 'VariableNames', {'x', 'y'}); %! [~, TF] = rmmissing (T); %! assert_equal (TF, [false; true; true]); %!test # a table with no missing values is returned unchanged %! T = table ([1; 2; 3], 'VariableNames', {'x'}); %! [R, TF] = rmmissing (T); %! assert_equal (isequal (R, T), true); %! assert_equal (TF, [false; false; false]); %!test # 'DataVariables' restricts which variables are inspected %! T = table ([1; NaN; 3], {'a'; 'b'; ''}, 'VariableNames', {'x', 'y'}); %! [R, TF] = rmmissing (T, 'DataVariables', 'x'); %! assert_equal (TF, [false; true; false]); %! assert_equal (R.x, [1; 3]); %!test # 'MinNumMissing' needs at least N missing values in a row %! T = table ([1; NaN; NaN], {'a'; 'b'; ''}, 'VariableNames', {'x', 'y'}); %! [R, TF] = rmmissing (T, 'MinNumMissing', 2); %! assert_equal (TF, [false; false; true]); %! assert_equal (height (R), 2); %!test # 'MissingLocations' as a logical matrix overrides the derived mask %! T = table ([1; 2; 3], [4; 5; 6], 'VariableNames', {'a', 'b'}); %! [R, TF] = rmmissing (T, 'MissingLocations', logical ([0, 0; 1, 0; 0, 1])); %! assert_equal (TF, [false; true; true]); %! assert_equal (R.a, 1); %!test # 'MissingLocations' as a table of logicals overrides the derived mask %! T = table ([1; 2; 3], [4; 5; 6], 'VariableNames', {'a', 'b'}); %! ML = table (logical ([0; 1; 0]), logical ([0; 0; 1]), ... %! 'VariableNames', {'a', 'b'}); %! [R, TF] = rmmissing (T, 'MissingLocations', ML); %! assert_equal (TF, [false; true; true]); %! assert_equal (R.a, 1); %!test # 'DataVariables' with 'MissingLocations' inspects only the named vars %! T = table ([1; 2; 3], [4; 5; 6], 'VariableNames', {'a', 'b'}); %! ML = table (logical ([0; 1; 0]), 'VariableNames', {'a'}); %! [R, TF] = rmmissing (T, 'DataVariables', 'a', 'MissingLocations', ML); %! assert_equal (TF, [false; true; false]); %! assert_equal (R.b, [4; 6]); ## Test input validation for 'rmmissing' method %!error ... %! rmmissing (table ([1; NaN]), 'MinNumMissing', 1.5); %!error ... %! rmmissing (table ([1; NaN], 'VariableNames', {'x'}), 'DataVariables', 'zzz'); %!error ... %! rmmissing (table ([1; 2; 3], 'VariableNames', {'a'}), ... %! 'MissingLocations', table ([1; 2; 3], 'VariableNames', {'a'})); %!error ... %! rmmissing (table ([1; 2; 3], 'VariableNames', {'a'}), ... %! 'MissingLocations', table (logical ([0; 1]), 'VariableNames', {'a'})); %!error ... %! rmmissing (table ([1; 2; 3], 'VariableNames', {'a'}), ... %! 'MissingLocations', table (logical ([0; 1; 0]), 'VariableNames', {'b'})); ## Test 'fillmissing' method -- 'constant' method %!test # a scalar fill value is broadcast to the targeted numeric variable %! T = table ([1; NaN; 3; NaN], 'VariableNames', {'x'}); %! [R, TF] = fillmissing (T, 'constant', 0); %! assert_equal (R.x, [1; 0; 3; 0]); %! assert_equal (TF, [false; true; false; true]); %!test # a cell array gives one fill value per variable %! T = table ([1; NaN; 3], {'a'; ''; 'c'}, 'VariableNames', {'x', 'y'}); %! R = fillmissing (T, 'constant', {0, 'z'}); %! assert_equal (R.x, [1; 0; 3]); %! assert_equal (R.y, {'a'; 'z'; 'c'}); %!test # a vector gives one fill value per variable %! T = table ([1; NaN], [NaN; 4], 'VariableNames', {'a', 'b'}); %! R = fillmissing (T, 'constant', [7, 9]); %! assert_equal (R.a, [1; 7]); %! assert_equal (R.b, [9; 4]); %!test # 'constant' fills a string variable %! s = string ({'a'; 'b'; 'c'}); %! s(2) = string (missing); %! R = fillmissing (table (s, 'VariableNames', {'s'}), 'constant', ... %! string ('Z')); %! assert_equal (cellstr (R.s), {'a'; 'Z'; 'c'}); %!test # 'constant' fills a categorical variable %! c = categorical ({'p'; ''; 'r'}); %! R = fillmissing (table (c, 'VariableNames', {'c'}), 'constant', ... %! categorical ({'q'})); %! assert_equal (cellstr (R.c), {'p'; 'q'; 'r'}); %!test # 'constant' fills a duration variable %! d = hours ([1; NaN; 3]); %! R = fillmissing (table (d, 'VariableNames', {'d'}), 'constant', hours (9)); %! assert_equal (hours (R.d), [1; 9; 3]); %!test # 'constant' fills a datetime variable %! t = datetime (2020, 1, [1; 1; 3]); %! t(2) = NaT; %! R = fillmissing (table (t, 'VariableNames', {'t'}), 'constant', ... %! datetime (2020, 1, 2)); %! assert_equal (datevec (R.t)(:,3), [1; 2; 3]); %!test # 'DataVariables' restricts the fill to a subset of variables %! T = table ([1; NaN], [NaN; 4], 'VariableNames', {'a', 'b'}); %! [R, TF] = fillmissing (T, 'constant', 0, 'DataVariables', 'a'); %! assert_equal (R.a, [1; 0]); %! assert_equal (R.b, [NaN; 4]); %! assert_equal (TF, [false, false; true, false]); ## Test 'fillmissing' method -- 'previous'/'next'/'nearest' methods %!test # 'previous' carries the last non-missing value forward %! T = table ([1; NaN; NaN; 4], 'VariableNames', {'x'}); %! [R, TF] = fillmissing (T, 'previous'); %! assert_equal (R.x, [1; 1; 1; 4]); %! assert_equal (TF, [false; true; true; false]); %!test # 'next' carries the next non-missing value backward %! T = table ([1; NaN; NaN; 4], 'VariableNames', {'x'}); %! R = fillmissing (T, 'next'); %! assert_equal (R.x, [1; 4; 4; 4]); %!test # 'nearest' uses the closest value; ties favor the next one %! T = table ([1; NaN; NaN; NaN; 5], 'VariableNames', {'x'}); %! R = fillmissing (T, 'nearest'); %! assert_equal (R.x, [1; 1; 5; 5; 5]); %!test # leading missing entries are left missing by 'previous' %! T = table ([NaN; NaN; 3], 'VariableNames', {'x'}); %! [R, TF] = fillmissing (T, 'previous'); %! assert_equal (R.x, [NaN; NaN; 3]); %! assert_equal (TF, [false; false; false]); %!test # the neighbor methods work on a string variable %! s = string ({'a'; 'b'; 'c'; 'd'}); %! s(2) = string (missing); %! s(3) = string (missing); %! R = fillmissing (table (s, 'VariableNames', {'s'}), 'previous'); %! assert_equal (cellstr (R.s), {'a'; 'a'; 'a'; 'd'}); %!test # the neighbor methods work on a categorical variable %! c = categorical ({'p'; ''; ''; 's'}); %! R = fillmissing (table (c, 'VariableNames', {'c'}), 'next'); %! assert_equal (cellstr (R.c), {'p'; 's'; 's'; 's'}); %!test # the neighbor methods work on a datetime variable %! t = datetime (2020, 1, [1; 1; 4]); %! t(2) = NaT; %! R = fillmissing (table (t, 'VariableNames', {'t'}), 'next'); %! assert_equal (datevec (R.t)(:,3), [1; 4; 4]); %!test # a multicolumn variable is filled column by column %! T = table ([1, 10; NaN, NaN; 3, 30]); %! [R, TF] = fillmissing (T, 'previous'); %! assert_equal (R.Var1, [1, 10; 1, 10; 3, 30]); %! assert_equal (TF, [false; true; false]); %!test # the method name is case-insensitive %! T = table ([1; NaN; 3], 'VariableNames', {'x'}); %! R = fillmissing (T, 'Previous'); %! assert_equal (R.x, [1; 1; 3]); ## Test 'fillmissing' method -- 'linear' method %!test # interior gaps are linearly interpolated %! T = table ([1; NaN; NaN; 4], 'VariableNames', {'x'}); %! [R, TF] = fillmissing (T, 'linear'); %! assert_equal (R.x, [1; 2; 3; 4]); %! assert_equal (TF, [false; true; true; false]); %!test # 'EndValues' defaults to 'extrap' (linear extrapolation) %! T = table ([NaN; 2; 4; NaN], 'VariableNames', {'x'}); %! R = fillmissing (T, 'linear'); %! assert_equal (R.x, [0; 2; 4; 6]); %!test # 'EndValues' 'none' leaves leading and trailing gaps missing %! T = table ([NaN; 2; 4; NaN], 'VariableNames', {'x'}); %! R = fillmissing (T, 'linear', 'EndValues', 'none'); %! assert_equal (R.x, [NaN; 2; 4; NaN]); %!test # 'EndValues' accepts a numeric constant for the end gaps %! T = table ([NaN; 2; 4; NaN], 'VariableNames', {'x'}); %! R = fillmissing (T, 'linear', 'EndValues', 0); %! assert_equal (R.x, [0; 2; 4; 0]); %!test # 'linear' leaves non-numeric variables unchanged %! T = table ([1; NaN; 3], {'a'; ''; 'c'}, 'VariableNames', {'x', 'y'}); %! R = fillmissing (T, 'linear'); %! assert_equal (R.x, [1; 2; 3]); %! assert_equal (R.y, {'a'; ''; 'c'}); %!test # fewer than two anchors cannot be interpolated %! T = table ([NaN; 5; NaN], 'VariableNames', {'x'}); %! R = fillmissing (T, 'linear', 'EndValues', 'none'); %! assert_equal (R.x, [NaN; 5; NaN]); %!test # a table without missing values is returned unchanged %! T = table ([1; 2; 3], 'VariableNames', {'x'}); %! [R, TF] = fillmissing (T, 'previous'); %! assert_equal (isequal (R, T), true); %! assert_equal (TF, [false; false; false]); ## Test input validation for 'fillmissing' method %!error ... %! fillmissing (table (1)); %!error ... %! fillmissing (table (1), 5); %!error ... %! fillmissing (table (1), 'bogus'); %!error ... %! fillmissing (table (1), 'movmean', 3); %!error ... %! fillmissing (table (1), 'constant'); %!error ... %! fillmissing (table ([1; NaN], 'VariableNames', {'x'}), 'constant', {1, 2}); %!error ... %! fillmissing (table ([1; NaN], 'VariableNames', {'x'}), 'constant', [1, 2, 3]); %!error ... %! fillmissing (table ([1; NaN], {'a'; ''}, 'VariableNames', {'x', 'y'}), 'constant', 0); %!error ... %! fillmissing (table ([1; NaN], 'VariableNames', {'x'}), 'previous', 'DataVariables', 'zzz'); %!error ... %! fillmissing (table ([NaN; 2; 3], 'VariableNames', {'x'}), 'linear', 'EndValues', 'bogus'); %!error ... %! fillmissing (table (1), 'previous', 'ReplaceValues', false); ## Test 'standardizeMissing' method %!test # a numeric scalar indicator becomes NaN in numeric variables %! T = table ([1; 99; 3], 'VariableNames', {'x'}); %! R = standardizeMissing (T, 99); %! assert_equal (R.x, [1; NaN; 3]); %!test # a numeric vector indicator matches any of its values %! T = table ([1; -99; 3; 0], 'VariableNames', {'x'}); %! R = standardizeMissing (T, [-99, 0]); %! assert_equal (R.x, [1; NaN; 3; NaN]); %!test # a text indicator standardizes a cellstr variable to '' %! T = table ({'a'; 'NA'; 'c'}, 'VariableNames', {'y'}); %! R = standardizeMissing (T, 'NA'); %! assert_equal (R.y, {'a'; ''; 'c'}); %! assert_equal (cellfun (@isempty, R.y), [false; true; false]); %!test # a text indicator standardizes a string variable to %! T = table (string ({'a'; 'NA'; 'c'}), 'VariableNames', {'s'}); %! R = standardizeMissing (T, 'NA'); %! assert_equal (ismissing (R.s), [false; true; false]); %!test # a text indicator standardizes a categorical variable to %! T = table (categorical ({'a'; 'NA'; 'c'}), 'VariableNames', {'c'}); %! R = standardizeMissing (T, {'NA'}); %! assert_equal (ismissing (R.c), [false; true; false]); %!test # a cell indicator mixes numeric and text across a mixed table %! T = table ([1; 5; 99], {'a'; 'x'; 'c'}, 'VariableNames', {'n', 't'}); %! R = standardizeMissing (T, {99, 'x'}); %! assert_equal (R.n, [1; 5; NaN]); %! assert_equal (R.t, {'a'; ''; 'c'}); %!test # numeric and text indicators only touch compatible variables %! T = table ([1; 5; 99], {'a'; '99'; 'c'}, 'VariableNames', {'n', 't'}); %! R = standardizeMissing (T, 99); %! assert_equal (R.n, [1; 5; NaN]); %! assert_equal (R.t, {'a'; '99'; 'c'}); %!test # 'DataVariables' restricts the standardization to a subset %! T = table ([1; 99], [99; 2], 'VariableNames', {'a', 'b'}); %! R = standardizeMissing (T, 99, 'DataVariables', 'a'); %! assert_equal (R.a, [1; NaN]); %! assert_equal (R.b, [99; 2]); %!test # integer variables have no standard missing value; pass through %! T = table (int32 ([1; 99; 3]), 'VariableNames', {'x'}); %! R = standardizeMissing (T, 99); %! assert_equal (R.x, int32 ([1; 99; 3])); ## Test input validation for 'standardizeMissing' method %!error ... %! standardizeMissing (table (1)); %!error ... %! standardizeMissing (table (1), struct ('a', 1)); %!error ... %! standardizeMissing (table (1), {struct('a', 1)}); %!error ... %! standardizeMissing (table ([1; 99], 'VariableNames', {'x'}), 99, 'DataVariables', 'zzz'); ################################################################################ ## ** Apply Functions to Table Contents ** ## ################################################################################ ## Available Methods ## ## ## ## 'pivot' 'groupcounts' 'groupfilter' 'groupsummary' ## ## 'grouptransform' 'findgroups' 'splitapply' 'rowfun' ## ## 'varfun' ## ## ## ################################################################################ ## 'findgroups' labels rows by sorted unique value; group k is the k-th value %!assert_equal (findgroups (table ([1; 3; 1; 2])), [1; 3; 1; 2]) %!assert_equal (findgroups (table ([30; 10; 10; 20])), [3; 1; 1; 2]) %!assert_equal (findgroups (table ({'b'; 'a'; 'b'; 'c'})), [2; 1; 2; 3]) %!assert_equal (findgroups (table (logical ([1; 0; 1; 0]))), [2; 1; 2; 1]) %!test %! ## Missing values map to NaN in G (numeric, text, datetime) %! assert_equal (findgroups (table ([1; NaN; 2; 1])), [1; NaN; 2; 1]); %! assert_equal (findgroups (table ({'b'; ''; 'a'})), [2; NaN; 1]); %! assert_equal (findgroups (table ([datetime(2026,1,2); NaT; ... %! datetime(2026,1,1)])), [2; NaN; 1]); %!test %! ## Multiple variables group by unique sorted combinations %! T = table ([1; 1; 2; 1], {'b'; 'a'; 'a'; 'b'}); %! assert_equal (findgroups (T), [2; 1; 3; 2]); %!test %! ## Second output is the sorted-unique identifier table %! [G, TID] = findgroups (table ([30; 10; 10; 20])); %! assert_equal (G, [3; 1; 1; 2]); %! assert_equal (TID.Var1, [10; 20; 30]); %! assert_equal (TID.Properties.VariableNames, {'Var1'}); %!error findgroups (table ()) ## 'splitapply' splits rows by group number and concatenates per-group results %!assert_equal (splitapply (@(x) mean (x), table ([1; 2; 3; 4]), [1; 1; 2; 2]), ... %! [1.5; 3.5]) %!assert_equal (splitapply (@(x) sum (x), table ([1; 2; 3]), [2; 1; 2]), [2; 4]) %!test %! ## Each table variable is passed to FUNC as a separate argument %! T = table ([1; 2; 3; 4], [10; 20; 30; 40]); %! y = splitapply (@(a, b) sum (a) + sum (b), T, [1; 1; 2; 2]); %! assert_equal (y, [33; 77]); %!test %! ## Multiple outputs are concatenated separately %! [lo, hi] = splitapply (@(x) deal (min (x), max (x)), ... %! table ([4; 1; 3; 2]), [1; 1; 2; 2]); %! assert_equal (lo, [1; 2]); %! assert_equal (hi, [4; 3]); %!test %! ## NaN group numbers omit the corresponding rows %! y = splitapply (@(x) mean (x), table ([1; 2; 99; 4]), [1; 1; NaN; 2]); %! assert_equal (y, [1.5; 4]); %!error splitapply (@mean, table ([1; 2]), [1; 1; 1]) %!error splitapply (@mean, table ([1; 2]), [1; 3]) %!error splitapply (1, table ([1; 2]), [1; 2]) ## 'varfun' applies FUNC to each variable; output names are FUNC_VARNAME %!test %! T = table ([1; 2; 3], [10; 20; 30], 'VariableNames', {'a', 'b'}); %! B = varfun (@sum, T); %! assert_equal (B.Properties.VariableNames, {'sum_a', 'sum_b'}); %! assert_equal (B.sum_a, 6); %! assert_equal (B.sum_b, 60); %!test %! ## Anonymous functions are named with the 'Fun' prefix %! T = table ([1; 2; 3], [10; 20; 30], 'VariableNames', {'a', 'b'}); %! B = varfun (@(x) max (x), T); %! assert_equal (B.Properties.VariableNames, {'Fun_a', 'Fun_b'}); %! assert_equal (B.Fun_b, 30); %!test %! ## 'OutputFormat' 'uniform' concatenates scalar results into an array %! T = table ([1; 2; 3], [10; 20; 30], 'VariableNames', {'a', 'b'}); %! assert_equal (varfun (@mean, T, 'OutputFormat', 'uniform'), [2, 20]); %!test %! ## 'OutputFormat' 'cell' returns the results in a cell array %! T = table ([1; 2; 3], [10; 20; 30], 'VariableNames', {'a', 'b'}); %! assert_equal (varfun (@sum, T, 'OutputFormat', 'cell'), {6, 60}); %!test %! ## 'InputVariables' restricts the variables passed to FUNC %! T = table ([1; 2; 3], [10; 20; 30], 'VariableNames', {'a', 'b'}); %! B = varfun (@sum, T, 'InputVariables', 'a'); %! assert_equal (B.Properties.VariableNames, {'sum_a'}); %! assert_equal (B.sum_a, 6); %!test %! ## 'GroupingVariables' add grouping columns and a GroupCount column %! T = table ([1; 1; 2; 2], [10; 20; 30; 40], 'VariableNames', {'g', 'x'}); %! B = varfun (@sum, T, 'GroupingVariables', 'g'); %! assert_equal (B.Properties.VariableNames, {'g', 'GroupCount', 'sum_x'}); %! assert_equal (B.g, [1; 2]); %! assert_equal (B.GroupCount, [2; 2]); %! assert_equal (B.sum_x, [30; 70]); %!test %! ## Grouped 'uniform' output holds only the per-group results %! T = table ([1; 1; 2; 2], [10; 20; 30; 40], 'VariableNames', {'g', 'x'}); %! v = varfun (@sum, T, 'GroupingVariables', 'g', 'OutputFormat', 'uniform'); %! assert_equal (v, [30; 70]); %!test %! ## A grouped FUNC may return several rows per group; the grouping and %! ## GroupCount columns are replicated to match the stacked results %! T = table ([1; 1; 2; 2], [10; 20; 30; 40], 'VariableNames', {'g', 'x'}); %! B = varfun (@(x) x * 2, T, 'GroupingVariables', 'g'); %! assert_equal (B.Properties.VariableNames, {'g', 'GroupCount', 'Fun_x'}); %! assert_equal (B.g, [1; 1; 2; 2]); %! assert_equal (B.GroupCount, [2; 2; 2; 2]); %! assert_equal (B.Fun_x, [20; 40; 60; 80]); %!test %! ## Grouped 'cell' output holds one cell per group with the full result %! T = table ([1; 1; 2; 2], [10; 20; 30; 40], 'VariableNames', {'g', 'x'}); %! C = varfun (@(x) x * 2, T, 'GroupingVariables', 'g', 'OutputFormat', 'cell'); %! assert_equal (size (C), [2, 1]); %! assert_equal (C{1}, [20; 40]); %! assert_equal (C{2}, [60; 80]); %!error varfun (1, table ([1; 2])) %!error ... %! varfun (@sum, table ([1; 2]), 'OutputFormat', 'bogus') %!error ... %! varfun (@sum, table ([1; 2], 'VariableNames', {'g'}), 'GroupingVariables', 'g') %!error ... %! varfun (@(x) x, table ([1; 2; 3]), 'OutputFormat', 'uniform') ## 'rowfun' applies FUNC to each row; default output names are Var1, Var2, ... %!test %! T = table ([1; 2; 3], [10; 20; 30], 'VariableNames', {'a', 'b'}); %! B = rowfun (@(x, y) x + y, T); %! assert_equal (B.Properties.VariableNames, {'Var1'}); %! assert_equal (B.Var1, [11; 22; 33]); %!test %! ## Multiple outputs with 'OutputVariableNames' %! T = table ([1; 2; 3], [10; 20; 30], 'VariableNames', {'a', 'b'}); %! B = rowfun (@(x, y) deal (x + y, x * y), T, ... %! 'OutputVariableNames', {'add', 'mul'}); %! assert_equal (B.Properties.VariableNames, {'add', 'mul'}); %! assert_equal (B.add, [11; 22; 33]); %! assert_equal (B.mul, [10; 40; 90]); %!test %! ## 'SeparateInputs' false passes the row as a single concatenated argument %! T = table ([1; 2; 3], [10; 20; 30], 'VariableNames', {'a', 'b'}); %! B = rowfun (@(row) sum (row), T, 'SeparateInputs', false); %! assert_equal (B.Var1, [11; 22; 33]); %!test %! ## 'InputVariables' restricts and orders the variables passed to FUNC %! T = table ([1; 2; 3], [10; 20; 30], [100; 200; 300], ... %! 'VariableNames', {'a', 'b', 'c'}); %! B = rowfun (@(x, y) x + y, T, 'InputVariables', {'a', 'c'}); %! assert_equal (B.Var1, [101; 202; 303]); %!test %! ## 'ExtractCellContents' unwraps cell-valued variables before the call %! T = table ({1; 2; 3}, 'VariableNames', {'a'}); %! B = rowfun (@(x) x * 2, T, 'ExtractCellContents', true); %! assert_equal (B.Var1, [2; 4; 6]); %!test %! ## 'OutputFormat' 'uniform' and 'cell' %! T = table ([1; 2; 3], [10; 20; 30], 'VariableNames', {'a', 'b'}); %! assert_equal (rowfun (@(x, y) x + y, T, 'OutputFormat', 'uniform'), ... %! [11; 22; 33]); %! assert_equal (rowfun (@(x, y) x + y, T, 'OutputFormat', 'cell'), ... %! {11; 22; 33}); %!test %! ## Ungrouped output preserves the row names of the input table %! T = table ([1; 2], [10; 20], 'VariableNames', {'a', 'b'}, ... %! 'RowNames', {'r1', 'r2'}); %! B = rowfun (@(x, y) x + y, T); %! assert_equal (B.Properties.RowNames, T.Properties.RowNames); %!test %! ## 'GroupingVariables' apply FUNC per group with a GroupCount column; the %! ## default output name continues past the grouping and GroupCount columns %! T = table ([1; 1; 2; 2], [10; 20; 30; 40], 'VariableNames', {'g', 'x'}); %! B = rowfun (@(x) sum (x), T, 'GroupingVariables', 'g'); %! assert_equal (B.Properties.VariableNames, {'g', 'GroupCount', 'Var3'}); %! assert_equal (B.g, [1; 2]); %! assert_equal (B.GroupCount, [2; 2]); %! assert_equal (B.Var3, [30; 70]); %!test %! ## A grouped FUNC may return several rows per group; the grouping and %! ## GroupCount columns are replicated to match the stacked results %! T = table ([1; 1; 2; 2], [10; 20; 30; 40], [5; 6; 7; 8], ... %! 'VariableNames', {'g', 'x', 'y'}); %! B = rowfun (@(x, y) x + y, T, 'GroupingVariables', 'g'); %! assert_equal (B.Properties.VariableNames, {'g', 'GroupCount', 'Var3'}); %! assert_equal (B.g, [1; 1; 2; 2]); %! assert_equal (B.GroupCount, [2; 2; 2; 2]); %! assert_equal (B.Var3, [15; 26; 37; 48]); %!error rowfun (1, table ([1; 2])) %!error ... %! rowfun (@(x) x, table ([1; 2]), 'SeparateInputs', 'yes') %!error ... %! rowfun (@(x) x, table ([1; 2]), 'OutputVariableNames', {'p', 'q'}, 'NumOutputs', 1) ## 'groupsummary' with no method returns the grouping variables and GroupCount %!test %! T = table ([1; 1; 2; 2; 1], [10; 20; 30; 40; 50], 'VariableNames', {'g', 'x'}); %! G = groupsummary (T, 'g'); %! assert_equal (G.Properties.VariableNames, {'g', 'GroupCount'}); %! assert_equal (G.g, [1; 2]); %! assert_equal (G.GroupCount, [3; 2]); ## A named method appends a '_' column for each data variable %!test %! T = table ([1; 1; 2; 2; 1], [10; 20; 30; 40; 50], [1; 2; 3; 4; 5], ... %! 'VariableNames', {'g', 'x', 'y'}); %! G = groupsummary (T, 'g', 'sum'); %! assert_equal (G.Properties.VariableNames, {'g', 'GroupCount', 'sum_x', 'sum_y'}); %! assert_equal (G.GroupCount, [3; 2]); %! assert_equal (G.sum_x, [80; 70]); %! assert_equal (G.sum_y, [8; 7]); ## 'DataVars' restricts the variables the method is applied to %!test %! T = table ([1; 1; 2; 2; 1], [10; 20; 30; 40; 50], [1; 2; 3; 4; 5], ... %! 'VariableNames', {'g', 'x', 'y'}); %! G = groupsummary (T, 'g', 'mean', 'x'); %! assert_equal (G.Properties.VariableNames, {'g', 'GroupCount', 'mean_x'}); %! assert_equal (G.mean_x, [80/3; 35]); ## Several methods and data variables are ordered data variable first, then method %!test %! T = table ([1; 1; 2; 2; 1], [10; 20; 30; 40; 50], [1; 2; 3; 4; 5], ... %! 'VariableNames', {'g', 'x', 'y'}); %! G = groupsummary (T, 'g', {'mean', 'sum'}, {'x', 'y'}); %! assert_equal (G.Properties.VariableNames, ... %! {'g', 'GroupCount', 'mean_x', 'sum_x', 'mean_y', 'sum_y'}); %! assert_equal (G.mean_x, [80/3; 35]); %! assert_equal (G.sum_x, [80; 70]); %! assert_equal (G.mean_y, [8/3; 3.5]); %! assert_equal (G.sum_y, [8; 7]); ## Function handles are named 'fun' by their position among the methods %!test %! T = table ([1; 1; 2; 2; 1], [10; 20; 30; 40; 50], 'VariableNames', {'g', 'x'}); %! G = groupsummary (T, 'g', @std); %! assert_equal (G.Properties.VariableNames, {'g', 'GroupCount', 'fun1_x'}); %! assert_equal (G.fun1_x, [std([10; 20; 50]); std([30; 40])]); ## Multiple function handles keep their 'fun' index across data variables %!test %! T = table ([1; 1; 2; 2; 1], [10; 20; 30; 40; 50], [1; 2; 3; 4; 5], ... %! 'VariableNames', {'g', 'x', 'y'}); %! G = groupsummary (T, 'g', {@std, @(v) median(v)}, {'x', 'y'}); %! assert_equal (G.Properties.VariableNames, ... %! {'g', 'GroupCount', 'fun1_x', 'fun2_x', 'fun1_y', 'fun2_y'}); %! assert_equal (G.fun2_x, [median([10; 20; 50]); median([30; 40])]); ## A function handle may return a row, producing a multi-column result variable %!test %! T = table ([1; 1; 2; 2; 1], [10; 20; 30; 40; 50], 'VariableNames', {'g', 'x'}); %! G = groupsummary (T, 'g', @(v) [min(v), max(v)]); %! assert_equal (G.fun1_x, [10, 50; 30, 40]); ## 'min', 'max', 'range', 'std', 'var', 'median', 'mode' spot checks %!test %! T = table ([1; 1; 2; 2; 1], [10; 20; 30; 40; 50], 'VariableNames', {'g', 'x'}); %! G = groupsummary (T, 'g', {'min', 'max', 'range', 'var', 'median', 'mode'}); %! assert_equal (G.min_x, [10; 30]); %! assert_equal (G.max_x, [50; 40]); %! assert_equal (G.range_x, [40; 10]); %! assert_equal (G.var_x, [var([10; 20; 50]); var([30; 40])]); %! assert_equal (G.median_x, [20; 35]); %! assert_equal (G.mode_x, [10; 30]); ## 'nnz' counts the nonzero values in the group, omitting NaN %!test %! T = table ([1; 1; 2; 2], [0; 2; 0; 0], 'VariableNames', {'g', 'x'}); %! G = groupsummary (T, 'g', 'nnz'); %! assert_equal (G.nnz_x, [1; 0]); ## Named methods omit NaN in the data; 'nummissing' counts them %!test %! T = table ([1; 1; 2; 2], [NaN; 20; NaN; NaN], 'VariableNames', {'g', 'x'}); %! G = groupsummary (T, 'g', {'mean', 'nummissing'}); %! assert_equal (G.mean_x, [20; NaN]); %! assert_equal (G.nummissing_x, [1; 2]); ## 'numunique' counts the unique non-missing values, for any data type %!test %! T = table ([1; 1; 2; 2], {'a'; 'a'; 'b'; ''}, 'VariableNames', {'g', 's'}); %! G = groupsummary (T, 'g', {'numunique', 'nummissing'}); %! assert_equal (G.numunique_s, [1; 1]); %! assert_equal (G.nummissing_s, [0; 1]); ## Missing values in a grouping variable form their own group, sorted last %!test %! T = table ([1; 1; 2; NaN; 2], [10; 20; 30; 40; 50], 'VariableNames', {'g', 'x'}); %! G = groupsummary (T, 'g', 'mean'); %! assert_equal (G.g, [1; 2; NaN]); %! assert_equal (G.GroupCount, [2; 2; 1]); %! assert_equal (G.mean_x, [15; 40; 40]); ## 'IncludeMissingGroups' false drops the rows with a missing grouping value %!test %! T = table ([1; 1; 2; NaN; 2], [10; 20; 30; 40; 50], 'VariableNames', {'g', 'x'}); %! G = groupsummary (T, 'g', 'mean', 'IncludeMissingGroups', false); %! assert_equal (G.g, [1; 2]); %! assert_equal (G.GroupCount, [2; 2]); %! assert_equal (G.mean_x, [15; 40]); ## Several grouping variables group by their unique combinations, missing last %!test %! T = table ([1; 1; 2; NaN; 2], {'b'; 'a'; 'a'; 'a'; 'b'}, [10; 20; 30; 40; 50], ... %! 'VariableNames', {'g1', 'g2', 'x'}); %! G = groupsummary (T, {'g1', 'g2'}, 'sum'); %! assert_equal (G.g1, [1; 1; 2; 2; NaN]); %! assert_equal (G.g2, {'a'; 'b'; 'a'; 'b'; 'a'}); %! assert_equal (G.GroupCount, [1; 1; 1; 1; 1]); %! assert_equal (G.sum_x, [20; 10; 30; 50; 40]); ## A categorical grouping variable keeps its type in the output %!test %! T = table (categorical ({'a'; 'b'; 'a'; 'c'}), [1; 2; 3; 4], ... %! 'VariableNames', {'g', 'x'}); %! G = groupsummary (T, 'g', 'sum'); %! assert_equal (iscategorical (G.g), true); %! assert_equal (cellstr (G.g), {'a'; 'b'; 'c'}); %! assert_equal (G.sum_x, [4; 2; 4]); ## An ordinal categorical grouping variable groups by category order %!test %! c = categorical ({'medium'; 'low'; 'high'; 'low'}, ... %! {'low', 'medium', 'high'}, 'Ordinal', true); %! T = table (c, [1; 2; 3; 4], 'VariableNames', {'g', 'x'}); %! G = groupsummary (T, 'g', 'sum'); %! assert_equal (cellstr (G.g), {'low'; 'medium'; 'high'}); %! assert_equal (G.sum_x, [6; 1; 3]); %!error ... %! groupsummary (table ([1; 2], [3; 4], 'VariableNames', {'g', 'x'}), 'g', 'bogus') %!error ... %! groupsummary (table ([1; 2], [3; 4], 'VariableNames', {'g', 'x'}), 'g', 'sum', 'x', 5) %!error ... %! groupsummary (table ([1; 2], 'VariableNames', {'g'}), false) %!error ... %! groupsummary (table ([1; 2], {'a'; 'b'}, 'VariableNames', {'g', 'x'}), 'g', 'mean') ## 'IncludeEmptyGroups' adds the unused categories of a categorical grouping ## variable as empty groups (GroupCount 0, sum 0, NaN for the rest) %!test %! c = categorical ({'a'; 'a'; 'b'}, {'a', 'b', 'c'}); %! T = table (c, [10; 20; 30], 'VariableNames', {'g', 'x'}); %! G = groupsummary (T, 'g', {'sum', 'mean', 'max', 'numunique'}, 'x', ... %! 'IncludeEmptyGroups', true); %! assert_equal (cellstr (G.g), {'a'; 'b'; 'c'}); %! assert_equal (G.GroupCount, [2; 1; 0]); %! assert_equal (G.sum_x, [30; 30; 0]); %! assert_equal (G.mean_x, [15; 30; NaN]); %! assert_equal (G.max_x, [20; 30; NaN]); %! assert_equal (G.numunique_x, [2; 1; 0]); ## A GROUPBINS edge vector groups a numeric variable by bin interval %!test %! T = table ([1; 3; 5; 7; 9; 11], (1:6)', 'VariableNames', {'g', 'x'}); %! G = groupsummary (T, 'g', [0 6 12], 'sum', 'x'); %! assert_equal (iscategorical (G.disc_g), true); %! assert_equal (cellstr (G.disc_g), {'[0, 6)'; '[6, 12]'}); %! assert_equal (G.GroupCount, [3; 3]); %! assert_equal (G.sum_x, [6; 15]); ## A GROUPBINS number of bins makes equal-width bins over the data range %!test %! T = table ([1; 2; 3; 4], (1:4)', 'VariableNames', {'g', 'x'}); %! G = groupsummary (T, 'g', 3, 'sum', 'x'); %! assert_equal (cellstr (G.disc_g), {'[1, 2)'; '[2, 3)'; '[3, 4]'}); %! assert_equal (G.GroupCount, [1; 1; 2]); ## 'IncludedEdge' 'right' makes the right bin edge the inclusive one %!test %! T = table ([0; 6; 12], (1:3)', 'VariableNames', {'g', 'x'}); %! G = groupsummary (T, 'g', [0 6 12], 'sum', 'x', 'IncludedEdge', 'right'); %! assert_equal (cellstr (G.disc_g), {'[0, 6]'; '(6, 12]'}); %! assert_equal (G.GroupCount, [2; 1]); ## Binning combined with 'IncludeEmptyGroups' keeps the empty bins %!test %! T = table ([1; 2; 3; 20], (1:4)', 'VariableNames', {'g', 'x'}); %! G = groupsummary (T, 'g', [0 5 10 25], 'sum', 'x', 'IncludeEmptyGroups', true); %! assert_equal (cellstr (G.disc_g), {'[0, 5)'; '[5, 10)'; '[10, 25]'}); %! assert_equal (G.GroupCount, [3; 0; 1]); %! assert_equal (G.sum_x, [6; 0; 4]); ## GROUPBINS time-unit 'month' bins a datetime by calendar month (month_) %!test %! t = datetime (2023, [1; 1; 2; 3; 3], [1; 2; 3; 4; 5]); %! T = table (t, (1:5)', 'VariableNames', {'t', 'x'}); %! G = groupsummary (T, 't', 'month', 'sum', 'x'); %! assert_equal (G.Properties.VariableNames, {'month_t', 'GroupCount', 'sum_x'}); %! assert_equal (iscategorical (G.month_t), true); %! assert_equal (cellstr (G.month_t), {'Jan-2023'; 'Feb-2023'; 'Mar-2023'}); %! assert_equal (G.GroupCount, [2; 1; 2]); %! assert_equal (G.sum_x, [3; 3; 9]); ## Time-unit 'year' point labels; empty periods appear with IncludeEmptyGroups %!test %! t = datetime ([2020; 2020; 2023], 1, 1); %! T = table (t, (1:3)', 'VariableNames', {'t', 'x'}); %! G = groupsummary (T, 't', 'year', 'sum', 'x', 'IncludeEmptyGroups', true); %! assert_equal (numel (categories (G.year_t)), 4); %! assert_equal (cellstr (G.year_t), {'2020'; '2021'; '2022'; '2023'}); %! assert_equal (G.GroupCount, [2; 0; 0; 1]); ## Time-unit 'day' and 'quarter' labels %!test %! t = datetime (2024, [1; 4], [3; 10]); %! T = table (t, (1:2)', 'VariableNames', {'t', 'x'}); %! assert_equal (cellstr (groupsummary (T, 't', 'day', 'sum', 'x').day_t), ... %! {'03-Jan-2024'; '10-Apr-2024'}); %! assert_equal (cellstr (groupsummary (T, 't', 'quarter', 'sum', 'x').quarter_t), ... %! {'Q1 2024'; 'Q2 2024'}); ## A duration bin width bins a duration variable (labelled in its unit, disc_) %!test %! d = minutes ([100; 200; 300]); %! T = table (d, (1:3)', 'VariableNames', {'d', 'x'}); %! G = groupsummary (T, 'd', minutes (90), 'sum', 'x'); %! assert_equal (G.Properties.VariableNames, {'disc_d', 'GroupCount', 'sum_x'}); %! assert_equal (cellstr (G.disc_d), {'[90 min, 180 min)'; '[180 min, 270 min)'; ... %! '[270 min, 360 min]'}); ## Time-unit binning of a numeric variable is rejected %!error ... %! groupsummary (table ([1; 2], [3; 4], 'VariableNames', {'g', 'x'}), 'g', 'month', 'sum') %!error ... %! groupsummary (table ([1; 2], [3; 4], 'VariableNames', {'g', 'x'}), 'g', 2, 'sum', 'IncludedEdge', 'mid') %!error ... %! groupsummary (table ({'a'; 'b'}, [3; 4], 'VariableNames', {'g', 'x'}), 'g', 2, 'sum') %!error ... %! groupsummary (table ([1; 2], [3; 4], 'VariableNames', {'g', 'x'}), 'g', 'month', 'sum') ## 'groupcounts' returns the grouping variables, GroupCount, and Percent %!test %! T = table ([1; 1; 2; 2; 1], [10; 20; 30; 40; 50], 'VariableNames', {'g', 'x'}); %! G = groupcounts (T, 'g'); %! assert_equal (G.Properties.VariableNames, {'g', 'GroupCount', 'Percent'}); %! assert_equal (G.g, [1; 2]); %! assert_equal (G.GroupCount, [3; 2]); %! assert_equal (G.Percent, [60; 40]); ## Percent is each group's count as a percentage of the total row count %!test %! T = table ([1; 1; 1; 2], 'VariableNames', {'g'}); %! G = groupcounts (T, 'g'); %! assert_equal (G.GroupCount, [3; 1]); %! assert_equal (G.Percent, [75; 25]); ## Several grouping variables group by their unique combinations, missing last %!test %! T = table ([1; 1; 2; NaN; 2], {'b'; 'a'; 'a'; 'a'; 'b'}, ... %! 'VariableNames', {'g1', 'g2'}); %! G = groupcounts (T, {'g1', 'g2'}); %! assert_equal (G.g1, [1; 1; 2; 2; NaN]); %! assert_equal (G.g2, {'a'; 'b'; 'a'; 'b'; 'a'}); %! assert_equal (G.GroupCount, [1; 1; 1; 1; 1]); %! assert_equal (G.Percent, [20; 20; 20; 20; 20]); ## Missing values in a grouping variable form their own group, sorted last %!test %! T = table ([1; 1; 2; NaN; 2], 'VariableNames', {'g'}); %! G = groupcounts (T, 'g'); %! assert_equal (G.g, [1; 2; NaN]); %! assert_equal (G.GroupCount, [2; 2; 1]); %! assert_equal (G.Percent, [40; 40; 20]); ## 'IncludeMissingGroups' false drops the rows with a missing grouping value %!test %! T = table ([1; 1; 2; NaN; 2], 'VariableNames', {'g'}); %! G = groupcounts (T, 'g', 'IncludeMissingGroups', false); %! assert_equal (G.g, [1; 2]); %! assert_equal (G.GroupCount, [2; 2]); %! assert_equal (G.Percent, [50; 50]); ## A categorical grouping variable keeps its type and groups by category order %!test %! c = categorical ({'medium'; 'low'; 'high'; 'low'}, ... %! {'low', 'medium', 'high'}, 'Ordinal', true); %! T = table (c, 'VariableNames', {'g'}); %! G = groupcounts (T, 'g'); %! assert_equal (iscategorical (G.g), true); %! assert_equal (cellstr (G.g), {'low'; 'medium'; 'high'}); %! assert_equal (G.GroupCount, [2; 1; 1]); %!error ... %! groupcounts (table ([1; 2], 'VariableNames', {'g'}), false) ## 'IncludeEmptyGroups' adds unused categorical categories with zero count %!test %! c = categorical ({'a'; 'a'; 'b'}, {'a', 'b', 'c'}); %! T = table (c, 'VariableNames', {'g'}); %! G = groupcounts (T, 'g', 'IncludeEmptyGroups', true); %! assert_equal (cellstr (G.g), {'a'; 'b'; 'c'}); %! assert_equal (G.GroupCount, [2; 1; 0]); %! assert_equal (G.Percent, [100*2/3; 100/3; 0]); ## A GROUPBINS edge vector groups a numeric variable by bin interval %!test %! T = table ([1; 3; 5; 7; 9; 11], 'VariableNames', {'g'}); %! G = groupcounts (T, 'g', [0 6 12]); %! assert_equal (cellstr (G.disc_g), {'[0, 6)'; '[6, 12]'}); %! assert_equal (G.GroupCount, [3; 3]); %! assert_equal (G.Percent, [50; 50]); ## Binning combined with 'IncludeEmptyGroups' keeps the empty bins %!test %! T = table ([1; 2; 3; 20], 'VariableNames', {'g'}); %! G = groupcounts (T, 'g', [0 5 10 25], 'IncludeEmptyGroups', true); %! assert_equal (cellstr (G.disc_g), {'[0, 5)'; '[5, 10)'; '[10, 25]'}); %! assert_equal (G.GroupCount, [3; 0; 1]); %!error ... %! groupcounts (table ([1; 2], 'VariableNames', {'g'}), 'g', 'bogus') %!error ... %! groupcounts (table ([1; 2], 'VariableNames', {'g'}), 'g', 2, 'IncludedEdge', 'x') ## 'groupfilter' with a scalar-returning method keeps or drops whole groups %!test %! T = table ([1; 1; 1; 2; 2; 3], [3; 1; 2; 9; 8; 5], ... %! 'VariableNames', {'g', 'x'}); %! G = groupfilter (T, 'g', @(x) numel (x) > 2); %! assert_equal (G.Properties.VariableNames, {'g', 'x'}); %! assert_equal (G.g, [1; 1; 1]); %! assert_equal (G.x, [3; 1; 2]); ## A vector-returning method keeps individual rows within each group %!test %! T = table ([1; 1; 1; 1; 2; 2; 2; 2], [67; 65; 71; 55; 61; 79; 58; 78], ... %! 'VariableNames', {'d', 't'}); %! G = groupfilter (T, 'd', @(x) x == max (x)); %! assert_equal (G.d, [1; 2]); %! assert_equal (G.t, [71; 79]); ## Output rows keep their original order, not group order %!test %! T = table ([2; 1; 2; 1], [1; 2; 3; 4], 'VariableNames', {'g', 'x'}); %! G = groupfilter (T, 'g', @(x) numel (x) >= 1); %! assert_equal (G.x, [1; 2; 3; 4]); ## A row is kept only when the condition holds across all data variables %!test %! T = table ([1; 1; 2; 2], [5; 1; 9; 2], [1; 9; 1; 9], ... %! 'VariableNames', {'g', 'x', 'y'}); %! G = groupfilter (T, 'g', @(v) v > 3); %! assert_equal (G.x, zeros (0, 1)); ## 'DataVars' restricts the variables the filter operates on %!test %! T = table ([1; 1; 2; 2], [5; 1; 9; 2], [1; 9; 1; 9], ... %! 'VariableNames', {'g', 'x', 'y'}); %! G = groupfilter (T, 'g', @(v) v > 3, 'x'); %! assert_equal (G.x, [5; 9]); %! assert_equal (G.y, [1; 1]); ## Rows with a missing grouping value form their own group and are filtered too %!test %! T = table ([1; 1; NaN; 2; 2], [10; 20; 30; 40; 50], ... %! 'VariableNames', {'g', 'x'}); %! G = groupfilter (T, 'g', @(v) mean (v) > 25); %! assert_equal (G.g, [NaN; 2; 2]); %! assert_equal (G.x, [30; 40; 50]); ## A categorical grouping variable groups by category %!test %! T = table (categorical ({'a'; 'b'; 'a'; 'b'; 'a'}), [1; 5; 2; 6; 9], ... %! 'VariableNames', {'g', 'x'}); %! G = groupfilter (T, 'g', @(x) x == max (x)); %! assert_equal (cellstr (G.g), {'b'; 'a'}); %! assert_equal (G.x, [6; 9]); ## A GROUPBINS edge vector filters within bins of a numeric grouping variable %!test %! T = table ([1; 3; 5; 7; 9; 11], (1:6)', 'VariableNames', {'g', 'x'}); %! G = groupfilter (T, 'g', [0 6 12], @(x) numel (x) > 2); %! assert_equal (G.x, (1:6)'); %! G2 = groupfilter (T, 'g', [0 6 12], @(x) mean (x) > 4, 'IncludedEdge', 'right'); %! assert_equal (G2.x, [4; 5; 6]); %!error ... %! groupfilter (table ([1; 2], [3; 4], 'VariableNames', {'g', 'x'}), 'g', 'sum') %!error ... %! groupfilter (table ([1; 2], [3; 4], 'VariableNames', {'g', 'x'}), 'g', 'month', @(x) x > 0) %!error ... %! groupfilter (table ([1; 2], [3; 4], 'VariableNames', {'g', 'x'}), 'g', @(x) x > 0, 'x', 5) %!error ... %! groupfilter (table ([1; 2], 'VariableNames', {'g'}), false, @(x) x > 0) %!error ... %! groupfilter (table ([1; 1], [3; 4], 'VariableNames', {'g', 'x'}), 'g', @(x) [true; true; true]) ## 'grouptransform' 'meancenter' subtracts the group mean, in place %!test %! T = table ([1; 1; 1; 2; 2], [10; 20; 30; 40; 60], ... %! 'VariableNames', {'g', 'x'}); %! G = grouptransform (T, 'g', 'meancenter'); %! assert_equal (G.Properties.VariableNames, {'g', 'x'}); %! assert_equal (G.g, [1; 1; 1; 2; 2]); %! assert_equal (G.x, [-10; 0; 10; -10; 10]); ## 'zscore', 'norm', and 'rescale' spot checks %!test %! T = table ([1; 1; 1; 2; 2], [10; 20; 30; 40; 60], ... %! 'VariableNames', {'g', 'x'}); %! assert_equal (grouptransform (T, 'g', 'zscore').x, ... %! [-1; 0; 1; -1/sqrt(2); 1/sqrt(2)], 8 * eps); %! assert_equal (grouptransform (T, 'g', 'norm').x, ... %! [[10; 20; 30] ./ norm([10; 20; 30]); ... %! [40; 60] ./ norm([40; 60])], 8 * eps); %! assert_equal (grouptransform (T, 'g', 'rescale').x, [0; 0.5; 1; 0; 1]); ## 'meanfill' replaces missing values with the group mean %!test %! T = table ([1; 1; 2; 2], [10; NaN; 30; 40], 'VariableNames', {'g', 'x'}); %! G = grouptransform (T, 'g', 'meanfill'); %! assert_equal (G.x, [10; 10; 30; 40]); ## 'zscore' omits NaN for the group statistics and leaves NaN in place %!test %! T = table ([1; 1; 2; 2], [10; NaN; 30; 40], 'VariableNames', {'g', 'x'}); %! G = grouptransform (T, 'g', 'zscore'); %! assert_equal (G.x, [NaN; NaN; -1/sqrt(2); 1/sqrt(2)], 8 * eps); ## 'linearfill' interpolates interior missing values, leaving the edges as NaN %!test %! T = table ([1; 1; 1; 1; 2; 2; 2], [1; NaN; NaN; 4; NaN; 10; NaN], ... %! 'VariableNames', {'g', 'x'}); %! G = grouptransform (T, 'g', 'linearfill'); %! assert_equal (G.x, [1; 2; 3; 4; NaN; 10; NaN]); ## A function handle is applied to each group's slice %!test %! T = table ([1; 1; 1; 2; 2], [10; 20; 30; 40; 60], ... %! 'VariableNames', {'g', 'x'}); %! G = grouptransform (T, 'g', @(v) v - mean (v)); %! assert_equal (G.x, [-10; 0; 10; -10; 10]); ## 'ReplaceValues' false appends a '_' column, keeping originals %!test %! T = table ([1; 1; 1; 2; 2], [10; 20; 30; 40; 60], ... %! 'VariableNames', {'g', 'x'}); %! G = grouptransform (T, 'g', 'meancenter', 'ReplaceValues', false); %! assert_equal (G.Properties.VariableNames, {'g', 'x', 'meancenter_x'}); %! assert_equal (G.x, [10; 20; 30; 40; 60]); %! assert_equal (G.meancenter_x, [-10; 0; 10; -10; 10]); ## 'DataVars' restricts the variables that are transformed %!test %! T = table ([1; 1; 1; 2; 2], [10; 20; 30; 40; 60], [1; 2; 3; 4; 5], ... %! 'VariableNames', {'g', 'y', 'z'}); %! G = grouptransform (T, 'g', 'meancenter', 'y'); %! assert_equal (G.y, [-10; 0; 10; -10; 10]); %! assert_equal (G.z, [1; 2; 3; 4; 5]); ## Rows with a missing grouping value form their own group, in original order %!test %! T = table ([1; 1; NaN; 2; 2], [10; 20; 30; 40; 60], ... %! 'VariableNames', {'g', 'x'}); %! G = grouptransform (T, 'g', 'meancenter'); %! assert_equal (G.g, [1; 1; NaN; 2; 2]); %! assert_equal (G.x, [-5; 5; 0; -10; 10]); ## A GROUPBINS edge vector transforms within bins of a numeric grouping variable %!test %! T = table ([1; 3; 5; 7; 9; 11], [10; 20; 30; 40; 50; 60], ... %! 'VariableNames', {'g', 'x'}); %! G = grouptransform (T, 'g', [0 6 12], 'meancenter', 'x'); %! assert_equal (G.x, [-10; 0; 10; -10; 0; 10]); %!error ... %! grouptransform (table ([1; 2], [3; 4], 'VariableNames', {'g', 'x'}), 'g', 'bogus') %!error ... %! grouptransform (table ([1; 2], [3; 4], 'VariableNames', {'g', 'x'}), 'g', 'month', 'zscore') %!error ... %! grouptransform (table ([1; 2], [3; 4], 'VariableNames', {'g', 'x'}), 'g', 'zscore', 'ReplaceValues', 'yes') %!error ... %! grouptransform (table ([1; 2], [3; 4], 'VariableNames', {'g', 'x'}), 'g', 'zscore', 'x', 5) %!error ... %! grouptransform (table ([1; 2], {'a'; 'b'}, 'VariableNames', {'g', 'x'}), 'g', 'zscore') ## 'pivot' counts the rows of each row-and-column group by default %!test %! T = table (categorical ({'a'; 'a'; 'b'; 'b'; 'b'}), ... %! categorical ({'x'; 'y'; 'x'; 'y'; 'y'}), ... %! 'VariableNames', {'g', 'h'}); %! P = pivot (T, 'Rows', 'g', 'Columns', 'h'); %! assert_equal (P.Properties.VariableNames, {'g', 'x', 'y'}); %! assert_equal (cellstr (P{:, 'g'}), {'a'; 'b'}); %! assert_equal (P{:, {'x', 'y'}}, [1, 1; 1, 2]); ## Column variable names come from the column grouping values, joined with '_' %!test %! T = table (categorical ({'a'; 'a'; 'b'}), categorical ({'x'; 'y'; 'x'}), ... %! [1; 2; 3], 'VariableNames', {'g', 'h', 'v'}); %! P = pivot (T, 'Columns', {'g', 'h'}, 'DataVariable', 'v', 'Method', 'sum'); %! assert_equal (P.Properties.VariableNames, {'a_x', 'a_y', 'b_x'}); %! assert_equal (P{:, :}, [1, 2, 3]); ## A logical column grouping variable names the variables 'false' and 'true' %!test %! T = table (logical ([0; 0; 1; 1]), [1; 2; 3; 4], ... %! 'VariableNames', {'b', 'v'}); %! P = pivot (T, 'Columns', 'b', 'DataVariable', 'v', 'Method', 'sum'); %! assert_equal (P.Properties.VariableNames, {'false', 'true'}); %! assert_equal (P{:, :}, [3, 7]); ## 'DataVariable' with 'Method' aggregates that variable; empty mean cells are NaN %!test %! T = table (categorical ({'a'; 'a'; 'b'; 'b'}), ... %! categorical ({'x'; 'y'; 'x'; 'x'}), [10; 20; 30; 50], ... %! 'VariableNames', {'g', 'h', 'v'}); %! P = pivot (T, 'Rows', 'g', 'Columns', 'h', 'DataVariable', 'v', ... %! 'Method', 'mean'); %! assert_equal (P{:, {'x', 'y'}}, [10, 20; 40, NaN]); ## A numeric data variable defaults to 'sum'; the empty cell sums to zero %!test %! T = table (categorical ({'a'; 'a'; 'b'}), categorical ({'x'; 'y'; 'x'}), ... %! [10; 20; 30], 'VariableNames', {'g', 'h', 'v'}); %! P = pivot (T, 'Rows', 'g', 'Columns', 'h', 'DataVariable', 'v'); %! assert_equal (P{:, {'x', 'y'}}, [10, 20; 30, 0]); ## A function handle is applied to each cell's data slice %!test %! T = table (categorical ({'a'; 'a'; 'b'; 'b'}), ... %! categorical ({'x'; 'x'; 'y'; 'y'}), [1; 3; 5; 9], ... %! 'VariableNames', {'g', 'h', 'v'}); %! P = pivot (T, 'Rows', 'g', 'Columns', 'h', 'DataVariable', 'v', ... %! 'Method', @(x) max (x) - min (x)); %! assert_equal (P{:, {'x', 'y'}}, [2, NaN; NaN, 4]); ## Method 'percentage' gives each cell's row count as a percentage of the total %!test %! T = table (categorical ({'a'; 'a'; 'b'; 'b'}), ... %! categorical ({'x'; 'y'; 'x'; 'y'}), ... %! 'VariableNames', {'g', 'h'}); %! P = pivot (T, 'Rows', 'g', 'Columns', 'h', 'Method', 'percentage'); %! assert_equal (P{:, {'x', 'y'}}, [25, 25; 25, 25]); ## Method 'none' rearranges the data without aggregating; empty cells are missing %!test %! T = table (categorical ({'a'; 'a'; 'b'}), categorical ({'x'; 'y'; 'x'}), ... %! [10; 20; 30], 'VariableNames', {'g', 'h', 'v'}); %! P = pivot (T, 'Rows', 'g', 'Columns', 'h', 'DataVariable', 'v', ... %! 'Method', 'none'); %! assert_equal (P{:, {'x', 'y'}}, [10, 20; 30, NaN]); ## An omitted 'Rows' dimension collapses to a single row %!test %! T = table (categorical ({'a'; 'a'; 'b'}), [1; 2; 3], ... %! 'VariableNames', {'g', 'v'}); %! P = pivot (T, 'Columns', 'g', 'DataVariable', 'v', 'Method', 'sum'); %! assert_equal (P.Properties.VariableNames, {'a', 'b'}); %! assert_equal (P{:, :}, [3, 3]); ## With no 'Columns', a single variable named '_' (or 'count') %!test %! T = table (categorical ({'a'; 'a'; 'b'}), [1; 2; 3], ... %! 'VariableNames', {'g', 'v'}); %! P = pivot (T, 'Rows', 'g', 'DataVariable', 'v', 'Method', 'sum'); %! assert_equal (P.Properties.VariableNames, {'g', 'sum_v'}); %! assert_equal (P{:, 'sum_v'}, [3; 3]); %! P2 = pivot (T, 'Rows', 'g'); %! assert_equal (P2.Properties.VariableNames, {'g', 'count'}); %! assert_equal (P2{:, 'count'}, [2; 1]); ## 'RowLabelPlacement' 'rownames' moves the row labels to the RowNames property %!test %! T = table (categorical ({'a'; 'a'; 'b'}), categorical ({'x'; 'y'; 'x'}), ... %! 'VariableNames', {'g', 'h'}); %! P = pivot (T, 'Rows', 'g', 'Columns', 'h', 'RowLabelPlacement', 'rownames'); %! assert_equal (P.Properties.VariableNames, {'x', 'y'}); %! assert_equal (P.Properties.RowNames, {'a'; 'b'}); ## 'IncludeMissingGroups' controls whether missing grouping values form a group %!test %! T = table (categorical ({'a'; 'a'; 'b'; ''}, {'a', 'b'}), ... %! [1; 2; 3; 4], 'VariableNames', {'g', 'v'}); %! P = pivot (T, 'Rows', 'g', 'DataVariable', 'v', 'Method', 'sum'); %! assert_equal (P{:, 'sum_v'}, [3; 3; 4]); %! Q = pivot (T, 'Rows', 'g', 'DataVariable', 'v', 'Method', 'sum', ... %! 'IncludeMissingGroups', false); %! assert_equal (cellstr (Q{:, 'g'}), {'a'; 'b'}); %! assert_equal (Q{:, 'sum_v'}, [3; 3]); ## A missing column group is named '>' %!test %! T = table (categorical ({'a'; 'a'; 'b'; ''}, {'a', 'b'}), ... %! [1; 2; 3; 4], 'VariableNames', {'g', 'v'}); %! P = pivot (T, 'Columns', 'g', 'DataVariable', 'v', 'Method', 'sum'); %! assert_equal (P.Properties.VariableNames, {'a', 'b', ''}); %! assert_equal (P{:, :}, [3, 3, 4]); ## 'IncludeEmptyGroups' adds unused categorical categories as empty groups %!test %! T = table (setcats (categorical ({'a'; 'a'; 'b'}), {'a', 'b', 'c'}), ... %! categorical ({'x'; 'y'; 'x'}), 'VariableNames', {'g', 'h'}); %! P = pivot (T, 'Rows', 'g', 'Columns', 'h', 'IncludeEmptyGroups', true); %! assert_equal (cellstr (P{:, 'g'}), {'a'; 'b'; 'c'}); %! assert_equal (P{:, {'x', 'y'}}, [1, 1; 1, 0; 0, 0]); ## 'IncludeTotals' appends an 'Overall_' marginal row and column %!test %! T = table (categorical ({'a'; 'a'; 'b'; 'b'}), ... %! categorical ({'x'; 'y'; 'x'; 'y'}), [1; 2; 3; 9], ... %! 'VariableNames', {'g', 'h', 'v'}); %! P = pivot (T, 'Rows', 'g', 'Columns', 'h', 'DataVariable', 'v', ... %! 'Method', 'sum', 'IncludeTotals', true); %! assert_equal (P.Properties.VariableNames, {'g', 'x', 'y', 'Overall_sum'}); %! assert_equal (cellstr (P{:, 'g'}), {'a'; 'b'; 'Overall_sum'}); %! assert_equal (P{:, {'x', 'y', 'Overall_sum'}}, [1, 2, 3; 3, 9, 12; 4, 11, 15]); %!error ... %! pivot (table (categorical ({'a'; 'b'}), 'VariableNames', {'g'})) %!error ... %! pivot (table ([1; 2], [3; 4], [5; 6], 'VariableNames', {'g', 'x', 'y'}), 'Rows', 'g', 'DataVariable', {'x', 'y'}) %!error ... %! pivot (table (categorical ({'a'; 'b'}), 'VariableNames', {'g'}), 'Rows', 'g', 'Method', 'bogus') %!error ... %! pivot (table (categorical ({'a'; 'b'}), 'VariableNames', {'g'}), 'Rows', 'g', 'Method', 'sum') %!error ... %! pivot (table (categorical ({'a'; 'a'}), categorical ({'x'; 'x'}), [1; 2], 'VariableNames', {'g', 'h', 'v'}), 'Rows', 'g', 'Columns', 'h', 'DataVariable', 'v', 'Method', 'none') %!error ... %! pivot (table (categorical ({'a'; 'b'}), [1; 2], 'VariableNames', {'g', 'v'}), 'Rows', 'g', 'DataVariable', 'v', 'Method', 'none', 'IncludeTotals', true) %!error ... %! pivot (table (categorical ({'a'; 'b'}), 'VariableNames', {'g'}), 'Rows', 'g', 'RowLabelPlacement', 'foo') %!error ... %! pivot (table (categorical ({'a'; 'b'}), 'VariableNames', {'g'}), 'Rows', 'g', 'IncludeMissingGroups', 'yes') %!error ... %! pivot (table (categorical ({'a'; 'b'}), 'VariableNames', {'g'}), 'Rows', 'g', 'OutputFormat', 'bogus') ## 'OutputFormat','nested' nests multiple Columns variables into nested tables %!test %! r = [1; 1; 2; 2; 1; 2]; %! c1 = categorical ({'A'; 'A'; 'B'; 'B'; 'A'; 'B'}); %! c2 = categorical ({'X'; 'Y'; 'X'; 'Y'; 'X'; 'Y'}); %! v = [10; 20; 30; 40; 50; 60]; %! T = table (r, c1, c2, v, 'VariableNames', {'r', 'c1', 'c2', 'v'}); %! P = pivot (T, 'Rows', 'r', 'Columns', {'c1', 'c2'}, 'DataVariable', 'v', ... %! 'Method', 'sum', 'OutputFormat', 'nested'); %! assert_equal (P.Properties.VariableNames, {'r', 'A', 'B'}); %! assert_equal (isa (P.A, 'table'), true); %! assert_equal (P.A.Properties.VariableNames, {'X', 'Y'}); %! assert_equal (P.A.X, [60; 0]); %! assert_equal (P.A.Y, [20; 0]); %! assert_equal (P.B.X, [0; 30]); %! assert_equal (P.B.Y, [0; 100]); ## Nested with a single Columns variable collapses to flat columns %!test %! r = [1; 1; 2]; %! c = categorical ({'A'; 'B'; 'A'}); %! v = [10; 20; 30]; %! T = table (r, c, v, 'VariableNames', {'r', 'c', 'v'}); %! P = pivot (T, 'Rows', 'r', 'Columns', 'c', 'DataVariable', 'v', ... %! 'Method', 'sum', 'OutputFormat', 'nested'); %! assert_equal (P.Properties.VariableNames, {'r', 'A', 'B'}); %! assert_equal (isa (P.A, 'double'), true); ## Nested output with IncludeTotals keeps a flat Overall_ column %!test %! r = [1; 1; 2; 2; 1; 2]; %! c1 = categorical ({'A'; 'A'; 'B'; 'B'; 'A'; 'B'}); %! c2 = categorical ({'X'; 'Y'; 'X'; 'Y'; 'X'; 'Y'}); %! v = [10; 20; 30; 40; 50; 60]; %! T = table (r, c1, c2, v, 'VariableNames', {'r', 'c1', 'c2', 'v'}); %! P = pivot (T, 'Rows', 'r', 'Columns', {'c1', 'c2'}, 'DataVariable', 'v', ... %! 'Method', 'sum', 'OutputFormat', 'nested', 'IncludeTotals', true); %! assert_equal (any (strcmp (P.Properties.VariableNames, 'Overall_sum')), true); %! assert_equal (P.Overall_sum, [80; 130; 210]); %!error ... %! pivot (table ([1; 2], 'VariableNames', {'g'}), 'Rows', 'g', 'RowsBinMethod', 2, 'IncludedEdge', 'mid') %!error ... %! pivot (table (categorical ({'a'; 'b'}), 'VariableNames', {'g'}), 'Rows', 'g', 'RowsBinMethod', 5) %!error ... %! pivot (table (categorical ({'a'; 'b'}), 'VariableNames', {'g'}), 'Rows', 'g', 'Wibble', 1) ## 'RowsBinMethod' bins a numeric row grouping variable by bin interval %!test %! T = table ([1; 5; 1; 9; 5], [1; 1; 2; 2; 3], [10; 20; 30; 40; 50], ... %! 'VariableNames', {'r', 'c', 'v'}); %! P = pivot (T, 'Rows', 'r', 'Columns', 'c', 'RowsBinMethod', [0 4 10]); %! assert_equal (iscategorical (P.disc_r), true); %! assert_equal (cellstr (P.disc_r), {'[0, 4)'; '[4, 10]'}); %! assert_equal (P{:, 2:end}, [1, 1, 0; 1, 1, 1]); ## 'ColumnsBinMethod' names the output variables after the column bins %!test %! T = table ([1; 1; 2; 2; 3], [1; 5; 1; 9; 5], [10; 20; 30; 40; 50], ... %! 'VariableNames', {'c', 'r', 'v'}); %! P = pivot (T, 'Rows', 'c', 'Columns', 'r', 'ColumnsBinMethod', 2, ... %! 'DataVariable', 'v', 'Method', 'sum'); %! assert_equal (P.Properties.VariableNames, {'c', '[1, 5)', '[5, 9]'}); %! assert_equal (P{:, 2:end}, [10, 20; 30, 40; 0, 50]); ################################################################################ ## ** Auxiliary Methods ** ## ################################################################################ ## Available Methods ## ## ## ## 'horzcat' 'iscolumn' 'isempty' 'ismatrix' ## ## 'isrow' 'isscalar' 'istable' 'isvector' ## ## 'length' 'ndims' 'numel' 'repelem' ## ## 'repmat' 'size' 'squeeze' 'vertcat' ## ## ## ################################################################################ %!assert_equal (iscolumn (table ([1; 2; 3])), true) %!assert_equal (iscolumn (table ([1, 2, 3])), true) %!assert_equal (iscolumn (table (1, 2, 3)), false) %!assert_equal (isempty (table ()), true) %!assert_equal (isempty (table ([])), true) %!assert_equal (isempty (table (1)), false) %!assert_equal (ismatrix (table ([])), true) %!assert_equal (ismatrix (table (1)), true) %!assert_equal (isrow (table ([1, 2, 3])), true) %!assert_equal (isrow (table (1, 2, 3)), true) %!assert_equal (isrow (table ([1; 2; 3])), false) %!assert_equal (isscalar (table (1)), true) %!assert_equal (isscalar (table ([1, 2, 3])), true) %!assert_equal (isscalar (table (1, 2, 3)), false) %!assert_equal (isscalar (table ([1; 2; 3])), false) %!assert_equal (istable (table ()), true) %!assert_equal (isvector (table (1)), true) %!assert_equal (isvector (table (1, 2, 3)), true) %!assert_equal (isvector (table ([1, 2, 3])), true) %!assert_equal (isvector (table ([1; 2; 3])), true) %!assert_equal (isvector (table ([1; 2], {'a'; 'b'})), false) %!assert_equal (length (table (1)), 1) %!assert_equal (length (table ([1; 2; 3])), 3) %!assert_equal (length (table ([1, 2, 3])), 1) %!assert_equal (length (table ([1; 2; 3], [2; 3; 4])), 3) %!assert_equal (length (table ([1; 2], [2; 3], [3; 4], [4; 5])), 4) %!assert_equal (ndims (table ()), 2) %!assert_equal (ndims (table ([])), 2) %!assert_equal (ndims (table ([1, 2, 3, 4])), 2) %!assert_equal (ndims (table ([1; 2], {'a'; 'b'})), 2) ## 'numel' returns rows times variables, i.e. prod (size (tbl)) %!assert_equal (numel (table ()), 0) %!assert_equal (numel (table (1)), 1) %!assert_equal (numel (table (1, 2, 3, 4)), 4) %!assert_equal (numel (table ([1; 2; 3], [4; 5; 6])), 6) %!assert_equal (numel (table (1, 2, 3, 4)), prod (size (table (1, 2, 3, 4)))) ## 'horzcat' combines variables of equal-height tables %!test %! T1 = table ([1; 2; 3], {'a'; 'b'; 'c'}, 'VariableNames', {'x', 'y'}); %! T2 = table ([10; 20; 30], 'VariableNames', {'z'}); %! H = horzcat (T1, T2); %! assert_equal (size (H), [3, 3]); %! assert_equal (H.Properties.VariableNames, {'x', 'y', 'z'}); %! assert_equal (H.x, [1; 2; 3]); %! assert_equal (H.z, [10; 20; 30]); %!test # '[ , ]' operator dispatches to horzcat %! T1 = table ([1; 2; 3], 'VariableNames', {'x'}); %! T2 = table ([10; 20; 30], 'VariableNames', {'z'}); %! H = [T1, T2]; %! assert_equal (size (H), [3, 2]); %! assert_equal (H.Properties.VariableNames, {'x', 'z'}); %!test # row names of an input table are carried over %! T1 = table ([1; 2], 'VariableNames', {'x'}, 'RowNames', {'r1', 'r2'}); %! T2 = table ([3; 4], 'VariableNames', {'y'}); %! H = horzcat (T1, T2); %! assert_equal (H.Properties.VariableNames, {'x', 'y'}); %! assert_equal (H.Properties.RowNames, {'r1'; 'r2'}); %!test # multiple row-named inputs are aligned by row name, not position %! T1 = table ([1; 2; 3], 'VariableNames', {'x'}, 'RowNames', {'r1', 'r2', 'r3'}); %! T2 = table ([20; 30; 10], 'VariableNames', {'y'}, 'RowNames', {'r2', 'r3', 'r1'}); %! T3 = table ([300; 100; 200], 'VariableNames', {'z'}, 'RowNames', {'r3', 'r1', 'r2'}); %! H = horzcat (T1, T2, T3); %! assert_equal (H.Properties.RowNames, {'r1'; 'r2'; 'r3'}); %! assert_equal (H.x, [1; 2; 3]); %! assert_equal (H.y, [10; 20; 30]); %! assert_equal (H.z, [100; 200; 300]); ## Test input validation for 'horzcat' method %!error ... %! horzcat (table (1), 5); %!error ... %! horzcat (table (1, 'VariableNames', {'x'}), ... %! table (2, 'VariableNames', {'x'})); %!error ... %! horzcat (table ([1; 2], 'VariableNames', {'x'}), ... %! table (3, 'VariableNames', {'y'})); %!error ... %! horzcat (table (1, 'VariableNames', {'x'}, 'RowNames', {'r1'}), ... %! table (2, 'VariableNames', {'y'}, 'RowNames', {'q1'})); ## 'vertcat' stacks rows of tables sharing variable names %!test %! T1 = table ([1; 2; 3], {'a'; 'b'; 'c'}, 'VariableNames', {'x', 'y'}); %! T2 = table ([4; 5], {'d'; 'e'}, 'VariableNames', {'x', 'y'}); %! V = [T1; T2]; %! assert_equal (size (V), [5, 2]); %! assert_equal (V.x, [1; 2; 3; 4; 5]); %! assert_equal (V.y, {'a'; 'b'; 'c'; 'd'; 'e'}); %!test # variables are matched by name, not position %! T1 = table ([1; 2; 3], {'a'; 'b'; 'c'}, 'VariableNames', {'x', 'y'}); %! T2 = table ({'f'}, 6, 'VariableNames', {'y', 'x'}); %! V = vertcat (T1, T2); %! assert_equal (V.x, [1; 2; 3; 6]); %! assert_equal (V.y, {'a'; 'b'; 'c'; 'f'}); %!test # two row-named inputs concatenate, keeping their unique row names %! T1 = table ([1; 2], 'VariableNames', {'x'}, 'RowNames', {'r1', 'r2'}); %! T2 = table ([3; 4], 'VariableNames', {'x'}, 'RowNames', {'r3', 'r4'}); %! V = [T1; T2]; %! assert_equal (V.Properties.RowNames, {'r1'; 'r2'; 'r3'; 'r4'}); %! assert_equal (V.x, [1; 2; 3; 4]); %!test # rows from an unnamed input get default 'Row' names by position %! T1 = table ([1; 2], 'VariableNames', {'x'}, 'RowNames', {'r1', 'r2'}); %! T2 = table ([3; 4; 5], 'VariableNames', {'x'}); %! V = [T1; T2]; %! assert_equal (V.Properties.RowNames, {'r1'; 'r2'; 'Row3'; 'Row4'; 'Row5'}); %! assert_equal (V.x, [1; 2; 3; 4; 5]); %!test # default 'Row' numbering tracks the output position %! T1 = table ([1; 2; 3], 'VariableNames', {'x'}); %! T2 = table ([4; 5], 'VariableNames', {'x'}, 'RowNames', {'r1', 'r2'}); %! V = [T1; T2]; %! assert_equal (V.Properties.RowNames, {'Row1'; 'Row2'; 'Row3'; 'r1'; 'r2'}); %! assert_equal (V.x, [1; 2; 3; 4; 5]); %!test # object-typed variables retain their class through concatenation %! T1 = table (datetime (2020, 1, [1; 2]), categorical ({'a'; 'b'}), ... %! string ({'p'; 'q'}), 'VariableNames', {'d', 'c', 's'}); %! T2 = table (datetime (2020, 1, [3; 4]), categorical ({'b'; 'a'}), ... %! string ({'r'; 's'}), 'VariableNames', {'d', 'c', 's'}); %! V = vertcat (T1, T2); %! assert_equal (height (V), 4); %! assert_equal (class (V.d), 'datetime'); %! assert_equal (class (V.c), 'categorical'); %! assert_equal (class (V.s), 'string'); %! assert_equal (cellstr (V.c), {'a'; 'b'; 'b'; 'a'}); %! assert_equal (cellstr (V.s), {'p'; 'q'; 'r'; 's'}); ## Test input validation for 'vertcat' method %!error ... %! vertcat (table (1), 5); %!error ... %! vertcat (table (1, 'VariableNames', {'x'}), ... %! table (2, 'VariableNames', {'y'})); %!error ... %! vertcat (table (1, 'VariableNames', {'x'}, 'RowNames', {'r1'}), ... %! table (2, 'VariableNames', {'x'}, 'RowNames', {'r1'})); ## 'repelem' replicates rows and variables in place %!test # rows replicated consecutively (1 1 2 2 3 3) %! T = table ([1; 2; 3], {'a'; 'b'; 'c'}, 'VariableNames', {'x', 'y'}); %! R = repelem (T, 2, 1); %! assert_equal (size (R), [6, 2]); %! assert_equal (R.x, [1; 1; 2; 2; 3; 3]); %! assert_equal (R.y, {'a'; 'a'; 'b'; 'b'; 'c'; 'c'}); %!test # columns replicated; copied variables get a '_N' suffix %! T = table ([1; 2; 3], {'a'; 'b'; 'c'}, 'VariableNames', {'x', 'y'}); %! R = repelem (T, 1, 2); %! assert_equal (size (R), [3, 4]); %! assert_equal (R.Properties.VariableNames, {'x', 'x_1', 'y', 'y_1'}); %! assert_equal (R.x, [1; 2; 3]); %! assert_equal (R.x_1, [1; 2; 3]); %!test # a scalar count applies to both dimensions %! T = table ([1; 2; 3], 'VariableNames', {'x'}); %! R = repelem (T, 2); %! assert_equal (size (R), [6, 2]); %! assert_equal (R.x, [1; 1; 2; 2; 3; 3]); %! assert_equal (R.Properties.VariableNames, {'x', 'x_1'}); %!test # repelem (T, 1, 1) leaves the table unchanged %! T = table ([1; 2; 3], 'VariableNames', {'x'}); %! assert_equal (isequal (repelem (T, 1, 1), T), true); ## Test input validation for 'repelem' method %!error ... %! repelem (table (1)); %!error ... %! repelem (table (1), 1, 2, 3); %!error ... %! repelem (table (1), 1.5); %!error ... %! repelem (table (1), 0); %!error ... %! repelem (table (1), 1.5, 1); %!error ... %! repelem (table (1), 1, 0); %!error ... %! repelem (table (1), 2, 1.5); ## 'repmat' tiles whole rows and variables of a table %!test # rows tiled as a block (1 2 3 1 2 3) %! T = table ([1; 2; 3], {'a'; 'b'; 'c'}, 'VariableNames', {'x', 'y'}); %! M = repmat (T, 2, 1); %! assert_equal (size (M), [6, 2]); %! assert_equal (M.x, [1; 2; 3; 1; 2; 3]); %! assert_equal (M.y, {'a'; 'b'; 'c'; 'a'; 'b'; 'c'}); %!test # columns tiled; the variable block is repeated then suffixed %! T = table ([1; 2; 3], {'a'; 'b'; 'c'}, 'VariableNames', {'x', 'y'}); %! M = repmat (T, 1, 2); %! assert_equal (size (M), [3, 4]); %! assert_equal (M.Properties.VariableNames, {'x', 'y', 'x_1', 'y_1'}); %!test # a scalar count applies to both dimensions %! T = table ([1; 2], 'VariableNames', {'x'}); %! M = repmat (T, 2); %! assert_equal (size (M), [4, 2]); %! assert_equal (M.x, [1; 2; 1; 2]); %!test # repmat (T, 1, 1) leaves the table unchanged %! T = table ([1; 2; 3], 'VariableNames', {'x'}); %! assert_equal (isequal (repmat (T, 1, 1), T), true); ## Test input validation for 'repmat' method %!error ... %! repmat (table (1)); %!error ... %! repmat (table (1), 1, 2, 3); %!error ... %! repmat (table (1), 1.5); %!error ... %! repmat (table (1), 1.5, 1); %!error ... %! repmat (table (1), 1, 0); %!error ... %! repmat (table (1), 2, 1.5); ## 'size' returns [height, width] and supports dimension arguments %!test %! T = table ([1; 2; 3], [4; 5; 6], 'VariableNames', {'a', 'b'}); %! assert_equal (size (T), [3, 2]); %! assert_equal (size (T, 1), 3); %! assert_equal (size (T, 2), 2); %! assert_equal (size (T, 3), 1); %! assert_equal (size (T, [1, 2]), [3, 2]); %! assert_equal (size (T, [2, 1]), [2, 3]); %! assert_equal (size (T, [2, 3]), [2, 1]); %!test # multiple output forms %! T = table ([1; 2; 3], [4; 5; 6], 'VariableNames', {'a', 'b'}); %! [r, c] = size (T); %! assert_equal ([r, c], [3, 2]); %! [r, c, d] = size (T); %! assert_equal ([r, c, d], [3, 2, 1]); ## 'squeeze' is a no-op for tables (always two-dimensional) %!test %! T = table ([1; 2; 3], {'a'; 'b'; 'c'}, 'VariableNames', {'x', 'y'}); %! assert_equal (isequal (squeeze (T), T), true); %!assert_equal (isequal (squeeze (table ()), table ()), true) ################################################################################ ## ** Forbidden Methods ** ## ################################################################################ ## Available Methods ## ## ## ## 'repelems' 'reshape' 'resize' 'shiftdims' ## ## 'vec' ## ## ## ################################################################################ ## These methods are unsupported by design and only emit an error %!error ... %! repelems (table (1)); %!error ... %! reshape (table (1), 1, 1); %!error ... %! resize (table (1), 1); %!error ... %! shiftdims (table (1), 1); %!error ... %! vec (table (1)); ################################################################################ ## ** Reference and Assignment Operations ** ## ################################################################################ ## Available Methods ## ## ## ## 'end' 'subsref' 'subsasgn' ## ## ## ################################################################################ ## Test 'subref' and 'subsasgn' methods %!shared LastName, Age, Smoker, Height, Weight, BloodPressure, T, tblA %! LastName = {"Sanchez"; "Johnson"; "Li"; "Diaz"; "Brown"}; %! Age = [38; 43; 38; 40; 49]; %! Smoker = logical ([1; 0; 1; 0; 1]); %! Height = [71; 69; 64; 67; 64]; %! Weight = [176; 163; 131; 133; 119]; %! BloodPressure = [124, 93; 109, 77; 125, 83; 117, 75; 122, 80]; %! T = table (Age, Smoker, Height, Weight, BloodPressure); %!assert_equal (size (T(1,:)), [1, 5]); %!assert_equal (class (T(1,:)), "table"); %!assert_equal (size (T{1,:}), [1, 6]); %!assert_equal (T{1,:}, [38, 1, 71, 176, 124, 93]); %!assert_equal (T{:,2}, logical([1; 0; 1; 0; 1])); %!assert_equal (T{:,"Smoker"}, logical([1; 0; 1; 0; 1])); %!assert_equal (T{:,"Height"}, [71; 69; 64; 67; 64]); %!assert_equal (size(T{:,"Smoker"}), [5, 1]); %!assert_equal (size(T{:,"Height"}), [5, 1]); %!test # brace-indexing all-cell variables yields a cell array %! Tc = table ({'a'; 'b'}, {'c'; 'd'}, 'VariableNames', {'x', 'y'}); %! assert_equal (Tc{:,:}, {'a', 'c'; 'b', 'd'}); %!error ... %! Tm = table ([1; 2; 3], {'a'; 'b'; 'c'}, 'VariableNames', {'x', 'y'}); Tm{1,:}; %!error ... %! Tm = table ([1; 2; 3], {'a'; 'b'; 'c'}, 'VariableNames', {'x', 'y'}); Tm{:,:}; %!assert_equal (T.Variables, [Age, Smoker, Height, Weight, BloodPressure]); %!assert_equal (isempty (T.Properties.RowNames), true); %!assert_equal (numel (T.Properties.VariableNames), 5); %!test %! T.Properties.Description = "text"; %! assert_equal (T.Properties.Description, "text"); %!assert_equal (size (T(:,{"Age", "Smoker"})), [5, 2]); %!assert_equal (T{:,{"Age", "Smoker"}}, [Age, Smoker]); %!test %! T = table (Age, Smoker); %! T.("29-May-2019 Blood Pressure Reading") = BloodPressure; %! assert_equal (T.("29-May-2019 Blood Pressure Reading"), BloodPressure); %!test %! T = table (Age, Smoker); %! T.Height = Height; %! T.Weight = Weight; %! assert_equal (size (T), [5, 4]); %! assert_equal (T.Weight, Weight); %! assert_equal (T.Weight, T{:,"Weight"}); %! assert_equal (T.Weight, T{:,4}); %!test %! T.Weight(1) = 25; %! assert_equal (T.Weight, [25;Weight(2:end)]); %!test %! T.Weight([1,3]) = 25; %! assert_equal (T.Weight, [25;Weight(2);25;Weight(4:end)]); %!test %! T.Weight([1,3]) = 25; %! assert_equal (T.Weight, [25;Weight(2);25;Weight(4:end)]); %!test %! T.Weight([1:3,5]) = 25; %! assert_equal (T.Weight, [25;25;25;Weight(4);25]); %!test %! T = table (LastName, Age, Smoker, Height, Weight); %! T.Properties.RowNames = string (LastName); %!test %! T = table (LastName, Age, Smoker, Height, Weight); %! T.Properties.RowNames = 'LastName'; %!test %! T = table (LastName, Age, Smoker, Height, Weight); %! T.Properties.RowNames = {'LastName'}; %!test %! T = table (LastName, Age, Smoker, Height, Weight); %! T.Properties.RowNames = ['LastName']; %!test %! T = table (LastName, Age, Smoker, Height, Weight); %! assert_equal (isempty (T.Row), true); %! T.Properties.DimensionNames(1) = 'Patients'; %! assert_equal (isempty (T.Patients), true); %!test %! T.Properties.RowNames = "LastName"; %! assert_equal (isempty (T.Patients), false); %! T.Properties.DimensionNames(2) = 'Data'; %! assert_equal (T.Data, [Age,Smoker,Height,Weight]); %!test %! T = table (Age, Smoker, Height, Weight, BloodPressure); %! assert_equal (T{1, @isnumeric}, [38, 71, 176, 124, 93]); %! assert_equal (T{1, vartype ("numeric")}, [38, 71, 176, 124, 93]); %!test %! T1 = table (LastName, Age); %! assert_equal (T1{:, @iscellstr}, LastName); %! assert_equal (T1{:, @isnumeric}, Age); %! assert_equal (T1(:, @iscellstr).LastName, LastName) %! assert_equal (T1(:, @isnumeric).Age, Age) %!test %! T1 = table (LastName, Age); %! assert_equal (T1{:, @iscellstr}, LastName); %! assert_equal (T1{:, @isnumeric}, Age); %!test %! T1 = table (LastName, Age, Height); %! assert_equal (T1{:, @iscellstr}, LastName); %! assert_equal (class (T1(:, @isnumeric)), "table"); %! assert_equal (size (T1(:, @isnumeric)), [5, 2]); ## Test "end" keyword %!test %! t = table ([1;2;3], [4;5;6], [7;8;9], 'VariableNames', {'A', 'B', 'C'}, ... %! 'RowNames', {'Row1', 'Row2', 'Row3'}); %! out = t(:,end); %! assert_equal (out.Properties.VariableNames, {'C'}); %! assert_equal (out.Properties.RowNames, {'Row1'; 'Row2'; 'Row3'}); %! assert_equal (out.C, [7; 8; 9]); %! out = t{:,end}; %! assert_equal (out, [7; 8; 9]); %!test %! t = table ([1;2;3], [4;5;6], [7;8;9], 'VariableNames', {'A', 'B', 'C'}, ... %! 'RowNames', {'Row1', 'Row2', 'Row3'}); %! out = t(end, :); %! assert_equal (out.Properties.VariableNames, {'A', 'B', 'C'}); %! assert_equal (out.Properties.RowNames, {'Row3'}); %! assert_equal ([out.A, out.B, out.C], [3, 6, 9]); %! out = t{end,:}; %! assert_equal (out, [3, 6, 9]); ## 'subsref' with '()' returns a sub-table for every row/variable reference type %!test %! T = table ([1;2;3], {'a';'b';'c'}, [10;20;30], 'VariableNames', {'x','y','z'}); %! assert_equal (class (T(2,:)), 'table'); %! assert_equal (T(2,:).x, 2); %! assert_equal (size (T(1:2,[1 3])), [2, 2]); %! assert_equal (T(1:2,[1 3]).Properties.VariableNames, {'x','z'}); %! assert_equal (height (T(logical ([1 0 1]),:)), 2); %! assert_equal (T(:,logical ([1 0 1])).Properties.VariableNames, {'x','z'}); %! assert_equal (T(:,'y').y, {'a';'b';'c'}); %! assert_equal (T(:,{'x','z'}).Properties.VariableNames, {'x','z'}); %! assert_equal (T(:,@isnumeric).Properties.VariableNames, {'x','z'}); %! assert_equal (T(:,vartype ('numeric')).Properties.VariableNames, {'x','z'}); %! assert_equal (size (T(:,:)), [3, 3]); %!test # named row references require RowNames %! T = table ([1;2;3], [10;20;30], 'VariableNames', {'x','z'}, ... %! 'RowNames', {'r1','r2','r3'}); %! assert_equal (T('r2',:).x, 2); %! assert_equal (T({'r1','r3'},:).x, [1;3]); %! assert_equal (T('r2',:).Properties.RowNames, {'r2'}); ## Test input validation for 'subsref' with '()' indexing %!error ... %! T = table ([1;2;3]); T(1); %!error ... %! T = table ([1;2;3], 'VariableNames', {'x'}); T(:,'q'); %!error ... %! T = table ([1;2;3], 'VariableNames', {'x'}); T('r1',:); %!error ... %! T = table ([1;2;3], 'VariableNames', {'x'}, 'RowNames', {'r1','r2','r3'}); ... %! T('r9',:); ## 'subsref' with '{}' extracts raw data from concatenable variables %!test %! T = table ([1;2;3], [10;20;30], [100;200;300], 'VariableNames', {'x','y','z'}); %! assert_equal (T{2,:}, [2, 20, 200]); %! assert_equal (T{:,2}, [10;20;30]); %! assert_equal (T{1:2,[1 3]}, [1, 100; 2, 200]); %! assert_equal (T{:,'y'}, [10;20;30]); %! assert_equal (T{:,@isnumeric}, [1 10 100; 2 20 200; 3 30 300]); ## Test input validation for 'subsref' with '{}' indexing %!error ... %! T = table ([1;2;3]); T{1}; ## 'subsref' with '.' returns variable values, properties, and dimension data %!test %! T = table ([1;2;3], {'a';'b';'c'}, 'VariableNames', {'x','y'}); %! assert_equal (T.x, [1;2;3]); %! assert_equal (T.y, {'a';'b';'c'}); %! assert_equal (class (T.Properties), 'struct'); %! assert_equal (T.Properties.VariableNames, {'x','y'}); %! assert_equal (isempty (T.Row), true); %! assert_equal (T.Variables, {1, 'a'; 2, 'b'; 3, 'c'}); %!test # '.' on the RowNames dimension name returns the row names %! T = table ([1;2;3], 'VariableNames', {'x'}, 'RowNames', {'r1','r2','r3'}); %! assert_equal (T.Row, {'r1';'r2';'r3'}); %!test # chained '.' references resolve left to right %! T = table ([1;2;3], {'a';'b';'c'}, 'VariableNames', {'x','y'}); %! assert_equal (T.x(2), 2); %! assert_equal (T.Properties.VariableNames{1}, 'x'); %! assert_equal (T(2:3,:).x, [2;3]); ## Test input validation for 'subsref' with '.' indexing %!error ... %! T = table ([1;2;3]); subsref (T, struct ('type', '.', 'subs', 5)); %!error ... %! T = table ([1;2;3], 'VariableNames', {'x'}); T.nope; ## 'subsasgn' with '()' writes numeric, cell, and table data element-wise %!test # numeric right-hand side %! T = table ([1;2;3], [10;20;30], 'VariableNames', {'x','z'}); %! T(1:2,[1 2]) = [70 700; 80 800]; %! assert_equal (T.x, [70;80;3]); %! assert_equal (T.z, [700;800;30]); %!test # cell right-hand side assigns element-wise (not broadcast) %! T = table ([1;2;3], [10;20;30], 'VariableNames', {'x','z'}); %! T(1:2,1) = {7; 8}; %! assert_equal (T.x, [7;8;3]); %! T(1:2,[1 2]) = {70 700; 80 800}; %! assert_equal (T.x, [70;80;3]); %! assert_equal (T.z, [700;800;30]); %!test # cellstr variable keeps its contents through cell assignment %! T = table ({'a';'b';'c'}, 'VariableNames', {'y'}); %! T(1:2,1) = {'P'; 'Q'}; %! assert_equal (T.y, {'P';'Q';'c'}); %!test # table right-hand side assigns element-wise across variables %! T = table ([1;2;3], {'a';'b';'c'}, 'VariableNames', {'x','y'}); %! T2 = table ([77;88], {'P';'Q'}, 'VariableNames', {'x','y'}); %! T(1:2,[1 2]) = T2; %! assert_equal (T.x, [77;88;3]); %! assert_equal (T.y, {'P';'Q';'c'}); %!test # single-element assignment %! T = table ([1;2;3], 'VariableNames', {'x'}); %! T(1,1) = {99}; %! assert_equal (T.x, [99;2;3]); ## Test input validation for 'subsasgn' with '()' indexing %!error ... %! T = table ([1;2;3]); T(1) = 5; %!error ... %! T = table ([1;2;3], 'VariableNames', {'x'}); T(1,1) = [9 9]; %!error ... %! T = table ([1;2;3], 'VariableNames', {'x'}); T(1,1) = {[1 2 3]}; ## 'subsasgn' with '{}' is not allowed for assigning values %!error ... %! T = table ([1;2;3]); T{1,1} = 5; ## 'subsasgn' with '.' adds, replaces, and scalar-expands variables %!test # add a new variable %! T = table ([1;2;3], 'VariableNames', {'x'}); %! T.y = [4;5;6]; %! assert_equal (T.Properties.VariableNames, {'x','y'}); %! assert_equal (T.y, [4;5;6]); %!test # replace an existing variable %! T = table ([1;2;3], 'VariableNames', {'x'}); %! T.x = [7;8;9]; %! assert_equal (T.x, [7;8;9]); %!test # scalar right-hand side is expanded to the table height %! T = table ([1;2;3], 'VariableNames', {'x'}); %! T.x = 5; %! assert_equal (T.x, [5;5;5]); %!test # chained assignment into a single variable %! T = table ([1;2;3], 'VariableNames', {'x'}); %! T.x(2) = 77; %! assert_equal (T.x, [1;77;3]); ## Test input validation for 'subsasgn' with '.' indexing %!error ... %! T = table ([1;2;3]); subsasgn (T, struct ('type', '.', 'subs', 5), 1); %!error ... %! T = table ([1;2;3], 'VariableNames', {'x'}); T.y = [4;5]; ## 'subsasgn' sets table-level Properties %!test # Description and UserData %! T = table ([1;2;3], 'VariableNames', {'x'}); %! T.Properties.Description = 'a table'; %! assert_equal (T.Properties.Description, 'a table'); %! T.Properties.UserData = {1, 'two', 3}; %! assert_equal (T.Properties.UserData, {1, 'two', 3}); %!test # DimensionNames, full and indexed %! T = table ([1;2;3], 'VariableNames', {'x'}); %! T.Properties.DimensionNames = {'Rows', 'Vars'}; %! assert_equal (T.Properties.DimensionNames, {'Rows', 'Vars'}); %! T.Properties.DimensionNames(1) = 'Patients'; %! assert_equal (T.Properties.DimensionNames, {'Patients', 'Vars'}); %!test # VariableNames, full and indexed %! T = table ([1;2;3], [4;5;6], 'VariableNames', {'x','y'}); %! T.Properties.VariableNames = {'a', 'b'}; %! assert_equal (T.Properties.VariableNames, {'a', 'b'}); %! T.Properties.VariableNames(2) = {'B'}; %! assert_equal (T.Properties.VariableNames, {'a', 'B'}); %!test # VariableDescriptions and VariableUnits, full and indexed %! T = table ([1;2;3], [4;5;6], 'VariableNames', {'x','y'}); %! T.Properties.VariableDescriptions = {'first', 'second'}; %! assert_equal (T.Properties.VariableDescriptions, {'first', 'second'}); %! T.Properties.VariableDescriptions(1) = {'1st'}; %! assert_equal (T.Properties.VariableDescriptions, {'1st', 'second'}); %! T.Properties.VariableUnits = {'m', 's'}; %! assert_equal (T.Properties.VariableUnits, {'m', 's'}); %! T.Properties.VariableUnits(2) = {'kg'}; %! assert_equal (T.Properties.VariableUnits, {'m', 'kg'}); %!test # VariableTypes converts the underlying data (whole table) %! T = table ([1;2;3], [10;20;30], 'VariableNames', {'x','z'}); %! T.Properties.VariableTypes = {'single', 'int32'}; %! assert_equal (class (T.x), 'single'); %! assert_equal (class (T.z), 'int32'); %! assert_equal (T.x, single ([1;2;3])); %! assert_equal (T.z, int32 ([10;20;30])); %! assert_equal (T.Properties.VariableTypes, {'single', 'int32'}); %! assert_equal (T.Properties.VariableDescriptions, {'', ''}); %!test # VariableTypes converts a single indexed variable %! T = table ([1;2;3], [10;20;30], 'VariableNames', {'x','z'}); %! T.Properties.VariableTypes(2) = {'single'}; %! assert_equal (class (T.x), 'double'); %! assert_equal (class (T.z), 'single'); %! assert_equal (T.Properties.VariableTypes, {'double', 'single'}); %!test # RowNames, set from an array and removed with [] %! T = table ([1;2;3], 'VariableNames', {'x'}); %! T.Properties.RowNames = {'r1', 'r2', 'r3'}; %! assert_equal (T.Properties.RowNames, {'r1';'r2';'r3'}); %! T.Properties.RowNames = {}; %! assert_equal (isempty (T.Properties.RowNames), true); ## Test input validation for 'subsasgn' Properties assignment %!error ... %! T = table ([1;2;3]); subsasgn (T, substruct ('.', 'Properties'), 5); %!error ... %! T = table ([1;2;3]); T.Properties.Description = 5; %!error ... %! T = table ([1;2;3], [4;5;6], 'VariableNames', {'x','y'}); ... %! T.Properties.VariableNames = {'only_one'}; %!error ... %! T = table ([1;2;3], 'VariableNames', {'x'}); ... %! T.Properties.VariableNames(5) = {'a'}; %!error ... %! T = table ([1;2;3], [4;5;6], 'VariableNames', {'x','y'}); ... %! T.Properties.VariableTypes = {'double'}; %!error ... %! T = table ([1;2;3], 'VariableNames', {'x'}); ... %! T.Properties.RowNames = {'r1', 'r2'}; %!error ... %! T = table ([1;2;3], 'VariableNames', {'x'}); ... %! T.Properties.RowNames = {'r1', 'r1', 'r2'}; %!test # RowNames set from a variable with unique values moves and drops it %! T = table ({'x'; 'y'; 'z'}, [1; 2; 3], 'VariableNames', {'G', 'V'}); %! T.Properties.RowNames = 'G'; %! assert_equal (T.Properties.RowNames, {'x'; 'y'; 'z'}); %! assert_equal (T.Properties.VariableNames, {'V'}); %!error ... %! T = table ({'x'; 'y'; 'x'}, [1; 2; 3], 'VariableNames', {'G', 'V'}); ... %! T.Properties.RowNames = 'G'; ## 'subsasgn' sets custom properties through Properties.CustomProperties %!test # table-scoped and variable-scoped custom properties %! T = table ([1;2;3], [4;5;6], 'VariableNames', {'x','y'}); %! T = addprop (T, {'tp', 'vp'}, {'table', 'variable'}); %! T.Properties.CustomProperties.tp = 'meta'; %! assert_equal (T.Properties.CustomProperties.tp, 'meta'); %! T.Properties.CustomProperties.vp = [10 20]; %! assert_equal (T.Properties.CustomProperties.vp, [10 20]); %! T.Properties.CustomProperties.vp(2) = 99; %! assert_equal (T.Properties.CustomProperties.vp, [10 99]); ## Test input validation for 'subsasgn' custom property assignment %!error ... %! T = table ([1;2;3]); T.Properties.CustomProperties.nope = 5; %!error ... %! T = table ([1;2;3]); T.Properties.CustomProperties = 5; pr0m1th3as-datatypes-9c9a8d3/inst/tzdata/000077500000000000000000000000001522766574100203215ustar00rootroot00000000000000pr0m1th3as-datatypes-9c9a8d3/inst/tzdata/LICENSE000066400000000000000000000003741522766574100213320ustar00rootroot00000000000000Unless specified below, all files in the tz code and data (including this LICENSE file) are in the public domain. If the files date.c, newstrftime.3, and strftime.c are present, they contain material derived from BSD and use the BSD 3-clause license. pr0m1th3as-datatypes-9c9a8d3/inst/tzdata/africa000066400000000000000000001616411522766574100215020ustar00rootroot00000000000000# tzdb data for Africa and environs # This file is in the public domain, so clarified as of # 2009-05-17 by Arthur David Olson. # This file is by no means authoritative; if you think you know better, # go ahead and edit the file, and please send any changes to # the public mailing list tz@iana.org for general use in the future. # For more, please see the file CONTRIBUTING in the tz distribution. # From Paul Eggert (2018-05-27): # # Unless otherwise specified, the source for data through 1990 is: # Thomas G. Shanks and Rique Pottenger, The International Atlas (6th edition), # San Diego: ACS Publications, Inc. (2003). # Unfortunately this book contains many errors and cites no sources. # # Many years ago Gwillim Law wrote that a good source # for time zone data was the International Air Transport # Association's Standard Schedules Information Manual (IATA SSIM), # published semiannually. Law sent in several helpful summaries # of the IATA's data after 1990. Except where otherwise noted, # IATA SSIM is the source for entries after 1990. # # Another source occasionally used is Edward W. Whitman, World Time Differences, # Whitman Publishing Co, 2 Niagara Av, Ealing, London (undated), which # I found in the UCLA library. # # For data circa 1899, a common source is: # Milne J. Civil time. Geogr J. 1899 Feb;13(2):173-94. # https://www.jstor.org/stable/1774359 # # For the 1911/1912 establishment of standard time in French possessions, see: # Société Française de Physique, Recueil de constantes physiques (1913), # page 752, 18b. # # European-style abbreviations are commonly used along the Mediterranean. # For sub-Saharan Africa abbreviations were less standardized. # Previous editions of this database used WAT, CAT, SAT, and EAT # for UT +00 through +03, respectively, # but in 1997 Mark R V Murray reported that # 'SAST' is the official abbreviation for +02 in the country of South Africa, # 'CAT' is commonly used for +02 in countries north of South Africa, and # 'WAT' is probably the best name for +01, as the common phrase for # the area that includes Nigeria is "West Africa". # # To summarize, the following abbreviations seemed to have some currency: # +00 GMT Greenwich Mean Time # +02 CAT Central Africa Time # +02 SAST South Africa Standard Time # and Murray suggested the following abbreviation: # +01 WAT West Africa Time # Murray's suggestion seems to have caught on in news reports and the like. # I vaguely recall 'WAT' also being used for -01 in the past but # cannot now come up with solid citations. # # I invented the following abbreviations in the 1990s: # +02 WAST West Africa Summer Time # +03 CAST Central Africa Summer Time # +03 SAST South Africa Summer Time # +03 EAT East Africa Time # 'EAT' seems to have caught on and is in current timestamps, and though # the other abbreviations are rarer and are only in past timestamps, # they are paired with better-attested non-DST abbreviations. # Corrections are welcome. # Algeria # Rule NAME FROM TO - IN ON AT SAVE LETTER/S Rule Algeria 1916 only - Jun 14 23:00s 1:00 S Rule Algeria 1916 1919 - Oct Sun>=1 23:00s 0 - Rule Algeria 1917 only - Mar 24 23:00s 1:00 S Rule Algeria 1918 only - Mar 9 23:00s 1:00 S Rule Algeria 1919 only - Mar 1 23:00s 1:00 S Rule Algeria 1920 only - Feb 14 23:00s 1:00 S Rule Algeria 1920 only - Oct 23 23:00s 0 - Rule Algeria 1921 only - Mar 14 23:00s 1:00 S Rule Algeria 1921 only - Jun 21 23:00s 0 - Rule Algeria 1939 only - Sep 11 23:00s 1:00 S Rule Algeria 1939 only - Nov 19 1:00 0 - Rule Algeria 1944 1945 - Apr Mon>=1 2:00 1:00 S Rule Algeria 1944 only - Oct 8 2:00 0 - Rule Algeria 1945 only - Sep 16 1:00 0 - Rule Algeria 1971 only - Apr 25 23:00s 1:00 S Rule Algeria 1971 only - Sep 26 23:00s 0 - Rule Algeria 1977 only - May 6 0:00 1:00 S Rule Algeria 1977 only - Oct 21 0:00 0 - Rule Algeria 1978 only - Mar 24 1:00 1:00 S Rule Algeria 1978 only - Sep 22 3:00 0 - Rule Algeria 1980 only - Apr 25 0:00 1:00 S Rule Algeria 1980 only - Oct 31 2:00 0 - # See Europe/Paris for PMT-related transitions. # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone Africa/Algiers 0:12:12 - LMT 1891 Mar 16 0:09:21 - PMT 1911 Mar 11 # Paris Mean Time 0:00 Algeria WE%sT 1940 Feb 25 2:00 1:00 Algeria CE%sT 1946 Oct 7 0:00 - WET 1956 Jan 29 1:00 - CET 1963 Apr 14 0:00 Algeria WE%sT 1977 Oct 21 1:00 Algeria CE%sT 1979 Oct 26 0:00 Algeria WE%sT 1981 May 1:00 - CET # Cape Verde / Cabo Verde # # From Tim Parenti (2024-07-01), per Paul Eggert (2018-02-16): # For timestamps before independence, see commentary for Europe/Lisbon. # Shanks gives 1907 instead for the transition to -02. # # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone Atlantic/Cape_Verde -1:34:04 - LMT 1912 Jan 01 2:00u # Praia -2:00 - %z 1942 Sep -2:00 1:00 %z 1945 Oct 15 -2:00 - %z 1975 Nov 25 2:00 -1:00 - %z # Chad # Fort-Lamy was renamed to N’Djamena on 1973-04-06. # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone Africa/Ndjamena 1:00:12 - LMT 1912 Jan 1 # Fort-Lamy 1:00 - WAT 1979 Oct 14 1:00 1:00 WAST 1980 Mar 8 1:00 - WAT # Burkina Faso # Côte d'Ivoire (Ivory Coast) # The Gambia # Ghana # Guinea # Iceland # Mali # Mauritania # St Helena # Senegal # Sierra Leone # Togo # The other parts of the St Helena territory are similar: # Tristan da Cunha: on GMT, say Whitman and the CIA # Ascension: on GMT, say the USNO (1995-12-21) and the CIA # Gough (scientific station since 1955; sealers wintered previously): # on GMT, says the CIA # Inaccessible, Nightingale: uninhabited # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone Africa/Abidjan -0:16:08 - LMT 1912 Jan 1 0:00 - GMT ############################################################################### # Egypt # Milne says Cairo used 2:05:08.9, the local mean time of the Abbasizeh # observatory. Milne also says that the official time for # Egypt was mean noon at the Great Pyramid, 2:04:30.5, but apparently this # did not apply to Cairo, Alexandria, or Port Said. # Rule NAME FROM TO - IN ON AT SAVE LETTER/S Rule Egypt 1940 only - Jul 15 0:00 1:00 S Rule Egypt 1940 only - Oct 1 0:00 0 - Rule Egypt 1941 only - Apr 15 0:00 1:00 S Rule Egypt 1941 only - Sep 16 0:00 0 - Rule Egypt 1942 1944 - Apr 1 0:00 1:00 S Rule Egypt 1942 only - Oct 27 0:00 0 - Rule Egypt 1943 1945 - Nov 1 0:00 0 - Rule Egypt 1945 only - Apr 16 0:00 1:00 S Rule Egypt 1957 only - May 10 0:00 1:00 S Rule Egypt 1957 1958 - Oct 1 0:00 0 - Rule Egypt 1958 only - May 1 0:00 1:00 S Rule Egypt 1959 1981 - May 1 1:00 1:00 S Rule Egypt 1959 1965 - Sep 30 3:00 0 - Rule Egypt 1966 1994 - Oct 1 3:00 0 - Rule Egypt 1982 only - Jul 25 1:00 1:00 S Rule Egypt 1983 only - Jul 12 1:00 1:00 S Rule Egypt 1984 1988 - May 1 1:00 1:00 S Rule Egypt 1989 only - May 6 1:00 1:00 S Rule Egypt 1990 1994 - May 1 1:00 1:00 S # IATA (after 1990) says transitions are at 0:00. # Go with IATA starting in 1995, except correct 1995 entry from 09-30 to 09-29. # From Alexander Krivenyshev (2011-04-20): # "...Egypt's interim cabinet decided on Wednesday to cancel daylight # saving time after a poll posted on its website showed the majority of # Egyptians would approve the cancellation." # # Egypt to cancel daylight saving time # http://www.almasryalyoum.com/en/node/407168 # or # http://www.worldtimezone.com/dst_news/dst_news_egypt04.html Rule Egypt 1995 2010 - Apr lastFri 0:00s 1:00 S Rule Egypt 1995 2005 - Sep lastThu 24:00 0 - # From Steffen Thorsen (2006-09-19): # The Egyptian Gazette, issue 41,090 (2006-09-18), page 1, reports: # Egypt will turn back clocks by one hour at the midnight of Thursday # after observing the daylight saving time since May. # http://news.gom.com.eg/gazette/pdf/2006/09/18/01.pdf Rule Egypt 2006 only - Sep 21 24:00 0 - # From Dirk Losch (2007-08-14): # I received a mail from an airline which says that the daylight # saving time in Egypt will end in the night of 2007-09-06 to 2007-09-07. # From Jesper Nørgaard Welen (2007-08-15): [The following agree:] # http://www.nentjes.info/Bill/bill5.htm # https://www.timeanddate.com/worldclock/city.html?n=53 # From Steffen Thorsen (2007-09-04): The official information...: # http://www.sis.gov.eg/En/EgyptOnline/Miscellaneous/000002/0207000000000000001580.htm Rule Egypt 2007 only - Sep Thu>=1 24:00 0 - # From Abdelrahman Hassan (2007-09-06): # Due to the Hijri (lunar Islamic calendar) year being 11 days shorter # than the year of the Gregorian calendar, Ramadan shifts earlier each # year. This year it will be observed September 13 (September is quite # hot in Egypt), and the idea is to make fasting easier for workers by # shifting business hours one hour out of daytime heat. Consequently, # unless discontinued, next DST may end Thursday 28 August 2008. # From Paul Eggert (2007-08-17): # For lack of better info, assume the new rule is last Thursday in August. # From Petr Machata (2009-04-06): # The following appeared in Red Hat bugzilla[1] (edited): # # > $ zdump -v /usr/share/zoneinfo/Africa/Cairo | grep 2009 # > /usr/share/zoneinfo/Africa/Cairo Thu Apr 23 21:59:59 2009 UTC = Thu = # Apr 23 # > 23:59:59 2009 EET isdst=0 gmtoff=7200 # > /usr/share/zoneinfo/Africa/Cairo Thu Apr 23 22:00:00 2009 UTC = Fri = # Apr 24 # > 01:00:00 2009 EEST isdst=1 gmtoff=10800 # > /usr/share/zoneinfo/Africa/Cairo Thu Aug 27 20:59:59 2009 UTC = Thu = # Aug 27 # > 23:59:59 2009 EEST isdst=1 gmtoff=10800 # > /usr/share/zoneinfo/Africa/Cairo Thu Aug 27 21:00:00 2009 UTC = Thu = # Aug 27 # > 23:00:00 2009 EET isdst=0 gmtoff=7200 # # > end date should be Thu Sep 24 2009 (Last Thursday in September at 23:59= # :59) # > http://support.microsoft.com/kb/958729/ # # timeanddate[2] and another site I've found[3] also support that. # # [1] https://bugzilla.redhat.com/show_bug.cgi?id=492263 # [2] https://www.timeanddate.com/worldclock/clockchange.html?n=53 # [3] https://wwp.greenwichmeantime.com/time-zone/africa/egypt/ # From Arthur David Olson (2009-04-20): # In 2009 (and for the next several years), Ramadan ends before the fourth # Thursday in September; Egypt is expected to revert to the last Thursday # in September. # From Steffen Thorsen (2009-08-11): # We have been able to confirm the August change with the Egyptian Cabinet # Information and Decision Support Center: # https://www.timeanddate.com/news/time/egypt-dst-ends-2009.html # # The Middle East News Agency # https://www.mena.org.eg/index.aspx # also reports "Egypt starts winter time on August 21" # today in article numbered "71, 11/08/2009 12:25 GMT." # Only the title above is available without a subscription to their service, # and can be found by searching for "winter" in their search engine # (at least today). # From Alexander Krivenyshev (2010-07-20): # According to News from Egypt - Al-Masry Al-Youm Egypt's cabinet has # decided that Daylight Saving Time will not be used in Egypt during # Ramadan. # # Arabic translation: # "Clocks to go back during Ramadan - and then forward again" # http://www.almasryalyoum.com/en/news/clocks-go-back-during-ramadan-and-then-forward-again # http://www.worldtimezone.com/dst_news/dst_news_egypt02.html # From Ahmad El-Dardiry (2014-05-07): # Egypt is to change back to Daylight system on May 15 # http://english.ahram.org.eg/NewsContent/1/64/100735/Egypt/Politics-/Egypts-government-to-reapply-daylight-saving-time-.aspx # From Gunther Vermier (2014-05-13): # our Egypt office confirms that the change will be at 15 May "midnight" (24:00) # From Imed Chihi (2014-06-04): # We have finally "located" a precise official reference about the DST changes # in Egypt. The Ministers Cabinet decision is explained at # http://www.cabinet.gov.eg/Media/CabinetMeetingsDetails.aspx?id=347 ... # [T]his (Arabic) site is not accessible outside Egypt, but the page ... # translates into: "With regard to daylight saving time, it is scheduled to # take effect at exactly twelve o'clock this evening, Thursday, 15 MAY 2014, # to be suspended by twelve o'clock on the evening of Thursday, 26 JUN 2014, # and re-established again at the end of the month of Ramadan, at twelve # o'clock on the evening of Thursday, 31 JUL 2014." This statement has been # reproduced by other (more accessible) sites[, e.g.,]... # http://elgornal.net/news/news.aspx?id=4699258 # From Steffen Thorsen (2015-04-08): # Egypt will start DST on midnight after Thursday, April 30, 2015. # This is based on a law (no 35) from May 15, 2014 saying it starts the last # Thursday of April.... Clocks will still be turned back for Ramadan, but # dates not yet announced.... # http://almogaz.com/news/weird-news/2015/04/05/1947105 ... # https://www.timeanddate.com/news/time/egypt-starts-dst-2015.html # From Ahmed Nazmy (2015-04-20): # Egypt's ministers cabinet just announced ... that it will cancel DST at # least for 2015. # # From Tim Parenti (2015-04-20): # http://english.ahram.org.eg/WriterArticles/NewsContentP/1/128195/Egypt/No-daylight-saving-this-summer-Egypts-prime-minist.aspx # "Egypt's cabinet agreed on Monday not to switch clocks for daylight saving # time this summer, and carry out studies on the possibility of canceling the # practice altogether in future years." # # From Paul Eggert (2015-04-24): # Yesterday the office of Egyptian President El-Sisi announced his # decision to abandon DST permanently. See Ahram Online 2015-04-24. # http://english.ahram.org.eg/NewsContent/1/64/128509/Egypt/Politics-/Sisi-cancels-daylight-saving-time-in-Egypt.aspx # From Steffen Thorsen (2016-04-29): # Egypt will have DST from July 7 until the end of October.... # http://english.ahram.org.eg/NewsContentP/1/204655/Egypt/Daylight-savings-time-returning-to-Egypt-on--July.aspx # From Mina Samuel (2016-07-04): # Egyptian government took the decision to cancel the DST, # From Ahmad ElDardiry (2023-03-01): # Egypt officially announced today that daylight savings will be # applied from last Friday of April to last Thursday of October. # From Paul Eggert (2023-03-01): # Assume transitions are at 00:00 and 24:00 respectively. # From Amir Adib (2023-03-07): # https://www.facebook.com/EgyptianCabinet/posts/638829614954129/ Rule Egypt 2008 only - Aug lastThu 24:00 0 - Rule Egypt 2009 only - Aug 20 24:00 0 - Rule Egypt 2010 only - Aug 10 24:00 0 - Rule Egypt 2010 only - Sep 9 24:00 1:00 S Rule Egypt 2010 only - Sep lastThu 24:00 0 - Rule Egypt 2014 only - May 15 24:00 1:00 S Rule Egypt 2014 only - Jun 26 24:00 0 - Rule Egypt 2014 only - Jul 31 24:00 1:00 S Rule Egypt 2014 only - Sep lastThu 24:00 0 - Rule Egypt 2023 max - Apr lastFri 0:00 1:00 S Rule Egypt 2023 max - Oct lastThu 24:00 0 - # Zone NAME STDOFF RULES FORMAT [UNTIL] #STDOFF 2:05:08.9 Zone Africa/Cairo 2:05:09 - LMT 1900 Oct 2:00 Egypt EE%sT # Guinea-Bissau # # From Tim Parenti (2024-07-01), per Paul Eggert (2018-02-16): # For timestamps before independence, see commentary for Europe/Lisbon. # # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone Africa/Bissau -1:02:20 - LMT 1912 Jan 1 1:00u -1:00 - %z 1975 0:00 - GMT # Comoros # Djibouti # Eritrea # Ethiopia # Kenya # Madagascar # Mayotte # Somalia # Tanzania # Uganda # From P Chan (2020-10-24): # # The standard time of GMT+2:30 was adopted in the East Africa Protectorate.... # [The Official Gazette, 1908-05-01, p 274] # https://books.google.com/books?id=e-cAC-sjPSEC&pg=PA274 # # At midnight on 30 June 1928 the clocks throughout Kenya was put forward # half an hour by the Alteration of Time Ordinance, 1928. # https://gazettes.africa/archive/ke/1928/ke-government-gazette-dated-1928-05-11-no-28.pdf # [Ordinance No. 11 of 1928, The Official Gazette, 1928-06-26, p 813] # https://books.google.com/books?id=2S0S6os32ZUC&pg=PA813 # # The 1928 ordinance was repealed by the Alteration of Time (repeal) Ordinance, # 1929 and the time was restored to GMT+2:30 at midnight on 4 January 1930. # [Ordinance No. 97 of 1929, The Official Gazette, 1929-12-31, p 2701] # https://books.google.com/books?id=_g18jIZQlwwC&pg=PA2701 # # The Alteration of Time Ordinance, 1936 changed the time to GMT+2:45 # and repealed the previous ordinance at midnight on 31 December 1936. # [The Official Gazette, 1936-07-21, p 705] # https://books.google.com/books?id=K7j41z0aC5wC&pg=PA705 # # The Defence (Amendment of Laws No. 120) Regulations changed the time # to GMT+3 at midnight on 31 July 1942. # [Kenya Official Gazette Supplement No. 32, 1942-07-21, p 331] # https://books.google.com/books?hl=zh-TW&id=c_E-AQAAIAAJ&pg=PA331 # The provision of the 1936 ordinance was not repealed and was later # incorporated in the Interpretation and General Clauses Ordinance in 1948. # Although it was overridden by the 1942 regulations. # [The Laws of Kenya in force on 1948-09-21, Title I, Chapter 1, 31] # https://dds.crl.edu/item/217517 (p.101) # In 1950 the Interpretation and General Clauses Ordinance was amended to adopt # GMT+3 permanently as the 1942 regulations were due to expire on 10 December. # https://books.google.com/books?id=jvR8mUDAwR0C&pg=PA787 # [Ordinance No. 44 of 1950, Kenya Ordinances 1950, Vol. XXIX, p 294] # https://books.google.com/books?id=-_dQAQAAMAAJ&pg=PA294 # From Paul Eggert (2020-10-24): # The 1908-05-01 announcement does not give an effective date, # so just say "1908 May". # From Paul Eggert (2018-09-11): # Unfortunately tzdb records only Western clock time in use in Ethiopia, # as the tzdb format is not up to properly recording a common Ethiopian # timekeeping practice that is based on solar time. See: # Mortada D. If you have a meeting in Ethiopia, you'd better double # check the time. PRI's The World. 2015-01-30 15:15 -05. # https://www.pri.org/stories/2015-01-30/if-you-have-meeting-ethiopia-you-better-double-check-time # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone Africa/Nairobi 2:27:16 - LMT 1908 May 2:30 - %z 1928 Jun 30 24:00 3:00 - EAT 1930 Jan 4 24:00 2:30 - %z 1936 Dec 31 24:00 2:45 - %z 1942 Jul 31 24:00 3:00 - EAT # Liberia # # From Paul Eggert (2017-03-02): # # The Nautical Almanac for the Year 1970, p 264, is the source for -0:44:30. # # In 1972 Liberia was the last country to switch from a UT offset # that was not a multiple of 15 or 20 minutes. The 1972 change was on # 1972-01-07, according to an entry dated 1972-01-04 on p 330 of: # Presidential Papers: First year of the administration of # President William R. Tolbert, Jr., July 23, 1971-July 31, 1972. # Monrovia: Executive Mansion. # # Use the abbreviation "MMT" before 1972, as the more accurate numeric # abbreviation "-004430" would be one byte over the POSIX limit. # # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone Africa/Monrovia -0:43:08 - LMT 1882 -0:43:08 - MMT 1919 Mar # Monrovia Mean Time -0:44:30 - MMT 1972 Jan 7 # approximately MMT 0:00 - GMT ############################################################################### # Libya # From Even Scharning (2012-11-10): # Libya set their time one hour back at 02:00 on Saturday November 10. # https://www.libyaherald.com/2012/11/04/clocks-to-go-back-an-hour-on-saturday/ # Here is an official source [in Arabic]: http://ls.ly/fb6Yc # # Steffen Thorsen forwarded a translation (2012-11-10) in # https://mm.icann.org/pipermail/tz/2012-November/018451.html # # From Tim Parenti (2012-11-11): # Treat the 2012-11-10 change as a zone change from UTC+2 to UTC+1. # The DST rules planned for 2013 and onward roughly mirror those of Europe # (either two days before them or five days after them, so as to fall on # lastFri instead of lastSun). # From Even Scharning (2013-10-25): # The scheduled end of DST in Libya on Friday, October 25, 2013 was # cancelled yesterday.... # https://www.libyaherald.com/2013/10/24/correction-no-time-change-tomorrow/ # # From Paul Eggert (2013-10-25): # For now, assume they're reverting to the pre-2012 rules of permanent UT +02. # Rule NAME FROM TO - IN ON AT SAVE LETTER/S Rule Libya 1951 only - Oct 14 2:00 1:00 S Rule Libya 1952 only - Jan 1 0:00 0 - Rule Libya 1953 only - Oct 9 2:00 1:00 S Rule Libya 1954 only - Jan 1 0:00 0 - Rule Libya 1955 only - Sep 30 0:00 1:00 S Rule Libya 1956 only - Jan 1 0:00 0 - Rule Libya 1982 1984 - Apr 1 0:00 1:00 S Rule Libya 1982 1985 - Oct 1 0:00 0 - Rule Libya 1985 only - Apr 6 0:00 1:00 S Rule Libya 1986 only - Apr 4 0:00 1:00 S Rule Libya 1986 only - Oct 3 0:00 0 - Rule Libya 1987 1989 - Apr 1 0:00 1:00 S Rule Libya 1987 1989 - Oct 1 0:00 0 - Rule Libya 1997 only - Apr 4 0:00 1:00 S Rule Libya 1997 only - Oct 4 0:00 0 - Rule Libya 2013 only - Mar lastFri 1:00 1:00 S Rule Libya 2013 only - Oct lastFri 2:00 0 - # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone Africa/Tripoli 0:52:44 - LMT 1920 1:00 Libya CE%sT 1959 2:00 - EET 1982 1:00 Libya CE%sT 1990 May 4 # The 1996 and 1997 entries are from Shanks & Pottenger; # the IATA SSIM data entries contain some obvious errors. 2:00 - EET 1996 Sep 30 1:00 Libya CE%sT 1997 Oct 4 2:00 - EET 2012 Nov 10 2:00 1:00 Libya CE%sT 2013 Oct 25 2:00 2:00 - EET # Mauritius # From Steffen Thorsen (2008-06-25): # Mauritius plans to observe DST from 2008-11-01 to 2009-03-31 on a trial # basis.... # It seems that Mauritius observed daylight saving time from 1982-10-10 to # 1983-03-20 as well, but that was not successful.... # https://www.timeanddate.com/news/time/mauritius-daylight-saving-time.html # From Alex Krivenyshev (2008-06-25): # http://economicdevelopment.gov.mu/portal/site/Mainhomepage/menuitem.a42b24128104d9845dabddd154508a0c/?content_id=0a7cee8b5d69a110VgnVCM1000000a04a8c0RCRD # From Arthur David Olson (2008-06-30): # The www.timeanddate.com article cited by Steffen Thorsen notes that "A # final decision has yet to be made on the times that daylight saving # would begin and end on these dates." As a place holder, use midnight. # From Paul Eggert (2008-06-30): # Follow Thorsen on DST in 1982/1983, instead of Shanks & Pottenger. # From Steffen Thorsen (2008-07-10): # According to # http://www.lexpress.mu/display_article.php?news_id=111216 # (in French), Mauritius will start and end their DST a few days earlier # than previously announced (2008-11-01 to 2009-03-31). The new start # date is 2008-10-26 at 02:00 and the new end date is 2009-03-27 (no time # given, but it is probably at either 2 or 3 wall clock time). # # A little strange though, since the article says that they moved the date # to align itself with Europe and USA which also change time on that date, # but that means they have not paid attention to what happened in # USA/Canada last year (DST ends first Sunday in November). I also wonder # why that they end on a Friday, instead of aligning with Europe which # changes two days later. # From Alex Krivenyshev (2008-07-11): # Seems that English language article "The revival of daylight saving # time: Energy conservation?"- No. 16578 (07/11/2008) was originally # published on Monday, June 30, 2008... # # I guess that article in French "Le gouvernement avance l'introduction # de l'heure d'été" stating that DST in Mauritius starting on October 26 # and ending on March 27, 2009 is the most recent one.... # http://www.worldtimezone.com/dst_news/dst_news_mauritius02.html # From Riad M. Hossen Ally (2008-08-03): # The Government of Mauritius weblink # http://www.gov.mu/portal/site/pmosite/menuitem.4ca0efdee47462e7440a600248a521ca/?content_id=4728ca68b2a5b110VgnVCM1000000a04a8c0RCRD # Cabinet Decision of July 18th, 2008 states as follows: # # 4. ...Cabinet has agreed to the introduction into the National Assembly # of the Time Bill which provides for the introduction of summer time in # Mauritius. The summer time period which will be of one hour ahead of # the standard time, will be aligned with that in Europe and the United # States of America. It will start at two o'clock in the morning on the # last Sunday of October and will end at two o'clock in the morning on # the last Sunday of March the following year. The summer time for the # year 2008-2009 will, therefore, be effective as from 26 October 2008 # and end on 29 March 2009. # From Ed Maste (2008-10-07): # THE TIME BILL (No. XXVII of 2008) Explanatory Memorandum states the # beginning / ending of summer time is 2 o'clock standard time in the # morning of the last Sunday of October / last Sunday of March. # http://www.gov.mu/portal/goc/assemblysite/file/bill2708.pdf # From Steffen Thorsen (2009-06-05): # According to several sources, Mauritius will not continue to observe # DST the coming summer... # # Some sources, in French: # http://www.defimedia.info/news/946/Rashid-Beebeejaun-:-«-L%E2%80%99heure-d%E2%80%99été-ne-sera-pas-appliquée-cette-année-» # http://lexpress.mu/Story/3398~Beebeejaun---Les-objectifs-d-économie-d-énergie-de-l-heure-d-été-ont-été-atteints- # # Our wrap-up: # https://www.timeanddate.com/news/time/mauritius-dst-will-not-repeat.html # From Arthur David Olson (2009-07-11): # The "mauritius-dst-will-not-repeat" wrapup includes this: # "The trial ended on March 29, 2009, when the clocks moved back by one hour # at 2am (or 02:00) local time..." # Rule NAME FROM TO - IN ON AT SAVE LETTER/S Rule Mauritius 1982 only - Oct 10 0:00 1:00 - Rule Mauritius 1983 only - Mar 21 0:00 0 - Rule Mauritius 2008 only - Oct lastSun 2:00 1:00 - Rule Mauritius 2009 only - Mar lastSun 2:00 0 - # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone Indian/Mauritius 3:50:00 - LMT 1907 # Port Louis 4:00 Mauritius %z # Agalega Is, Rodriguez # no information; probably like Indian/Mauritius # Morocco # See Africa/Ceuta for Spanish Morocco. # From Paul Eggert (2026-06-26): # In “Morocco’s GMT+1, a century of shifting time and a debate far from over” # https://en.yabiladi.com/articles/details/191310/morocco-s-gmt1-century-shifting-time # (2026-03-30), Yabiladi’s Latifa Babas reports the following: # * A 1913-10-26 royal dahir established GMT as legal time in Morocco. # * A 1918-05-10 dahir instituted DST on 1918-05-16 at 00:00. # * A September 1939 dahir restarted DST on 1939-09-12. # * A February 1940 dahir restarted DST as early as 1940-02-25. # * Standard time resumed in September 1946. # * A June 1950 decree restarted DST, which ran until as late as October. # * Royal decree 455-67 (1967-06-02) restarted DST on June 3 at noon. # * After 1967 Morocco used DST “during several summers, particularly # throughout the 1970s and 1980s.” # Babas consulted official records that disagree with and are surely # more correct than our pre-2008 timestamp data, which came from the # unreliable Shanks & Pottenger. Unfortunately, Babas did not provide # enough detail to correct our data. # From Alex Krivenyshev (2008-05-09): # Here is an article that Morocco plan to introduce Daylight Saving Time between # 1 June, 2008 and 27 September, 2008. # # "... Morocco is to save energy by adjusting its clock during summer so it will # be one hour ahead of GMT between 1 June and 27 September, according to # Communication Minister and Government Spokesman, Khalid Naciri...." # # http://www.worldtimezone.com/dst_news/dst_news_morocco01.html # http://en.afrik.com/news11892.html # From Alex Krivenyshev (2008-05-09): # The Morocco time change can be confirmed on Morocco web site Maghreb Arabe # Presse: # http://www.map.ma/eng/sections/box3/morocco_shifts_to_da/view # # Morocco shifts to daylight time on June 1st through September 27, Govt. # spokesman. # From Patrice Scattolin (2008-05-09): # According to this article: # https://www.avmaroc.com/actualite/heure-dete-comment-a127896.html # (and republished here: ) # the changes occur at midnight: # # Saturday night May 31st at midnight (which in French is to be # interpreted as the night between Saturday and Sunday) # Sunday night the 28th at midnight # # Seeing that the 28th is Monday, I am guessing that she intends to say # the midnight of the 28th which is the midnight between Sunday and # Monday, which jives with other sources that say that it's inclusive # June 1st to Sept 27th. # # The decision was taken by decree *2-08-224 *but I can't find the decree # published on the web. # # It's also confirmed here: # http://www.maroc.ma/NR/exeres/FACF141F-D910-44B0-B7FA-6E03733425D1.htm # on a government portal as being between June 1st and Sept 27th (not yet # posted in English). # # The following Google query will generate many relevant hits: # https://www.google.com/search?hl=en&q=Conseil+de+gouvernement+maroc+heure+avance&btnG=Search # From Steffen Thorsen (2008-08-27): # Morocco will change the clocks back on the midnight between August 31 # and September 1. They originally planned to observe DST to near the end # of September: # # One article about it (in French): # http://www.menara.ma/fr/Actualites/Maroc/Societe/ci.retour_a_l_heure_gmt_a_partir_du_dimanche_31_aout_a_minuit_officiel_.default # # We have some further details posted here: # https://www.timeanddate.com/news/time/morocco-ends-dst-early-2008.html # From Steffen Thorsen (2009-03-17): # Morocco will observe DST from 2009-06-01 00:00 to 2009-08-21 00:00 according # to many sources, such as # http://news.marweb.com/morocco/entertainment/morocco-daylight-saving.html # http://www.medi1sat.ma/fr/depeche.aspx?idp=2312 # (French) # # Our summary: # https://www.timeanddate.com/news/time/morocco-starts-dst-2009.html # From Alexander Krivenyshev (2009-03-17): # Here is a link to official document from Royaume du Maroc Premier Ministre, # Ministère de la Modernisation des Secteurs Publics # # Under Article 1 of Royal Decree No. 455-67 of Act 23 safar 1387 (2 June 1967) # concerning the amendment of the legal time, the Ministry of Modernization of # Public Sectors announced that the official time in the Kingdom will be # advanced 60 minutes from Sunday 31 May 2009 at midnight. # # http://www.mmsp.gov.ma/francais/Actualites_fr/PDF_Actualites_Fr/HeureEte_FR.pdf # http://www.worldtimezone.com/dst_news/dst_news_morocco03.html # From Steffen Thorsen (2010-04-13): # Several news media in Morocco report that the Ministry of Modernization # of Public Sectors has announced that Morocco will have DST from # 2010-05-02 to 2010-08-08. # # Example: # http://www.lavieeco.com/actualites/4099-le-maroc-passera-a-l-heure-d-ete-gmt1-le-2-mai.html # (French) # Our page: # https://www.timeanddate.com/news/time/morocco-starts-dst-2010.html # From Dan Abitol (2011-03-30): # ...Rules for Africa/Casablanca are the following (24h format) # The 3rd April 2011 at 00:00:00, [it] will be 3rd April 01:00:00 # The 31st July 2011 at 00:59:59, [it] will be 31st July 00:00:00 # ...Official links of change in morocco # The change was broadcast on the FM Radio # I ve called ANRT (telecom regulations in Morocco) at # +212.537.71.84.00 # http://www.anrt.net.ma/fr/ # They said that # http://www.map.ma/fr/sections/accueil/l_heure_legale_au_ma/view # is the official publication to look at. # They said that the decision was already taken. # # More articles in the press # https://www.yabiladi.com/articles/details/5058/secret-l-heure-d-ete-maroc-leve.html # http://www.lematin.ma/Actualite/Express/Article.asp?id=148923 # http://www.lavieeco.com/actualite/Le-Maroc-passe-sur-GMT+1-a-partir-de-dim # From Petr Machata (2011-03-30): # They have it written in English here: # http://www.map.ma/eng/sections/home/morocco_to_spring_fo/view # # It says there that "Morocco will resume its standard time on July 31, # 2011 at midnight." Now they don't say whether they mean midnight of # wall clock time (i.e. 11pm UTC), but that's what I would assume. It has # also been like that in the past. # From Alexander Krivenyshev (2012-03-09): # According to Infomédiaire web site from Morocco (infomediaire.ma), # on March 9, 2012, (in French) Heure légale: # Le Maroc adopte officiellement l'heure d'été # http://www.infomediaire.ma/news/maroc/heure-légale-le-maroc-adopte-officiellement-lheure-dété # Governing Council adopted draft decree, that Morocco DST starts on # the last Sunday of March (March 25, 2012) and ends on # last Sunday of September (September 30, 2012) # except the month of Ramadan. # or (brief) # http://www.worldtimezone.com/dst_news/dst_news_morocco06.html # From Arthur David Olson (2012-03-10): # The infomediaire.ma source indicates that the system is to be in # effect every year. It gives 03H00 as the "fall back" time of day; # it lacks a "spring forward" time of day; assume 2:00 XXX. # Wait on specifying the Ramadan exception for details about # start date, start time of day, end date, and end time of day XXX. # From Christophe Tropamer (2012-03-16): # Seen Morocco change again: # http://www.le2uminutes.com/actualite.php # "...à partir du dernier dimanche d'avril et non fins mars, # comme annoncé précédemment." # From Milamber Space Network (2012-07-17): # The official return to GMT is announced by the Moroccan government: # http://www.mmsp.gov.ma/fr/actualites.aspx?id=288 [in French] # # Google translation, lightly edited: # Back to the standard time of the Kingdom (GMT) # Pursuant to Decree No. 2-12-126 issued on 26 Jumada (I) 1433 (April 18, # 2012) and in accordance with the order of Mr. President of the # Government No. 3-47-12 issued on 24 Sha'ban (11 July 2012), the Ministry # of Public Service and Administration Modernization announces the return # of the legal time of the Kingdom (GMT) from Friday, July 20, 2012 until # Monday, August 20, 2012. So the time will be delayed by 60 minutes from # 3:00 am Friday, July 20, 2012 and will again be advanced by 60 minutes # August 20, 2012 from 2:00 am. # From Paul Eggert (2013-03-06): # Morocco's daylight-saving transitions due to Ramadan seem to be # announced a bit in advance. On 2012-07-11 the Moroccan government # announced that year's Ramadan daylight-saving transitions would be # 2012-07-20 and 2012-08-20; see # http://www.mmsp.gov.ma/fr/actualites.aspx?id=288 # From Andrew Paprocki (2013-07-02): # Morocco announced that the year's Ramadan daylight-savings # transitions would be 2013-07-07 and 2013-08-10; see: # http://www.maroc.ma/en/news/morocco-suspends-daylight-saving-time-july-7-aug10 # From Steffen Thorsen (2013-09-28): # Morocco extends DST by one month, on very short notice, just 1 day # before it was going to end. There is a new decree (2.13.781) for # this, where DST from now on goes from last Sunday of March at 02:00 # to last Sunday of October at 03:00, similar to EU rules. Official # source (French): # http://www.maroc.gov.ma/fr/actualites/lhoraire-dete-gmt1-maintenu-jusquau-27-octobre-2013 # Another source (specifying the time for start and end in the decree): # http://www.lemag.ma/Heure-d-ete-au-Maroc-jusqu-au-27-octobre_a75620.html # From Sebastien Willemijns (2014-03-18): # http://www.afriquinfos.com/articles/2014/3/18/maroc-heure-dete-avancez-tous-horloges-247891.asp # From Milamber Space Network (2014-06-05): # The Moroccan government has recently announced that the country will return # to standard time at 03:00 on Saturday, June 28, 2014 local time.... DST # will resume again at 02:00 on Saturday, August 2, 2014.... # http://www.mmsp.gov.ma/fr/actualites.aspx?id=586 # From Milamber (2015-06-08): # (Google Translation) The hour will thus be delayed 60 minutes # Sunday, June 14 at 3:00, the ministry said in a statement, adding # that the time will be advanced again 60 minutes Sunday, July 19, # 2015 at 2:00. The move comes under 2.12.126 Decree of 26 Jumada I # 1433 (18 April 2012) and the decision of the Head of Government of # 16 N. 3-29-15 Chaaban 1435 (4 June 2015). # Source (french): # https://lnt.ma/le-maroc-reculera-dune-heure-le-dimanche-14-juin/ # # From Milamber (2015-06-09): # http://www.mmsp.gov.ma/fr/actualites.aspx?id=863 # # From Michael Deckers (2015-06-09): # [The gov.ma announcement] would (probably) make the switch on 2015-07-19 go # from 03:00 to 04:00 rather than from 02:00 to 03:00, as in the patch.... # I think the patch is correct and the quoted text is wrong; the text in # agrees # with the patch. # From Mohamed Essedik Najd (2018-10-26): # Today, a Moroccan government council approved the perpetual addition # of 60 minutes to the regular Moroccan timezone. # From Matt Johnson (2018-10-28): # http://www.sgg.gov.ma/Portals/1/BO/2018/BO_6720-bis_Ar.pdf # # From Maamar Abdelkader (2018-11-01): # We usually move clocks back the previous week end and come back to the +1 # the week end after.... The government does not announce yet the decision # about this temporary change. But it s 99% sure that it will be the case, # as in previous years. An unofficial survey was done these days, showing # that 64% of asked people are ok for moving from +1 to +0 during Ramadan. # https://leconomiste.com/article/1035870-enquete-l-economiste-sunergia-64-des-marocains-plebiscitent-le-gmt-pendant-ramadan # From Naoufal Semlali (2019-04-16): # Morocco will be on GMT starting from Sunday, May 5th 2019 at 3am. # The switch to GMT+1 will occur on Sunday, June 9th 2019 at 2am.... # http://fr.le360.ma/societe/voici-la-date-du-retour-a-lheure-legale-au-maroc-188222 # From Semlali Naoufal (2020-04-14): # Following the announcement by the Moroccan government, the switch to # GMT time will take place on Sunday, April 19, 2020 from 3 a.m. and # the return to GMT+1 time will take place on Sunday, May 31, 2020 at 2 a.m.... # https://maroc-diplomatique.net/maroc-le-retour-a-lheure-gmt-est-prevu-dimanche-prochain/ # http://aujourdhui.ma/actualite/gmt1-retour-a-lheure-normale-dimanche-prochain-1 # # From Milamber (2020-05-31) # In Morocco (where I live), the end of Ramadan (Arabic month) is followed by # the Eid al-Fitr, and concretely it's 1 or 2 day offs for the people (with # traditional visiting of family, big lunches/dinners, etc.). So for this # year the astronomical calculations don't include the following 2 days off in # the calc. These 2 days fall in a Sunday/Monday, so it's not acceptable by # people to have a time shift during these 2 days off. Perhaps you can modify # the (predicted) rules for next years: if the end of Ramadan is a (probable) # Friday or Saturday (and so the 2 days off are on a weekend), the next time # shift will be the next weekend. # # From Milamber (2021-03-31, 2022-03-10): # https://www.mmsp.gov.ma/fr/actualites.aspx?id=2076 # https://www.ecoactu.ma/horaires-administration-ramadan-gmtheure-gmt-a-partir-de-dimanche-27-mars/ # # From Milamber (2023-03-14, 2023-03-15): # The return to legal GMT time will take place this Sunday, March 19 at 3 a.m. # ... the return to GMT+1 will be made on Sunday April 23, 2023 at 2 a.m. # https://www.mmsp.gov.ma/fr/actualites/passage-à-l%E2%80%99heure-gmt-à-partir-du-dimanche-19-mars-2023 # From Paul Eggert (2026-06-25): # https://www.moroccoworldnews.com/2026/06/325034/confirmed-morocco-to-restore-gmt-on-september-20-ending-eight-year-gmt1-saga/ # Today the Moroccan government adopted Decree No. 2.26.530, which abrogates # the 2018 decree that put it at +01 with daylight saving during Ramadan. # The plan is to go back to +00 without DST on 2026-09-20 at 02:00. # From Anass Taghjichte (2026-07-03): # https://www.sgg.gov.ma/BO/AR/3111/2026/BO_7521_Ar.pdf # From Afaf EL MAAYATI (2026-07-06): # https://www.mapexpress.ma/actualite/activite-gouvernementale/conseil-gouvernement-approuve-projet-decret-relatif-au-retour-lheure-legale/ # Rule NAME FROM TO - IN ON AT SAVE LETTER/S Rule Morocco 1939 only - Sep 12 0:00 1:00 - Rule Morocco 1939 only - Nov 19 0:00 0 - Rule Morocco 1940 only - Feb 25 0:00 1:00 - Rule Morocco 1945 only - Nov 18 0:00 0 - Rule Morocco 1950 only - Jun 11 0:00 1:00 - Rule Morocco 1950 only - Oct 29 0:00 0 - Rule Morocco 1967 only - Jun 3 12:00 1:00 - Rule Morocco 1967 only - Oct 1 0:00 0 - Rule Morocco 1974 only - Jun 24 0:00 1:00 - Rule Morocco 1974 only - Sep 1 0:00 0 - Rule Morocco 1976 1977 - May 1 0:00 1:00 - Rule Morocco 1976 only - Aug 1 0:00 0 - Rule Morocco 1977 only - Sep 28 0:00 0 - Rule Morocco 1978 only - Jun 1 0:00 1:00 - Rule Morocco 1978 only - Aug 4 0:00 0 - Rule Morocco 2008 only - Jun 1 0:00 1:00 - Rule Morocco 2008 only - Sep 1 0:00 0 - Rule Morocco 2009 only - Jun 1 0:00 1:00 - Rule Morocco 2009 only - Aug 21 0:00 0 - Rule Morocco 2010 only - May 2 0:00 1:00 - Rule Morocco 2010 only - Aug 8 0:00 0 - Rule Morocco 2011 only - Apr 3 0:00 1:00 - Rule Morocco 2011 only - Jul 31 0:00 0 - Rule Morocco 2012 2013 - Apr lastSun 2:00 1:00 - Rule Morocco 2012 only - Jul 20 3:00 0 - Rule Morocco 2012 only - Aug 20 2:00 1:00 - Rule Morocco 2012 only - Sep 30 3:00 0 - Rule Morocco 2013 only - Jul 7 3:00 0 - Rule Morocco 2013 only - Aug 10 2:00 1:00 - Rule Morocco 2013 2018 - Oct lastSun 3:00 0 - Rule Morocco 2014 2018 - Mar lastSun 2:00 1:00 - Rule Morocco 2014 only - Jun 28 3:00 0 - Rule Morocco 2014 only - Aug 2 2:00 1:00 - Rule Morocco 2015 only - Jun 14 3:00 0 - Rule Morocco 2015 only - Jul 19 2:00 1:00 - Rule Morocco 2016 only - Jun 5 3:00 0 - Rule Morocco 2016 only - Jul 10 2:00 1:00 - Rule Morocco 2017 only - May 21 3:00 0 - Rule Morocco 2017 only - Jul 2 2:00 1:00 - Rule Morocco 2018 only - May 13 3:00 0 - Rule Morocco 2018 only - Jun 17 2:00 1:00 - Rule Morocco 2019 only - May 5 3:00 -1:00 - Rule Morocco 2019 only - Jun 9 2:00 0 - Rule Morocco 2020 only - Apr 19 3:00 -1:00 - Rule Morocco 2020 only - May 31 2:00 0 - Rule Morocco 2021 only - Apr 11 3:00 -1:00 - Rule Morocco 2021 only - May 16 2:00 0 - Rule Morocco 2022 only - Mar 27 3:00 -1:00 - Rule Morocco 2022 only - May 8 2:00 0 - Rule Morocco 2023 only - Mar 19 3:00 -1:00 - Rule Morocco 2023 only - Apr 23 2:00 0 - Rule Morocco 2024 only - Mar 10 3:00 -1:00 - Rule Morocco 2024 only - Apr 14 2:00 0 - Rule Morocco 2025 only - Feb 23 3:00 -1:00 - Rule Morocco 2025 only - Apr 6 2:00 0 - Rule Morocco 2026 only - Feb 15 3:00 -1:00 - Rule Morocco 2026 only - Mar 22 2:00 0 - # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone Africa/Casablanca -0:30:20 - LMT 1913 Oct 26 0:00 Morocco %z 1984 Mar 16 1:00 - %z 1986 0:00 Morocco %z 2018 Oct 28 3:00 1:00 Morocco %z 2026 Sep 20 2:00 0:00 - %z # Western Sahara # # From Gwillim Law (2013-10-22): # A correspondent who is usually well informed about time zone matters # ... says that Western Sahara observes daylight saving time, just as # Morocco does. # # From Paul Eggert (2013-10-23): # Assume that this has been true since Western Sahara switched to GMT, # since most of it was then controlled by Morocco. Zone Africa/El_Aaiun -0:52:48 - LMT 1934 Jan # El Aaiún -1:00 - %z 1976 Apr 14 0:00 Morocco %z 2018 Oct 28 3:00 1:00 Morocco %z 2026 Sep 20 2:00 0:00 - %z # Botswana # Burundi # Democratic Republic of the Congo (eastern) # Malawi # Mozambique # Rwanda # Zambia # Zimbabwe # # From Tim Parenti (2024-07-01): # For timestamps before Mozambique's independence, see commentary for # Europe/Lisbon. # # From Paul Eggert (2024-05-24): # The London Gazette, 1903-04-03, page 2245, says that # as of 1903-03-03 a time ball at the port of Lourenço Marques # (as Maputo was then called) was dropped daily at 13:00:00 LMT, # corresponding to 22:49:41.7 GMT, so local time was +02:10:18.3. # Conversely, the newspaper South Africa, 1909-02-09, page 321, # says the port had just installed an apparatus that communicated # "from the controlling clock in the new Observatory at Reuben Point ... # exact mean South African time, i.e., 30 deg., or 2 hours East of Greenwich". # Although Shanks gives 1903-03-01 for the transition to CAT, # evidently the port transitioned to CAT after 1903-03-03 but before # the Portuguese legal transition of 1912-01-01 (see Europe/Lisbon commentary). # For lack of better info, list 1909 as the transition date. # # Zone NAME STDOFF RULES FORMAT [UNTIL] #STDOFF 2:10:18.3 Zone Africa/Maputo 2:10:18 - LMT 1909 2:00 - CAT # Namibia # From Arthur David Olson (2017-08-09): # The text of the "Namibia Time Act, 1994" is available online at # www.lac.org.na/laws/1994/811.pdf # and includes this nugget: # Notwithstanding the provisions of subsection (2) of section 1, the # first winter period after the commencement of this Act shall # commence at OOhOO on Monday 21 March 1994 and shall end at 02h00 on # Sunday 4 September 1994. # From Michael Deckers (2017-04-06): # ... both summer and winter time are called "standard" # (which differs from the use in Ireland) ... # From Petronella Sibeene (2007-03-30): # http://allafrica.com/stories/200703300178.html # While the entire country changes its time, Katima Mulilo and other # settlements in Caprivi unofficially will not because the sun there # rises and sets earlier compared to other regions. Chief of # Forecasting Riaan van Zyl explained that the far eastern parts of # the country are close to 40 minutes earlier in sunrise than the rest # of the country. # # From Paul Eggert (2017-02-22): # Although the Zambezi Region (formerly known as Caprivi) informally # observes Botswana time, we have no details about historical practice. # In the meantime people there can use Africa/Gaborone. # See: Immanuel S. The Namibian. 2017-02-23. # https://www.namibian.com.na/51480/read/Time-change-divides-lawmakers # From Steffen Thorsen (2017-08-09): # Namibia is going to change their time zone to what is now their DST: # https://www.newera.com.na/2017/02/23/namibias-winter-time-might-be-repealed/ # This video is from the government decision: # https://www.nbc.na/news/na-passes-namibia-time-bill-repealing-1994-namibia-time-act.8665 # We have made the assumption so far that they will change their time zone at # the same time they would normally start DST, the first Sunday in September: # https://www.timeanddate.com/news/time/namibia-new-time-zone.html # From Paul Eggert (2017-04-09): # Before the change, summer and winter time were both standard time legally. # However in common parlance, winter time was considered to be DST. See, e.g.: # http://www.nbc.na/news/namibias-winter-time-could-be-scrapped.2706 # https://zone.my.na/news/times-are-changing-in-namibia # https://www.newera.com.na/2017/02/23/namibias-winter-time-might-be-repealed/ # Use plain "WAT" and "CAT" for the time zone abbreviations, to be compatible # with Namibia's neighbors. # Rule NAME FROM TO - IN ON AT SAVE LETTER/S # Vanguard section, for zic and other parsers that support negative DST. Rule Namibia 1994 only - Mar 21 0:00 -1:00 WAT Rule Namibia 1994 2017 - Sep Sun>=1 2:00 0 CAT Rule Namibia 1995 2017 - Apr Sun>=1 2:00 -1:00 WAT # Rearguard section, for parsers lacking negative DST; see ziguard.awk. #Rule Namibia 1994 only - Mar 21 0:00 0 WAT #Rule Namibia 1994 2017 - Sep Sun>=1 2:00 1:00 CAT #Rule Namibia 1995 2017 - Apr Sun>=1 2:00 0 WAT # End of rearguard section. # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone Africa/Windhoek 1:08:24 - LMT 1892 Feb 8 1:30 - %z 1903 Mar 2:00 - SAST 1942 Sep 20 2:00 2:00 1:00 SAST 1943 Mar 21 2:00 2:00 - SAST 1990 Mar 21 # independence # Vanguard section, for zic and other parsers that support negative DST. 2:00 Namibia %s # Rearguard section, for parsers lacking negative DST; see ziguard.awk. # 2:00 - CAT 1994 Mar 21 0:00 # From Paul Eggert (2017-04-07): # The official date of the 2017 rule change was 2017-10-24. See: # http://www.lac.org.na/laws/annoSTAT/Namibian%20Time%20Act%209%20of%202017.pdf # 1:00 Namibia %s 2017 Oct 24 # 2:00 - CAT # End of rearguard section. # Angola # Benin # Cameroon # Central African Republic # Democratic Republic of the Congo (western) # Republic of the Congo # Equatorial Guinea # Gabon # Niger # Nigeria # From P Chan (2020-12-03): # GMT was adopted as the standard time of Lagos on 1905-07-01. # Lagos Weekly Record, 1905-06-24, p 3 # http://ddsnext.crl.edu/titles/31558#?c=0&m=668&s=0&cv=2&r=0&xywh=1446,5221,1931,1235 # says "It is officially notified that on and after the 1st of July 1905 # Greenwich Mean Solar Time will be adopted throughout the Colony and # Protectorate, and that it will be necessary to put all clocks 13 minutes and # 35 seconds back, recording local mean time." # # It seemed that Lagos returned to LMT on 1908-07-01. # [The Lagos Standard], 1908-07-01, p 5 # http://ddsnext.crl.edu/titles/31556#?c=0&m=78&s=0&cv=4&r=0&xywh=-92,3590,3944,2523 # says "Scarcely have the people become accustomed to this new time, when # another official notice has now appeared announcing that from and after the # 1st July next, return will be made to local mean time." # # From P Chan (2020-11-27): # On 1914-01-01, standard time of GMT+0:30 was adopted for the unified Nigeria. # Colonial Reports - Annual. No. 878. Nigeria. Report for 1914. (April 1916), # p 27 # https://libsysdigi.library.illinois.edu/ilharvest/Africana/Books2011-05/3064634/3064634_1914/3064634_1914_opt.pdf#page=27 # "On January 1st [1914], a universal standard time for Nigeria was adopted, # viz., half an hour fast on Greenwich mean time, corresponding to the meridian # 7° 30' E. long." # Lloyd's Register of Shipping (1915) says "Hitherto the time observed in Lagos # was the local mean time. On 1st January, 1914, standard time for the whole of # Nigeria was introduced ... Lagos time has been advanced about 16 minutes # accordingly." # # In 1919, standard time was changed to GMT+1. # Interpretation Ordinance (Cap 2) # The Laws of Nigeria, Containing the Ordinances of Nigeria, in Force on the # 1st Day of January, 1923, Vol.I [p 16] # https://books.google.com/books?id=BOMrAQAAMAAJ&pg=PA16 # "The expression 'Standard time' means standard time as used in Nigeria: # namely, 60 minutes in advance of Greenwich mean time. (As amended by 18 of # 1919, s. 2.)" # From Tim Parenti (2020-12-10): # The Lagos Weekly Record, 1919-09-20, p 3 details discussion on the first # reading of this Bill by the Legislative Council of the Colony of Nigeria on # Thursday 1919-08-28: # http://ddsnext.crl.edu/titles/31558?terms&item_id=303484#?m=1118&c=1&s=0&cv=2&r=0&xywh=1261,3408,2994,1915 # "The proposal is that the Globe should be divided into twelve zones East and # West of Greenwich, of one hour each, Nigeria falling into the zone with a # standard of one hour fast on Greenwich Mean Time. Nigeria standard time is # now 30 minutes in advance of Greenwich Mean Time ... according to the new # proposal, standard time will be advanced another 30 minutes". It was further # proposed that the firing of the time guns likewise be adjusted by 30 minutes # to compensate. # From Tim Parenti (2020-12-10), per P Chan (2020-12-11): # The text of Ordinance 18 of 1919, published in Nigeria Gazette, Vol 6, No 52, # shows that the change was assented to the following day and took effect "on # the 1st day of September, 1919." # Nigeria Gazette and Supplements 1919 Jan-Dec, Reference: 73266B-40, # img 245-246 # https://microform.digital/boa/collections/77/volumes/539/nigeria-lagos-1887-1919 # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone Africa/Lagos 0:13:35 - LMT 1905 Jul 1 0:00 - GMT 1908 Jul 1 0:13:35 - LMT 1914 Jan 1 0:30 - %z 1919 Sep 1 1:00 - WAT # São Tomé and Príncipe # See Europe/Lisbon for info about the 1912 transition. # From Steffen Thorsen (2018-01-08): # Multiple sources tell that São Tomé changed from UTC to UTC+1 as # they entered the year 2018. # From Michael Deckers (2018-01-08): # the switch is from 01:00 to 02:00 ... [Decree No. 25/2017] # http://www.mnec.gov.st/index.php/publicacoes/documentos/file/90-decreto-lei-n-25-2017 # From Vadim Nasardinov (2018-12-29): # São Tomé and Príncipe is about to do the following on Jan 1, 2019: # https://www.stp-press.st/2018/12/05/governo-jesus-ja-decidiu-repor-hora-legal-sao-tomense/ # # From Michael Deckers (2018-12-30): # https://www.legis-palop.org/download.jsp?idFile=102818 # ... [The legal time of the country, which coincides with universal # coordinated time, will be reinstituted at 2 o'clock on day 1 of January, 2019.] Zone Africa/Sao_Tome 0:26:56 - LMT 1884 #STDOFF -0:36:44.68 -0:36:45 - LMT 1912 Jan 1 00:00u # Lisbon MT 0:00 - GMT 2018 Jan 1 01:00 1:00 - WAT 2019 Jan 1 02:00 0:00 - GMT # Eswatini (Swaziland) # Lesotho # South Africa # Rule NAME FROM TO - IN ON AT SAVE LETTER/S Rule SA 1942 1943 - Sep Sun>=15 2:00 1:00 - Rule SA 1943 1944 - Mar Sun>=15 2:00 0 - # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone Africa/Johannesburg 1:52:00 - LMT 1892 Feb 8 1:30 - SAST 1903 Mar 2:00 SA SAST # # Marion and Prince Edward Is # scientific station since 1947 # no information # Sudan # From # Sudan News Agency (2000-01-13), # also reported by Michaël De Beukelaer-Dossche via Steffen Thorsen: # Clocks will be moved ahead for 60 minutes all over the Sudan as of noon # Saturday.... This was announced Thursday by Caretaker State Minister for # Manpower Abdul-Rahman Nur-Eddin. # From Ahmed Atyya, National Telecommunications Corp. (NTC), Sudan (2017-10-17): # ... the Republic of Sudan is going to change the time zone from (GMT+3:00) # to (GMT+ 2:00) starting from Wednesday 1 November 2017. # # From Paul Eggert (2017-10-18): # A scanned copy (in Arabic) of Cabinet Resolution No. 352 for the # year 2017 can be found as an attachment in email today from Yahia # Abdalla of NTC, archived at: # https://mm.icann.org/pipermail/tz/2017-October/025333.html # Rule NAME FROM TO - IN ON AT SAVE LETTER/S Rule Sudan 1970 only - May 1 0:00 1:00 S Rule Sudan 1970 1985 - Oct 15 0:00 0 - Rule Sudan 1971 only - Apr 30 0:00 1:00 S Rule Sudan 1972 1985 - Apr lastSun 0:00 1:00 S # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone Africa/Khartoum 2:10:08 - LMT 1931 2:00 Sudan CA%sT 2000 Jan 15 12:00 3:00 - EAT 2017 Nov 1 2:00 - CAT # South Sudan # From Steffen Thorsen (2021-01-18): # "South Sudan will change its time zone by setting the clock back 1 # hour on February 1, 2021...." # from https://eyeradio.org/south-sudan-adopts-new-time-zone-makuei/ # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone Africa/Juba 2:06:28 - LMT 1931 2:00 Sudan CA%sT 2000 Jan 15 12:00 3:00 - EAT 2021 Feb 1 00:00 2:00 - CAT # Tunisia # From Gwillim Law (2005-04-30): # My correspondent, Risto Nykänen, has alerted me to another adoption of DST, # this time in Tunisia. According to Yahoo France News # , in a story attributed to AP # and dated 2005-04-26, "Tunisia has decided to advance its official time by # one hour, starting on Sunday, May 1. Henceforth, Tunisian time will be # UTC+2 instead of UTC+1. The change will take place at 23:00 UTC next # Saturday." (My translation) # # From Oscar van Vlijmen (2005-05-02): # La Presse, the first national daily newspaper ... # http://www.lapresse.tn/archives/archives280405/actualites/lheure.html # ... DST for 2005: on: Sun May 1 0h standard time, off: Fri Sept. 30, # 1h standard time. # # From Atef Loukil (2006-03-28): # The daylight saving time will be the same each year: # Beginning : the last Sunday of March at 02:00 # Ending : the last Sunday of October at 03:00 ... # http://www.tap.info.tn/en/index.php?option=com_content&task=view&id=1188&Itemid=50 # From Steffen Thorsen (2009-03-16): # According to several news sources, Tunisia will not observe DST this year. # (Arabic) # http://www.elbashayer.com/?page=viewn&nid=42546 # https://www.babnet.net/kiwidetail-15295.asp # # We have also confirmed this with the US embassy in Tunisia. # We have a wrap-up about this on the following page: # https://www.timeanddate.com/news/time/tunisia-cancels-dst-2009.html # From Alexander Krivenyshev (2009-03-17): # Here is a link to Tunis Afrique Presse News Agency # # Standard time to be kept the whole year long (tap.info.tn): # # (in English) # http://www.tap.info.tn/en/index.php?option=com_content&task=view&id=26813&Itemid=157 # # (in Arabic) # http://www.tap.info.tn/ar/index.php?option=com_content&task=view&id=61240&Itemid=1 # From Arthur David Olson (2009-03-18): # The Tunis Afrique Presse News Agency notice contains this: "This measure is # due to the fact that the fasting month of Ramadan coincides with the period # concerned by summer time. Therefore, the standard time will be kept # unchanged the whole year long." So foregoing DST seems to be an exception # (albeit one that may be repeated in the future). # From Alexander Krivenyshev (2010-03-27): # According to some news reports Tunis confirmed not to use DST in 2010 # # (translation): # "The Tunisian government has decided to abandon DST, which was scheduled on # Sunday... # Tunisian authorities had suspended the DST for the first time last year also # coincided with the month of Ramadan..." # # (in Arabic) # http://www.moheet.com/show_news.aspx?nid=358861&pg=1 # http://www.almadenahnews.com/newss/news.php?c=118&id=38036 # http://www.worldtimezone.com/dst_news/dst_news_tunis02.html # Rule NAME FROM TO - IN ON AT SAVE LETTER/S Rule Tunisia 1939 only - Apr 15 23:00s 1:00 S Rule Tunisia 1939 only - Nov 18 23:00s 0 - Rule Tunisia 1940 only - Feb 25 23:00s 1:00 S Rule Tunisia 1941 only - Oct 6 0:00 0 - Rule Tunisia 1942 only - Mar 9 0:00 1:00 S Rule Tunisia 1942 only - Nov 2 3:00 0 - Rule Tunisia 1943 only - Mar 29 2:00 1:00 S Rule Tunisia 1943 only - Apr 17 2:00 0 - Rule Tunisia 1943 only - Apr 25 2:00 1:00 S Rule Tunisia 1943 only - Oct 4 2:00 0 - Rule Tunisia 1944 1945 - Apr Mon>=1 2:00 1:00 S Rule Tunisia 1944 only - Oct 8 0:00 0 - Rule Tunisia 1945 only - Sep 16 0:00 0 - Rule Tunisia 1977 only - Apr 30 0:00s 1:00 S Rule Tunisia 1977 only - Sep 24 0:00s 0 - Rule Tunisia 1978 only - May 1 0:00s 1:00 S Rule Tunisia 1978 only - Oct 1 0:00s 0 - Rule Tunisia 1988 only - Jun 1 0:00s 1:00 S Rule Tunisia 1988 1990 - Sep lastSun 0:00s 0 - Rule Tunisia 1989 only - Mar 26 0:00s 1:00 S Rule Tunisia 1990 only - May 1 0:00s 1:00 S Rule Tunisia 2005 only - May 1 0:00s 1:00 S Rule Tunisia 2005 only - Sep 30 1:00s 0 - Rule Tunisia 2006 2008 - Mar lastSun 2:00s 1:00 S Rule Tunisia 2006 2008 - Oct lastSun 2:00s 0 - # See Europe/Paris commentary for PMT-related transitions. # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone Africa/Tunis 0:40:44 - LMT 1881 May 12 0:09:21 - PMT 1911 Mar 11 # Paris Mean Time 1:00 Tunisia CE%sT pr0m1th3as-datatypes-9c9a8d3/inst/tzdata/antarctica000066400000000000000000000334001522766574100223550ustar00rootroot00000000000000# tzdb data for Antarctica and environs # This file is in the public domain, so clarified as of # 2009-05-17 by Arthur David Olson. # From Paul Eggert (2025-08-16): # To keep things manageable, list only locations occupied year-round; see # Antarctic Facilities Information # https://www.comnap.aq/antarctic-facilities-information # for information. # Unless otherwise specified, we have no time zone information. # FORMAT is '-00' and STDOFF is 0 for locations while uninhabited. # Argentina - year-round bases # Belgrano II, Confin Coast, -770227-0343737, since 1972-02-05 # Carlini, Potter Cove, King George Island, -6414-0602320, since 1982-01 # Esperanza, Hope Bay, -6323-05659, since 1952-12-17 # Marambio, -6414-05637, since 1969-10-29 # Orcadas, Laurie I, -6016-04444, since 1904-02-22 # San Martín, Barry I, -6808-06706, since 1951-03-21 # (except 1960-03 / 1976-03-21) # Australia - territories # Heard Island, McDonald Islands (uninhabited) # previously sealers and scientific personnel wintered # Margaret Turner reports # https://web.archive.org/web/20021204222245/http://www.dstc.qut.edu.au/DST/marg/daylight.html # (1999-09-30) that they're UT +05, with no DST; # presumably this is when they have visitors. # # year-round bases # Casey, Bailey Peninsula, -6617+11032, since 1969 # Davis, Vestfold Hills, -6835+07759, since 1957-01-13 # (except 1964-11 - 1969-02) # Mawson, Holme Bay, -6736+06253, since 1954-02-13 # From Steffen Thorsen (2009-03-11): # Three Australian stations in Antarctica have changed their time zone: # Casey moved from UTC+8 to UTC+11 # Davis moved from UTC+7 to UTC+5 # Mawson moved from UTC+6 to UTC+5 # The changes occurred on 2009-10-18 at 02:00 (local times). # # Government source: (Australian Antarctic Division) # http://www.aad.gov.au/default.asp?casid=37079 # # We have more background information here: # https://www.timeanddate.com/news/time/antarctica-new-times.html # From Steffen Thorsen (2010-03-10): # We got these changes from the Australian Antarctic Division: ... # # - Casey station reverted to its normal time of UTC+8 on 5 March 2010. # The change to UTC+11 is being considered as a regular summer thing but # has not been decided yet. # # - Davis station will revert to its normal time of UTC+7 at 10 March 2010 # 20:00 UTC. # # - Mawson station stays on UTC+5. # # Background: # https://www.timeanddate.com/news/time/antartica-time-changes-2010.html # From Steffen Thorsen (2016-10-28): # Australian Antarctica Division informed us that Casey changed time # zone to UTC+11 in "the morning of 22nd October 2016". # From Steffen Thorsen (2020-10-02, as corrected): # Based on information we have received from the Australian Antarctic # Division, Casey station and Macquarie Island station will move to Tasmanian # daylight savings time on Sunday 4 October. This will take effect from 0001 # hrs on Sunday 4 October 2020 and will mean Casey and Macquarie Island will # be on the same time zone as Hobart. Some past dates too for this 3 hour # time change back and forth between UTC+8 and UTC+11 for Casey: # - 2018 Oct 7 4:00 - 2019 Mar 17 3:00 - 2019 Oct 4 3:00 - 2020 Mar 8 3:00 # and now - 2020 Oct 4 0:01 # From Paul Eggert (2023-12-20): # Transitions from 2021 on are taken from: # https://www.timeanddate.com/time/zone/antarctica/casey # retrieved at various dates. # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone Antarctica/Casey 0 - -00 1969 8:00 - %z 2009 Oct 18 2:00 11:00 - %z 2010 Mar 5 2:00 8:00 - %z 2011 Oct 28 2:00 11:00 - %z 2012 Feb 21 17:00u 8:00 - %z 2016 Oct 22 11:00 - %z 2018 Mar 11 4:00 8:00 - %z 2018 Oct 7 4:00 11:00 - %z 2019 Mar 17 3:00 8:00 - %z 2019 Oct 4 3:00 11:00 - %z 2020 Mar 8 3:00 8:00 - %z 2020 Oct 4 0:01 11:00 - %z 2021 Mar 14 0:00 8:00 - %z 2021 Oct 3 0:01 11:00 - %z 2022 Mar 13 0:00 8:00 - %z 2022 Oct 2 0:01 11:00 - %z 2023 Mar 9 3:00 8:00 - %z Zone Antarctica/Davis 0 - -00 1957 Jan 13 7:00 - %z 1964 Nov 0 - -00 1969 Feb 7:00 - %z 2009 Oct 18 2:00 5:00 - %z 2010 Mar 10 20:00u 7:00 - %z 2011 Oct 28 2:00 5:00 - %z 2012 Feb 21 20:00u 7:00 - %z Zone Antarctica/Mawson 0 - -00 1954 Feb 13 6:00 - %z 2009 Oct 18 2:00 5:00 - %z # References: # Casey Weather (1998-02-26) # http://www.antdiv.gov.au/aad/exop/sfo/casey/casey_aws.html # Davis Station, Antarctica (1998-02-26) # http://www.antdiv.gov.au/aad/exop/sfo/davis/video.html # Mawson Station, Antarctica (1998-02-25) # http://www.antdiv.gov.au/aad/exop/sfo/mawson/video.html # Belgium - year-round base # Princess Elisabeth, Queen Maud Land, -713412+0231200, since 2007 # Brazil - year-round base # Ferraz, King George Island, -6205+05824, since 1983/4 # Bulgaria - year-round base # St. Kliment Ohridski, Livingston Island, -623829-0602153, since 1988 # Chile - year-round bases and towns # Escudero, South Shetland Is, -621157-0585735, since 1994 # Frei Montalva, King George Island, -6214-05848, since 1969-03-07 # O'Higgins, Antarctic Peninsula, -6319-05704, since 1948-02 # Prat, -6230-05941 # Villa Las Estrellas (a town), around the Frei base, since 1984-04-09 # These locations employ Region of Magallanes time; use # TZ='America/Punta_Arenas'. # China - year-round bases # Great Wall, King George Island, -6213-05858, since 1985-02-20 # Zhongshan, Larsemann Hills, Prydz Bay, -6922+07623, since 1989-02-26 # Qinling, Inexpressible I, Terra Nova Bay, -7456+16343, since 2024-02-07 # France - year-round bases (also see "France & Italy") # # From Antoine Leca (1997-01-20): # Time data entries are from Nicole Pailleau at the IFRTP # (French Institute for Polar Research and Technology). # She confirms that French Southern Territories and Terre Adélie bases # don't observe daylight saving time, even if Terre Adélie supplies came # from Tasmania. # # French Southern Territories with year-round inhabitants # # Alfred Faure, Possession Island, Crozet Islands, -462551+0515152, since 1964; # sealing & whaling stations operated variously 1802/1911+; # see Asia/Dubai. # # Martin-de-Viviès, Amsterdam Island, -374105+0773155, since 1950 # Port-aux-Français, Kerguelen Islands, -492110+0701303, since 1951; # whaling & sealing station operated 1908/1914, 1920/1929, and 1951/1956 # # St Paul Island - near Amsterdam, uninhabited # fishing stations operated variously 1819/1931 # # Kerguelen - see Indian/Maldives. # # year-round base in the main continent # Dumont d'Urville - see Pacific/Port_Moresby. # France & Italy - year-round base # Concordia, -750600+1232000, since 2005 # https://en.wikipedia.org/wiki/Concordia_Station # Can use Asia/Singapore, which it has agreed with since inception. # Germany - year-round base # Neumayer III, -704080-0081602, since 2009 # India - year-round bases # Bharati, -692428+0761114, since 2012 # Maitri, -704558+0114356, since 1989 # Italy - year-round base (also see "France & Italy") # Zuchelli, Terra Nova Bay, -744140+1640647, since 1986 # Japan - year-round bases # See Asia/Riyadh. # S Korea - year-round base # Jang Bogo, Terra Nova Bay, -743700+1641205 since 2014 # King Sejong, King George Island, -6213-05847, since 1988 # New Zealand - claims # Balleny Islands (never inhabited) # Scott Island (never inhabited) # # year-round base # Scott Base, Ross Island, since 1957-01. # See Pacific/Auckland. # Norway - territories # Bouvet (never inhabited) # # claims # Peter I Island (never inhabited) # # year-round base # Troll, Queen Maud Land, -720041+0023206, since 2005-02-12 # # From Paul-Inge Flakstad (2014-03-10): # I recently had a long dialog about this with the developer of timegenie.com. # In the absence of specific dates, he decided to choose some likely ones: # GMT +1 - From March 1 to the last Sunday in March # GMT +2 - From the last Sunday in March until the last Sunday in October # GMT +1 - From the last Sunday in October until November 7 # GMT +0 - From November 7 until March 1 # The dates for switching to and from UTC+0 will probably not be absolutely # correct, but they should be quite close to the actual dates. # # From Paul Eggert (2014-03-21): # The CET-switching Troll rules require zic from tz 2014b or later, so as # suggested by Bengt-Inge Larsson comment them out for now, and approximate # with only UTC and CEST. Uncomment them when 2014b is more prevalent. # # Rule NAME FROM TO - IN ON AT SAVE LETTER/S #Rule Troll 2005 max - Mar 1 1:00u 1:00 +01 Rule Troll 2005 max - Mar lastSun 1:00u 2:00 +02 #Rule Troll 2005 max - Oct lastSun 1:00u 1:00 +01 #Rule Troll 2004 max - Nov 7 1:00u 0:00 +00 # Remove the following line when uncommenting the above '#Rule' lines. Rule Troll 2004 max - Oct lastSun 1:00u 0:00 +00 # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone Antarctica/Troll 0 - -00 2005 Feb 12 0:00 Troll %s # Poland - year-round base # Arctowski, King George Island, -620945-0582745, since 1977 # Romania - year-bound base # Law-Racoviță, Larsemann Hills, -692319+0762251, since 1986 # Russia - year-round bases # Bellingshausen, King George Island, -621159-0585337, since 1968-02-22 # Mirny, Davis coast, -6633+09301, since 1956-02 # Molodezhnaya, Alasheyev Bay, -6740+04551, # year-round from 1962-02 to 1999-07-01 # Novolazarevskaya, Queen Maud Land, -7046+01150, # year-round from 1960/61 to 1992 # Vostok, since 1957-12-16, temporarily closed 1994-02/1994-11 # From Craig Mundell (1994-12-15): # http://quest.arc.nasa.gov/antarctica/QA/computers/Directions,Time,ZIP # Vostok, which is one of the Russian stations, is set on the same # time as Moscow, Russia. # # From Lee Hotz (2001-03-08): # I queried the folks at Columbia who spent the summer at Vostok and this is # what they had to say about time there: # "in the US Camp (East Camp) we have been on New Zealand (McMurdo) # time, which is 12 hours ahead of GMT. The Russian Station Vostok was # 6 hours behind that (although only 2 miles away, i.e. 6 hours ahead # of GMT). This is a time zone I think two hours east of Moscow. The # natural time zone is in between the two: 8 hours ahead of GMT." # # From Paul Eggert (2001-05-04): # This seems to be hopelessly confusing, so I asked Lee Hotz about it # in person. He said that some Antarctic locations set their local # time so that noon is the warmest part of the day, and that this # changes during the year and does not necessarily correspond to mean # solar noon. So the Vostok time might have been whatever the clocks # happened to be during their visit. So we still don't really know what time # it is at Vostok. # # From Zakhary V. Akulov (2023-12-17 22:00:48 +0700): # ... from December, 18, 2023 00:00 by my decision the local time of # the Antarctic research base Vostok will correspond to UTC+5. # (2023-12-19): We constantly interact with Progress base, with company who # builds new wintering station, with sledge convoys, with aviation - they all # use UTC+5. Besides, difference between Moscow time is just 2 hours now, not 4. # (2023-12-19, in response to the question "Has local time at Vostok # been UTC+6 ever since 1957, or has it changed before?"): No. At least # since my antarctic career start, 10 years ago, Vostok base has UTC+7. # (In response to a 2023-12-18 question "from 02:00 to 00:00 today"): This. # # From Paul Eggert (2023-12-18): # For lack of better info, guess Vostok was at +07 from founding through today, # except when closed. # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone Antarctica/Vostok 0 - -00 1957 Dec 16 7:00 - %z 1994 Feb 0 - -00 1994 Nov 7:00 - %z 2023 Dec 18 2:00 5:00 - %z # S Africa - year-round bases # Marion Island, -4653+03752 # SANAE IV, Vesleskarvet, Queen Maud Land, -714022-0025026, since 1997 # Ukraine - year-round base # Vernadsky (formerly Faraday), Galindez Island, -651445-0641526, since 1954 # United Kingdom # # British Antarctic Territories (BAT) claims # South Orkney Islands # scientific station from 1903 # whaling station at Signy I 1920/1926 # South Shetland Islands # # year-round bases # Bird Island, South Georgia, -5400-03803, since 1983 # Deception Island, -6259-06034, whaling station 1912/1931, # scientific station 1943/1967, # previously sealers and a scientific expedition wintered by accident, # and a garrison was deployed briefly # Halley, Coates Land, -7535-02604, since 1956-01-06 # Halley is on a moving ice shelf and is periodically relocated # so that it is never more than 10km from its nominal location. # Rothera, Adelaide Island, -6734-6808, since 1976-12-01 # # From Paul Eggert (2002-10-22) # says Rothera is -03 all year. # # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone Antarctica/Rothera 0 - -00 1976 Dec 1 -3:00 - %z # Uruguay - year round base # Artigas, King George Island, -621104-0585107 # USA - year-round bases # # Palmer, Anvers Island, since 1965 (moved 2 miles in 1968) # See 'southamerica' for Antarctica/Palmer, since it uses South American DST. # # McMurdo Station, Ross Island, since 1955-12 # Amundsen-Scott South Pole Station, continuously occupied since 1956-11-20 # # From Chris Carrier (1996-06-27): # Siple, the first commander of the South Pole station, # stated that he would have liked to have kept GMT at the station, # but that he found it more convenient to keep GMT+12 # as supplies for the station were coming from McMurdo Sound, # which was on GMT+12 because New Zealand was on GMT+12 all year # at that time (1957). (Source: Siple's book 90° South.) # # From Susan Smith # http://www.cybertours.com/whs/pole10.html # (1995-11-13 16:24:56 +1300, no longer available): # We use the same time as McMurdo does. # And they use the same time as Christchurch, NZ does.... # One last quirk about South Pole time. # All the electric clocks are usually wrong. # Something about the generators running at 60.1hertz or something # makes all of the clocks run fast. So every couple of days, # we have to go around and set them back 5 minutes or so. # Maybe if we let them run fast all of the time, we'd get to leave here sooner!! # # See Pacific/Auckland. pr0m1th3as-datatypes-9c9a8d3/inst/tzdata/asia000066400000000000000000005705471522766574100212030ustar00rootroot00000000000000# tzdb data for Asia and environs # This file is in the public domain, so clarified as of # 2009-05-17 by Arthur David Olson. # This file is by no means authoritative; if you think you know better, # go ahead and edit the file, and please send any changes to # the public mailing list tz@iana.org for general use in the future. # For more, please see the file CONTRIBUTING in the tz distribution. # From Paul Eggert (2019-07-11): # # Unless otherwise specified, the source for data through 1990 is: # Thomas G. Shanks and Rique Pottenger, The International Atlas (6th edition), # San Diego: ACS Publications, Inc. (2003). # Unfortunately this book contains many errors and cites no sources. # # Many years ago Gwillim Law wrote that a good source # for time zone data was the International Air Transport # Association's Standard Schedules Information Manual (IATA SSIM), # published semiannually. Law sent in several helpful summaries # of the IATA's data after 1990. Except where otherwise noted, # IATA SSIM is the source for entries after 1990. # # Another source occasionally used is Edward W. Whitman, World Time Differences, # Whitman Publishing Co, 2 Niagara Av, Ealing, London (undated), which # I found in the UCLA library. # # For data circa 1899, a common source is: # Milne J. Civil time. Geogr J. 1899 Feb;13(2):173-94. # https://www.jstor.org/stable/1774359 # # For Russian data circa 1919, a source is: # Byalokoz EL. New Counting of Time in Russia since July 1, 1919. # (See the 'europe' file for a fuller citation.) # # The following alphabetic abbreviations appear in these tables # (corrections are welcome): # std dst # LMT Local Mean Time # 2:00 EET EEST Eastern European Time # 2:00 IST IDT Israel # 5:30 IST India # 7:00 WIB west Indonesia (Waktu Indonesia Barat) # 8:00 WITA central Indonesia (Waktu Indonesia Tengah) # 8:00 CST China # 8:00 HKT HKST Hong Kong (HKWT* for Winter Time in late 1941) # 8:00 PST PDT* Philippines # 8:30 KST KDT Korea when at +0830 # 9:00 WIT east Indonesia (Waktu Indonesia Timur) # 9:00 JST JDT Japan # 9:00 KST KDT Korea when at +09 # *I invented the abbreviations HKWT and PDT; see below. # Otherwise, these tables typically use numeric abbreviations like +03 # and +0330 for integer hour and minute UT offsets. Although earlier # editions invented alphabetic time zone abbreviations for every # offset, this did not reflect common practice. # # See the 'europe' file for Russia and Turkey in Asia. # From Guy Harris: # Incorporates data for Singapore from Robert Elz' asia 1.1, as well as # additional information from Tom Yap, Sun Microsystems Intercontinental # Technical Support (including a page from the Official Airline Guide - # Worldwide Edition). ############################################################################### # These rules are stolen from the 'europe' file. # Rule NAME FROM TO - IN ON AT SAVE LETTER/S Rule EUAsia 1981 max - Mar lastSun 1:00u 1:00 S Rule EUAsia 1979 1995 - Sep lastSun 1:00u 0 - Rule EUAsia 1996 max - Oct lastSun 1:00u 0 - Rule E-EurAsia 1981 max - Mar lastSun 0:00 1:00 - Rule E-EurAsia 1979 1995 - Sep lastSun 0:00 0 - Rule E-EurAsia 1996 max - Oct lastSun 0:00 0 - Rule RussiaAsia 1981 1984 - Apr 1 0:00 1:00 - Rule RussiaAsia 1981 1983 - Oct 1 0:00 0 - Rule RussiaAsia 1984 1995 - Sep lastSun 2:00s 0 - Rule RussiaAsia 1985 2010 - Mar lastSun 2:00s 1:00 - Rule RussiaAsia 1996 2010 - Oct lastSun 2:00s 0 - # Afghanistan # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone Asia/Kabul 4:36:48 - LMT 1890 4:00 - %z 1945 4:30 - %z # Armenia # From Paul Eggert (2006-03-22): # Shanks & Pottenger have Yerevan switching to 3:00 (with Russian DST) # in spring 1991, then to 4:00 with no DST in fall 1995, then # readopting Russian DST in 1997. Go with Shanks & Pottenger, even # when they disagree with others. Edgar Der-Danieliantz # reported (1996-05-04) that Yerevan probably wouldn't use DST # in 1996, though it did use DST in 1995. IATA SSIM (1991/1998) reports that # Armenia switched from 3:00 to 4:00 in 1998 and observed DST after 1991, # but started switching at 3:00s in 1998. # From Arthur David Olson (2011-06-15): # While Russia abandoned DST in 2011, Armenia may choose to # follow Russia's "old" rules. # From Alexander Krivenyshev (2012-02-10): # According to News Armenia, on Feb 9, 2012, # http://newsarmenia.ru/society/20120209/42609695.html # # The Armenia National Assembly adopted final reading of Amendments to the # Law "On procedure of calculation time on the territory of the Republic of # Armenia" according to which Armenia [is] abolishing Daylight Saving Time. # or # (brief) # http://www.worldtimezone.com/dst_news/dst_news_armenia03.html # Rule NAME FROM TO - IN ON AT SAVE LETTER/S Rule Armenia 2011 only - Mar lastSun 2:00s 1:00 - Rule Armenia 2011 only - Oct lastSun 2:00s 0 - # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone Asia/Yerevan 2:58:00 - LMT 1924 May 2 3:00 - %z 1957 Mar 4:00 RussiaAsia %z 1991 Mar 31 2:00s 3:00 RussiaAsia %z 1995 Sep 24 2:00s 4:00 - %z 1997 4:00 RussiaAsia %z 2011 4:00 Armenia %z # Azerbaijan # From Rustam Aliyev of the Azerbaijan Internet Forum (2005-10-23): # According to the resolution of Cabinet of Ministers, 1997 # From Paul Eggert (2015-09-17): It was Resolution No. 21 (1997-03-17). # http://code.az/files/daylight_res.pdf # From Steffen Thorsen (2016-03-17): # ... the Azerbaijani Cabinet of Ministers has cancelled switching to # daylight saving time.... # https://www.azernews.az/azerbaijan/94137.html # http://vestnikkavkaza.net/news/Azerbaijani-Cabinet-of-Ministers-cancels-daylight-saving-time.html # http://en.apa.az/xeber_azerbaijan_abolishes_daylight_savings_ti_240862.html # Rule NAME FROM TO - IN ON AT SAVE LETTER/S Rule Azer 1997 2015 - Mar lastSun 4:00 1:00 - Rule Azer 1997 2015 - Oct lastSun 5:00 0 - # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone Asia/Baku 3:19:24 - LMT 1924 May 2 3:00 - %z 1957 Mar 4:00 RussiaAsia %z 1991 Mar 31 2:00s 3:00 RussiaAsia %z 1992 Sep lastSun 2:00s 4:00 - %z 1996 4:00 EUAsia %z 1997 4:00 Azer %z # Bangladesh # From Alexander Krivenyshev (2009-05-13): # According to newspaper Asian Tribune (May 6, 2009) Bangladesh may introduce # Daylight Saving Time from June 16 to Sept 30 # # Bangladesh to introduce daylight saving time likely from June 16 # http://www.asiantribune.com/?q=node/17288 # http://www.worldtimezone.com/dst_news/dst_news_bangladesh02.html # # "... Bangladesh government has decided to switch daylight saving time from # June # 16 till September 30 in a bid to ensure maximum use of daylight to cope with # crippling power crisis. " # # The switch will remain in effect from June 16 to Sept 30 (2009) but if # implemented the next year, it will come in force from April 1, 2010 # From Steffen Thorsen (2009-06-02): # They have finally decided now, but changed the start date to midnight between # the 19th and 20th, and they have not set the end date yet. # # Some sources: # https://in.reuters.com/article/southAsiaNews/idINIndia-40017620090601 # http://bdnews24.com/details.php?id=85889&cid=2 # # Our wrap-up: # https://www.timeanddate.com/news/time/bangladesh-daylight-saving-2009.html # From A. N. M. Kamrus Saadat (2009-06-15): # Finally we've got the official mail regarding DST start time where DST start # time is mentioned as Jun 19 2009, 23:00 from BTRC (Bangladesh # Telecommunication Regulatory Commission). # # No DST end date has been announced yet. # From Alexander Krivenyshev (2009-09-25): # Bangladesh won't go back to Standard Time from October 1, 2009, # instead it will continue DST measure till the cabinet makes a fresh decision. # # Following report by same newspaper-"The Daily Star Friday": # "DST change awaits cabinet decision-Clock won't go back by 1-hr from Oct 1" # http://www.thedailystar.net/newDesign/news-details.php?nid=107021 # http://www.worldtimezone.com/dst_news/dst_news_bangladesh04.html # From Steffen Thorsen (2009-10-13): # IANS (Indo-Asian News Service) now reports: # Bangladesh has decided that the clock advanced by an hour to make # maximum use of daylight hours as an energy saving measure would # "continue for an indefinite period." # # One of many places where it is published: # http://www.thaindian.com/newsportal/business/bangladesh-to-continue-indefinitely-with-advanced-time_100259987.html # From Alexander Krivenyshev (2009-12-24): # According to Bangladesh newspaper "The Daily Star," # Bangladesh will change its clock back to Standard Time on Dec 31, 2009. # # Clock goes back 1-hr on Dec 31 night. # http://www.thedailystar.net/newDesign/news-details.php?nid=119228 # http://www.worldtimezone.com/dst_news/dst_news_bangladesh05.html # # "...The government yesterday decided to put the clock back by one hour # on December 31 midnight and the new time will continue until March 31, # 2010 midnight. The decision came at a cabinet meeting at the Prime # Minister's Office last night..." # From Alexander Krivenyshev (2010-03-22): # According to Bangladesh newspaper "The Daily Star," # Cabinet cancels Daylight Saving Time # http://www.thedailystar.net/newDesign/latest_news.php?nid=22817 # http://www.worldtimezone.com/dst_news/dst_news_bangladesh06.html # Rule NAME FROM TO - IN ON AT SAVE LETTER/S Rule Dhaka 2009 only - Jun 19 23:00 1:00 - Rule Dhaka 2009 only - Dec 31 24:00 0 - # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone Asia/Dhaka 6:01:40 - LMT 1890 5:53:20 - HMT 1941 Oct # Howrah Mean Time? 6:30 - %z 1942 May 15 5:30 - %z 1942 Sep 6:30 - %z 1951 Sep 30 6:00 - %z 2009 6:00 Dhaka %z # Bhutan # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone Asia/Thimphu 5:58:36 - LMT 1947 Aug 15 # or Thimbu 5:30 - %z 1987 Oct 6:00 - %z # British Indian Ocean Territory # Whitman and the 1995 CIA time zone map say 5:00, but the # 1997 and later maps say 6:00. Assume the switch occurred in 1996. # We have no information as to when standard time was introduced; # assume it occurred in 1907, the same year as Mauritius (which # then contained the Chagos Archipelago). # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone Indian/Chagos 4:49:40 - LMT 1907 5:00 - %z 1996 6:00 - %z # Cocos (Keeling) Islands # Myanmar (Burma) # Milne says 6:24:40 was the meridian of the time ball observatory at Rangoon. # From Paul Eggert (2017-04-20): # Page 27 of Reed & Low (cited for Asia/Kolkata) says "Rangoon local time is # used upon the railways and telegraphs of Burma, and is 6h. 24m. 47s. ahead # of Greenwich." This refers to the period before Burma's transition to +0630, # a transition for which Shanks is the only source. # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone Asia/Yangon 6:24:47 - LMT 1880 # or Rangoon 6:24:47 - RMT 1920 # Rangoon local time 6:30 - %z 1942 May 9:00 - %z 1945 May 3 6:30 - %z # China # From Phake Nick (2020-04-15): # According to this news report: # http://news.sina.com.cn/c/2004-09-01/19524201403.shtml # on April 11, 1919, newspaper in Shanghai said clocks in Shanghai will spring # forward for an hour starting from midnight of that Saturday. The report did # not mention what happened in Shanghai thereafter, but it mentioned that a # similar trial in Tianjin which ended at October 1st as citizens are told to # recede the clock on September 30 from 12:00pm to 11:00pm. The trial at # Tianjin got terminated in 1920. # # From Paul Eggert (2020-04-15): # The Returns of Trade and Trade Reports, page 711, says "Daylight saving was # given a trial during the year, and from the 12th April to the 1st October # the clocks were all set one hour ahead of sun time. Though the scheme was # generally esteemed a success, it was announced early in 1920 that it would # not be repeated." # # Rule NAME FROM TO - IN ON AT SAVE LETTER/S Rule Shang 1919 only - Apr 12 24:00 1:00 D Rule Shang 1919 only - Sep 30 24:00 0 S # From Paul Eggert (2018-10-02): # The following comes from Table 1 of: # Li Yu. Research on the daylight saving movement in 1940s Shanghai. # Nanjing Journal of Social Sciences. 2014;(2):144-50. # http://oversea.cnki.net/kns55/detail.aspx?dbname=CJFD2014&filename=NJSH201402020 # The table lists dates only; I am guessing 00:00 and 24:00 transition times. # Also, the table lists the planned end of DST in 1949, but the corresponding # zone line cuts this off on May 28, when the Communists took power. # From Phake Nick (2020-04-15): # # For the history of time in Shanghai between 1940-1942, the situation is # actually slightly more complex than the table [below].... At the time, # there were three different authorities in Shanghai, including Shanghai # International Settlement, a settlement established by western countries with # its own westernized form of government, Shanghai French Concession, similar # to the international settlement but is controlled by French, and then the # rest of the city of Shanghai, which have already been controlled by Japanese # force through a puppet local government (Wang Jingwei regime). It was # additionally complicated by the circumstances that, according to the 1940s # Shanghai summer time essay cited in the database, some # departments/businesses/people in the Shanghai city itself during that time # period, refused to change their clock and instead only changed their opening # hours. # # For example, as quoted in the article, in 1940, other than the authority # itself, power, tram, bus companies, cinema, department stores, and other # public service organizations have all decided to follow the summer time and # spring forward the clock. On the other hand, the custom office refused to # spring forward the clock because of worry on mechanical wear to the physical # clock, postal office refused to spring forward because of disruption to # business and log-keeping, although they did changed their office hour to # match rest of the city. So is travel agents, and also weather # observatory. It is said both time standards had their own supporters in the # city at the time, those who prefer new time standard would have moved their # clock while those who prefer the old time standard would keep their clock # unchange, and there were different clocks that use different time standard # in the city at the time for people who use different time standard to adjust # their clock to their preferred time. # # a. For the 1940 May 31 spring forward, the essay [says] ... "Hong # Kong government implemented the spring forward in the same time on # the same date as Shanghai". # # b. For the 1940 fall back, it was said that they initially intended to do # so on September 30 00:59 at night, however they postponed it to October 12 # after discussion with relevant parties. However schools restored to the # original schedule ten days earlier. # # c. For the 1941 spring forward, it is said to start from March 15 # "following the previous year's method", and in addition to that the essay # cited an announcement in 1941 from the Wang regime which said the Special # City of Shanghai under Wang regime control will follow the DST rule set by # the Settlements, irrespective of the original DST plan announced by the Wang # regime for other area under its control(April 1 to September 30). (no idea # to situation before that announcement) # # d. For the 1941 fall back, it was said that the fall back would occurs at # the end of September (A newspaper headline cited by the essay, published on # October 1, 1941, have the headlines which said "French Concession would # rewind to the old clock this morning), but it ultimately didn't happen due # to disagreement between the international settlement authority and the # French concession authority, and the fall back ultimately occurred on # November 1. # # e. In 1941 December, Japan have officially started war with the United # States and the United Kingdom, and in Shanghai they have marched into the # international settlement, taken over its control # # f. For the 1942 spring forward, the essay said that the spring forward # started on January 31. It said this time the custom office and postal # department will also change their clocks, unlike before. # # g. The essay itself didn't cover any specific changes thereafter until the # end of the war, it quoted a November 1942 command from the government of the # Wang regime, which claim the daylight saving time applies year round during # the war. However, the essay ambiguously said the period is "February 1 to # September 30", which I don't really understand what is the meaning of such # period in the context of year round implementation here.. More researches # might be needed to show exactly what happened during that period of time. # From Phake Nick (2020-04-15): # According to a Japanese tour bus pamphlet in Nanjing area believed to be # from around year 1941: http://www.tt-museum.jp/tairiku_0280_nan1941.html , # the schedule listed was in the format of Japanese time. Which indicate some # use of the Japanese time (instead of syncing by DST) might have occurred in # the Yangtze river delta area during that period of time although the scope # of such use will need to be investigated to determine. # # Rule NAME FROM TO - IN ON AT SAVE LETTER/S Rule Shang 1940 only - Jun 1 0:00 1:00 D Rule Shang 1940 only - Oct 12 24:00 0 S Rule Shang 1941 only - Mar 15 0:00 1:00 D Rule Shang 1941 only - Nov 1 24:00 0 S Rule Shang 1942 only - Jan 31 0:00 1:00 D Rule Shang 1945 only - Sep 1 24:00 0 S Rule Shang 1946 only - May 15 0:00 1:00 D Rule Shang 1946 only - Sep 30 24:00 0 S Rule Shang 1947 only - Apr 15 0:00 1:00 D Rule Shang 1947 only - Oct 31 24:00 0 S Rule Shang 1948 1949 - May 1 0:00 1:00 D Rule Shang 1948 1949 - Sep 30 24:00 0 S #plan # From Guy Harris: # People's Republic of China. Yes, they really have only one time zone. # From Bob Devine (1988-01-28): # No they don't. See TIME mag, 1986-02-17 p.52. Even though # China is across 4 physical time zones, before Feb 1, 1986 only the # Peking (Beijing) time zone was recognized. Since that date, China # has two of 'em - Peking's and Ürümqi (named after the capital of # the Xinjiang Uyghur Autonomous Region). I don't know about DST for it. # # . . .I just deleted the DST table and this editor makes it too # painful to suck in another copy. So, here is what I have for # DST start/end dates for Peking's time zone (info from AP): # # 1986 May 4 - Sept 14 # 1987 mid-April - ?? # From U. S. Naval Observatory (1989-01-19): # CHINA 8 H AHEAD OF UTC ALL OF CHINA, INCL TAIWAN # CHINA 9 H AHEAD OF UTC APR 17 - SEP 10 # From Paul Eggert (2008-02-11): # Jim Mann, "A clumsy embrace for another western custom: China on daylight # time - sort of", Los Angeles Times, 1986-05-05 ... [says] that China began # observing daylight saving time in 1986. # From P Chan (2018-05-07): # The start and end time of DST in China [from 1986 on] should be 2:00 # (i.e. 2:00 to 3:00 at the start and 2:00 to 1:00 at the end).... # Government notices about summer time: # # 1986-04-12 http://www.zj.gov.cn/attach/zfgb/198608.pdf p.21-22 # (To establish summer time from 1986. On 4 May, set the clocks ahead one hour # at 2 am. On 14 September, set the clocks backward one hour at 2 am.) # # 1987-02-15 http://www.gov.cn/gongbao/shuju/1987/gwyb198703.pdf p.114 # (Summer time in 1987 to start from 12 April until 13 September) # # 1987-09-09 http://www.gov.cn/gongbao/shuju/1987/gwyb198721.pdf p.709 # (From 1988, summer time to start from 2 am of the first Sunday of mid-April # until 2 am of the first Sunday of mid-September) # # 1992-03-03 http://www.gov.cn/gongbao/shuju/1992/gwyb199205.pdf p.152 # (To suspend summer time from 1992) # # The first page of People's Daily on 12 April 1988 stating that summer time # to begin on 17 April. # http://data.people.com.cn/pic/101p/1988/04/1988041201.jpg # Rule NAME FROM TO - IN ON AT SAVE LETTER/S Rule PRC 1986 only - May 4 2:00 1:00 D Rule PRC 1986 1991 - Sep Sun>=11 2:00 0 S Rule PRC 1987 1991 - Apr Sun>=11 2:00 1:00 D # From Anthony Fok (2001-12-20): # BTW, I did some research on-line and found some info regarding these five # historic timezones from some Taiwan websites. And yes, there are official # Chinese names for these locales (before 1949). # # From Jesper Nørgaard Welen (2006-07-14): # I have investigated the timezones around 1970 on the # https://www.astro.com/atlas site [with provinces and county # boundaries summarized below].... A few other exceptions were two # counties on the Sichuan side of the Xizang-Sichuan border, # counties Dege and Baiyu which lies on the Sichuan side and are # therefore supposed to be GMT+7, Xizang region being GMT+6, but Dege # county is GMT+8 according to astro.com while Baiyu county is GMT+6 # (could be true), for the moment I am assuming that those two # counties are mistakes in the astro.com data. # From Paul Eggert (2017-01-05): # Alois Treindl kindly sent me translations of the following two sources: # # (1) # Guo Qing-sheng (National Time-Service Center, CAS, Xi'an 710600, China) # Beijing Time at the Beginning of the PRC # China Historical Materials of Science and Technology # (Zhongguo ke ji shi liao, 中国科技史料). 2003;24(1):5-9. # http://oversea.cnki.net/kcms/detail/detail.aspx?filename=ZGKS200301000&dbname=CJFD2003 # It gives evidence that at the beginning of the PRC, Beijing time was # officially apparent solar time! However, Guo also says that the # evidence is dubious, as the relevant institute of astronomy had not # been taken over by the PRC yet. It's plausible that apparent solar # time was announced but never implemented, and that people continued # to use UT+8. As the Shanghai radio station (and I presume the # observatory) was still under control of French missionaries, it # could well have ignored any such mandate. # # (2) # Guo Qing-sheng (Shaanxi Astronomical Observatory, CAS, Xi'an 710600, China) # A Study on the Standard Time Changes for the Past 100 Years in China # [undated and unknown publication location] # It says several things: # * The Qing dynasty used local apparent solar time throughout China. # * The Republic of China instituted Beijing mean solar time effective # the official calendar book of 1914. # * The French Concession in Shanghai set up signal stations in # French docks in the 1890s, controlled by Xujiahui (Zikawei) # Observatory and set to local mean time. # * "From the end of the 19th century" it changed to UT+8. # * Chinese Customs (by then reduced to a tool of foreign powers) # eventually standardized on this time for all ports, and it # became used by railways as well. # * In 1918 the Central Observatory proposed dividing China into # five time zones (see below for details). This caught on # at first only in coastal areas observing UT+8. # * During WWII all of China was in theory was at UT+7. In practice # this was ignored in the west, and I presume was ignored in # Japanese-occupied territory. # * Japanese-occupied Manchuria was at UT+9, i.e., Japan time. # * The five-zone plan was resurrected after WWII and officially put into # place (with some modifications) in March 1948. It's not clear # how well it was observed in areas under Nationalist control. # * The People's Liberation Army used UT+8 during the civil war. # # An AP article "Shanghai Internat'l Area Little Changed" in the # Lewiston (ME) Daily Sun (1939-05-29), p 17, said "Even the time is # different - the occupied districts going by Tokyo time, an hour # ahead of that prevailing in the rest of Shanghai." Guess that the # Xujiahui Observatory was under French control and stuck with UT +08. # # In earlier versions of this file, China had many separate Zone entries, but # this was based on what were apparently incorrect data in Shanks & Pottenger. # This has now been simplified to the two entries Asia/Shanghai and # Asia/Urumqi, with the others being links for backward compatibility. # Proposed in 1918 and theoretically in effect until 1949 (although in practice # mainly observed in coastal areas), the five zones were: # # Changbai Time ("Long-white Time", Long-white = Heilongjiang area) UT +08:30 # Now part of Asia/Shanghai; its pre-1970 times are not recorded here. # Heilongjiang (except Mohe county), Jilin # # Zhongyuan Time ("Central plain Time") UT +08 # Now part of Asia/Shanghai. # most of China # Milne gives 8:05:43.2 for Xujiahui Observatory time.... # Guo says Shanghai switched to UT +08 "from the end of the 19th century". # # Long-shu Time (probably as Long and Shu were two names of the area) UT +07 # Now part of Asia/Shanghai; its pre-1970 times are not recorded here. # Guangxi, Guizhou, Hainan, Ningxia, Sichuan, Shaanxi, and Yunnan; # most of Gansu; west Inner Mongolia; east Qinghai; and the Guangdong # counties Deqing, Enping, Kaiping, Luoding, Taishan, Xinxing, # Yangchun, Yangjiang, Yu'nan, and Yunfu. # # Xin-zang Time ("Xinjiang-Tibet Time") UT +06 # This region is now part of either Asia/Urumqi or Asia/Shanghai with # current boundaries uncertain; times before 1970 for areas that # disagree with Ürümqi or Shanghai are not recorded here. # The Gansu counties Aksay, Anxi, Dunhuang, Subei; west Qinghai; # the Guangdong counties Xuwen, Haikang, Suixi, Lianjiang, # Zhanjiang, Wuchuan, Huazhou, Gaozhou, Maoming, Dianbai, and Xinyi; # east Tibet, including Lhasa, Chamdo, Shigaise, Jimsar, Shawan and Hutubi; # east Xinjiang, including Ürümqi, Turpan, Karamay, Korla, Minfeng, Jinghe, # Wusu, Qiemo, Xinyan, Wulanwusu, Jinghe, Yumin, Tacheng, Tuoli, Emin, # Shihezi, Changji, Yanqi, Heshuo, Tuokexun, Tulufan, Shanshan, Hami, # Fukang, Kuitun, Kumukuli, Miquan, Qitai, and Turfan. # # Kunlun Time UT +05:30 # This region is now in the same status as Xin-zang Time (see above). # West Tibet, including Pulan, Aheqi, Shufu, Shule; # West Xinjiang, including Aksu, Atushi, Yining, Hetian, Cele, Luopu, Nileke, # Zhaosu, Tekesi, Gongliu, Chabuchaer, Huocheng, Bole, Pishan, Suiding, # and Yarkand. # From Luther Ma (2009-10-17): # Almost all (>99.9%) ethnic Chinese (properly ethnic Han) living in # Xinjiang use Chinese Standard Time. Some are aware of Xinjiang time, # but have no need of it. All planes, trains, and schools function on # what is called "Beijing time." When Han make an appointment in Chinese # they implicitly use Beijing time. # # On the other hand, ethnic Uyghurs, who make up about half the # population of Xinjiang, typically use "Xinjiang time" which is two # hours behind Beijing time, or UT +06. The government of the Xinjiang # Uyghur Autonomous Region, (XAUR, or just Xinjiang for short) as well as # local governments such as the Ürümqi city government use both times in # publications, referring to what is popularly called Xinjiang time as # "Ürümqi time." When Uyghurs make an appointment in the Uyghur language # they almost invariably use Xinjiang time. # # (Their ethnic Han compatriots would typically have no clue of its # widespread use, however, because so extremely few of them are fluent in # Uyghur, comparable to the number of Anglo-Americans fluent in Navajo.) # # (...As with the rest of China there was a brief interval ending in 1990 # or 1991 when summer time was in use. The confusion was severe, with # the province not having dual times but four times in use at the same # time. Some areas remained on standard Xinjiang time or Beijing time and # others moving their clocks ahead.) # From Luther Ma (2009-11-19): # With the risk of being redundant to previous answers these are the most common # English "transliterations" (w/o using non-English symbols): # # 1. Wulumuqi... # 2. Kashi... # 3. Urumqi... # 4. Kashgar... # ... # 5. It seems that Uyghurs in Ürümqi has been using Xinjiang since at least the # 1960's. I know of one Han, now over 50, who grew up in the surrounding # countryside and used Xinjiang time as a child. # # 6. Likewise for Kashgar and the rest of south Xinjiang I don't know of any # start date for Xinjiang time. # # Without having access to local historical records, nor the ability to legally # publish them, I would go with October 1, 1949, when Xinjiang became the Uyghur # Autonomous Region under the PRC. (Before that Uyghurs, of course, would also # not be using Beijing time, but some local time.) # From David Cochrane (2014-03-26): # Just a confirmation that Ürümqi time was implemented in Ürümqi on 1 Feb 1986: # https://content.time.com/time/magazine/article/0,9171,960684,00.html # From Luther Ma (2014-04-22): # I have interviewed numerous people of various nationalities and from # different localities in Xinjiang and can confirm the information in Guo's # report regarding Xinjiang, as well as the Time article reference by David # Cochrane. Whether officially recognized or not (and both are officially # recognized), two separate times have been in use in Xinjiang since at least # the Cultural Revolution: Xinjiang Time (XJT), aka Ürümqi Time or local time; # and Beijing Time. There is no confusion in Xinjiang as to which name refers # to which time. Both are widely used in the province, although in some # population groups might be use one to the exclusion of the other. The only # problem is that computers and smart phones list Ürümqi (or Kashgar) as # having the same time as Beijing. # From Paul Eggert (2014-06-30): # In the early days of the PRC, Tibet was given its own time zone (UT +06) # but this was withdrawn in 1959 and never reinstated; see Tubten Khétsun, # Memories of life in Lhasa under Chinese Rule, Columbia U Press, ISBN # 978-0231142861 (2008), translator's introduction by Matthew Akester, p x. # As this is before our 1970 cutoff, Tibet doesn't need a separate zone. # # Xinjiang Time is well-documented as being officially recognized. E.g., see # "The Working-Calendar for The Xinjiang Uygur Autonomous Region Government" # (2014-04-22). # Unfortunately, we have no good records of time in Xinjiang before 1986. # During the 20th century parts of Xinjiang were ruled by the Qing dynasty, # the Republic of China, various warlords, the First and Second East Turkestan # Republics, the Soviet Union, the Kuomintang, and the People's Republic of # China, and tracking down all these organizations' timekeeping rules would be # quite a trick. Approximate this lost history by a transition from LMT to # UT +06 at the start of 1928, the year of accession of the warlord Jin Shuren, # which happens to be the date given by Shanks & Pottenger (no doubt as a # guess) as the transition from LMT. Ignore the usage of +08 before # 1986-02-01 under the theory that the transition date to +08 is unknown and # that the sort of users who prefer Asia/Urumqi now typically ignored the # +08 mandate back then. # Zone NAME STDOFF RULES FORMAT [UNTIL] # Beijing time, used throughout China; represented by Shanghai. #STDOFF 8:05:43.2 Zone Asia/Shanghai 8:05:43 - LMT 1901 8:00 Shang C%sT 1949 May 28 8:00 PRC C%sT # Xinjiang time, used by many in western China; represented by Ürümqi / Ürümchi # / Wulumuqi. (Please use Asia/Shanghai if you prefer Beijing time.) Zone Asia/Urumqi 5:50:20 - LMT 1928 6:00 - %z # Hong Kong # Milne gives 7:36:41.7. # From Lee Yiu Chung (2009-10-24): # I found there are some mistakes for the...DST rule for Hong # Kong. [According] to the DST record from Hong Kong Observatory (actually, # it is not [an] observatory, but the official meteorological agency of HK, # and also serves as the official timing agency), there are some missing # and incorrect rules. Although the exact switch over time is missing, I # think 3:30 is correct. # From Phake Nick (2018-10-27): # According to Singaporean newspaper # http://eresources.nlb.gov.sg/newspapers/Digitised/Article/singfreepresswk19041102-1.2.37 # the day that Hong Kong start using GMT+8 should be Oct 30, 1904. # # From Paul Eggert (2018-11-17): # Hong Kong had a time ball near the Marine Police Station, Tsim Sha Tsui. # "The ball was raised manually each day and dropped at exactly 1pm # (except on Sundays and Government holidays)." # Dyson AD. From Time Ball to Atomic Clock. Hong Kong Government. 1983. # # "From 1904 October 30 the time-ball at Hong Kong has been dropped by order # of the Governor of the Colony at 17h 0m 0s G.M.T., which is 23m 18s.14 in # advance of 1h 0m 0s of Hong Kong mean time." # Hollis HP. Universal Time, Longitudes, and Geodesy. Mon Not R Astron Soc. # 1905-02-10;65(4):405-6. https://doi.org/10.1093/mnras/65.4.382 # # From Joseph Myers (2018-11-18): # An astronomer before 1925 referring to GMT would have been using the old # astronomical convention where the day started at noon, not midnight. # # From Steve Allen (2018-11-17): # Meteorological Observations made at the Hongkong Observatory in the year 1904 # page 4 # ... the log of drop times in Table II shows that on Sunday 1904-10-30 the # ball was dropped. So that looks like a special case drop for the sake # of broadcasting the new local time. # # From Phake Nick (2018-11-18): # According to The Hong Kong Weekly Press, 1904-10-29, p.324, the # governor of Hong Kong at the time stated that "We are further desired to # make it known that the change will be effected by firing the gun and by the # dropping of the Ball at 23min. 18sec. before one." # From Paul Eggert (2018-11-18): # See for this; unfortunately Flash is required. # From Phake Nick (2018-10-26): # I went to check microfilm records stored at Hong Kong Public Library.... # on September 30 1941, according to Ta Kung Pao (Hong Kong edition), it was # stated that fallback would occur on the next day (the 1st)'s "03:00 am (Hong # Kong Time 04:00 am)" and the clock will fall back for a half hour. (03:00 # probably refer to the time commonly used in mainland China at the time given # the paper's background) ... the sunrise/sunset time given by South China # Morning Post for October 1st was indeed moved by half an hour compares to # before. After that, in December, the battle to capture Hong Kong started and # the library doesn't seems to have any record stored about press during that # period of time. Some media resumed publication soon after that within the # same month, but there were not much information about time there. Later they # started including a radio program guide when they restored radio service, # explicitly mentioning it use Tokyo standard time, and later added a note # saying it's half an hour ahead of the old Hong Kong standard time, and it # also seems to indicate that Hong Kong was not using GMT+8 when it was # captured by Japan. # # Image of related sections on newspaper: # * 1941-09-30, Ta Kung Pao (Hong Kong), "Winter Time start tomorrow". # https://i.imgur.com/6waY51Z.jpg (Chinese) # * 1941-09-29, South China Morning Post, Information on sunrise/sunset # time and other things for September 30 and October 1. # https://i.imgur.com/kCiUR78.jpg # * 1942-02-05. The Hong Kong News, Radio Program Guide. # https://i.imgur.com/eVvDMzS.jpg # * 1941-06-14. Hong Kong Daily Press, Daylight Saving from 3am Tomorrow. # https://i.imgur.com/05KkvtC.png # * 1941-09-30, Hong Kong Daily Press, Winter Time Warning. # https://i.imgur.com/dge4kFJ.png # From Paul Eggert (2019-07-11): # "Hong Kong winter time" is considered to be daylight saving. # "Hong Kong had adopted daylight saving on June 15 as a wartime measure, # clocks moving forward one hour until October 1, when they would be put back # by just half an hour for 'Hong Kong Winter time', so that daylight saving # operated year round." -- Low Z. The longest day: when wartime Hong Kong # introduced daylight saving. South China Morning Post. 2019-06-28. # https://www.scmp.com/magazines/post-magazine/short-reads/article/3016281/longest-day-when-wartime-hong-kong-introduced # From P Chan (2018-12-31): # * According to the Hong Kong Daylight-Saving Regulations, 1941, the # 1941 spring-forward transition was at 03:00. # http://sunzi.lib.hku.hk/hkgro/view/g1941/304271.pdf # http://sunzi.lib.hku.hk/hkgro/view/g1941/305516.pdf # * According to some articles from South China Morning Post, +08 was # resumed on 1945-11-18 at 02:00. # https://i.imgur.com/M2IsZ3c.png # https://i.imgur.com/iOPqrVo.png # https://i.imgur.com/fffcGDs.png # * Some newspapers ... said the 1946 spring-forward transition was on # 04-21 at 00:00. The Kung Sheung Evening News 1946-04-20 (Chinese) # https://i.imgur.com/ZSzent0.png # https://mmis.hkpl.gov.hk///c/portal/cover?c=QF757YsWv5%2FH7zGe%2FKF%2BFLYsuqGhRBfe p.4 # The Kung Sheung Daily News 1946-04-21 (Chinese) # https://i.imgur.com/7ecmRlcm.png # https://mmis.hkpl.gov.hk///c/portal/cover?c=QF757YsWv5%2BQBGt1%2BwUj5qG2GqtwR3Wh p.4 # * According to the Summer Time Ordinance (1946), the fallback # transitions between 1946 and 1952 were at 03:30 Standard Time (+08) # http://oelawhk.lib.hku.hk/archive/files/bb74b06a74d5294620a15de560ab33c6.pdf # * Some other laws and regulations related to DST from 1953 to 1979 # Summer Time Ordinance 1953 # https://i.imgur.com/IOlJMav.jpg # Summer Time (Amendment) Ordinance 1965 # https://i.imgur.com/8rofeLa.jpg # Interpretation and General Clauses Ordinance (1966) # https://i.imgur.com/joy3msj.jpg # Emergency (Summer Time) Regulation 1973 # Interpretation and General Clauses (Amendment) Ordinance 1977 # https://i.imgur.com/RaNqnc4.jpg # Resolution of the Legislative Council passed on 9 May 1979 # https://www.legco.gov.hk/yr78-79/english/lc_sitg/hansard/h790509.pdf#page=39 # From Paul Eggert (2020-04-15): # Here are the dates given at # https://www.hko.gov.hk/en/gts/time/Summertime.htm # as of 2020-02-10: # Year Period # 1941 15 Jun to 30 Sep # 1942 Whole year # 1943 Whole year # 1944 Whole year # 1945 Whole year # 1946 20 Apr to 1 Dec # 1947 13 Apr to 30 Nov # 1948 2 May to 31 Oct # 1949 3 Apr to 30 Oct # 1950 2 Apr to 29 Oct # 1951 1 Apr to 28 Oct # 1952 6 Apr to 2 Nov # 1953 5 Apr to 1 Nov # 1954 21 Mar to 31 Oct # 1955 20 Mar to 6 Nov # 1956 18 Mar to 4 Nov # 1957 24 Mar to 3 Nov # 1958 23 Mar to 2 Nov # 1959 22 Mar to 1 Nov # 1960 20 Mar to 6 Nov # 1961 19 Mar to 5 Nov # 1962 18 Mar to 4 Nov # 1963 24 Mar to 3 Nov # 1964 22 Mar to 1 Nov # 1965 18 Apr to 17 Oct # 1966 17 Apr to 16 Oct # 1967 16 Apr to 22 Oct # 1968 21 Apr to 20 Oct # 1969 20 Apr to 19 Oct # 1970 19 Apr to 18 Oct # 1971 18 Apr to 17 Oct # 1972 16 Apr to 22 Oct # 1973 22 Apr to 21 Oct # 1973/74 30 Dec 73 to 20 Oct 74 # 1975 20 Apr to 19 Oct # 1976 18 Apr to 17 Oct # 1977 Nil # 1978 Nil # 1979 13 May to 21 Oct # 1980 to Now Nil # The page does not give times of day for transitions, # or dates for the 1942 and 1945 transitions. # The Japanese occupation of Hong Kong began 1941-12-25. # Rule NAME FROM TO - IN ON AT SAVE LETTER/S Rule HK 1946 only - Apr 21 0:00 1:00 S Rule HK 1946 only - Dec 1 3:30s 0 - Rule HK 1947 only - Apr 13 3:30s 1:00 S Rule HK 1947 only - Nov 30 3:30s 0 - Rule HK 1948 only - May 2 3:30s 1:00 S Rule HK 1948 1952 - Oct Sun>=28 3:30s 0 - Rule HK 1949 1953 - Apr Sun>=1 3:30 1:00 S Rule HK 1953 1964 - Oct Sun>=31 3:30 0 - Rule HK 1954 1964 - Mar Sun>=18 3:30 1:00 S Rule HK 1965 1976 - Apr Sun>=16 3:30 1:00 S Rule HK 1965 1976 - Oct Sun>=16 3:30 0 - Rule HK 1973 only - Dec 30 3:30 1:00 S Rule HK 1979 only - May 13 3:30 1:00 S Rule HK 1979 only - Oct 21 3:30 0 - # Zone NAME STDOFF RULES FORMAT [UNTIL] #STDOFF 7:36:41.7 Zone Asia/Hong_Kong 7:36:42 - LMT 1904 Oct 29 17:00u 8:00 - HKT 1941 Jun 15 3:00 8:00 1:00 HKST 1941 Oct 1 4:00 8:00 0:30 HKWT 1941 Dec 25 9:00 - JST 1945 Nov 18 2:00 8:00 HK HK%sT ############################################################################### # Taiwan # From smallufo (2010-04-03): # According to Taiwan's CWB [Central Weather Bureau], # http://www.cwb.gov.tw/V6/astronomy/cdata/summert.htm # Taipei has DST in 1979 between July 1st and Sep 30. # From Yu-Cheng Chuang (2013-07-12): # On Dec 28, 1895, the Meiji Emperor announced Ordinance No. 167 of # Meiji Year 28 "The clause about standard time", mentioned that # Taiwan and Penghu Islands, as well as Yaeyama and Miyako Islands # (both in Okinawa) adopt the Western Standard Time which is based on # 120E. The adoption began from Jan 1, 1896. The original text can be # found on Wikisource: # https://ja.wikisource.org/wiki/標準時ニ關スル件_(公布時) # ... This could be the first adoption of time zone in Taiwan, because # during the Qing Dynasty, it seems that there was no time zone # declared officially. # # Later, in the beginning of World War II, on Sep 25, 1937, the Showa # Emperor announced Ordinance No. 529 of Showa Year 12 "The clause of # revision in the ordinance No. 167 of Meiji year 28 about standard # time", in which abolished the adoption of Western Standard Time in # western islands (listed above), which means the whole Japan # territory, including later occupations, adopt Japan Central Time # (UT+9). The adoption began on Oct 1, 1937. The original text can # be found on Wikisource: # https://ja.wikisource.org/wiki/明治二十八年勅令第百六十七號標準時ニ關スル件中改正ノ件 # # That is, the time zone of Taipei switched to UT+9 on Oct 1, 1937. # From Yu-Cheng Chuang (2014-07-02): # I've found more evidence about when the time zone was switched from UT+9 # back to UT+8 after WW2. I believe it was on Sep 21, 1945. In a document # during Japanese era [1] in which the officer told the staff to change time # zone back to Western Standard Time (UT+8) on Sep 21. And in another # history page of National Cheng Kung University [2], on Sep 21 there is a # note "from today, switch back to Western Standard Time". From these two # materials, I believe that the time zone change happened on Sep 21. And # today I have found another monthly journal called "The Astronomical Herald" # from The Astronomical Society of Japan [3] in which it mentioned the fact # that: # # 1. Standard Time of the Country (Japan) was adopted on Jan 1, 1888, using # the time at 135E (GMT+9) # # 2. Standard Time of the Country was renamed to Central Standard Time, on Jan # 1, 1898, and on the same day, the new territories Taiwan and Penghu islands, # as well as Yaeyama and Miyako islands, adopted a new time zone called # Western Standard Time, which is in GMT+8. # # 3. Western Standard Time was deprecated on Sep 30, 1937. From then all the # territories of Japan adopted the same time zone, which is Central Standard # Time. # # [1] Academica Historica, Taiwan: # http://163.29.208.22:8080/govsaleShowImage/connect_img.php?s=00101738900090036&e=00101738900090037 # [2] Nat'l Cheng Kung University 70th Anniversary Special Site: # http://www.ncku.edu.tw/~ncku70/menu/001/01_01.htm # [3] Yukio Niimi, The Standard Time in Japan (1997), p.475: # http://www.asj.or.jp/geppou/archive_open/1997/pdf/19971001c.pdf # Yu-Cheng Chuang (2014-07-03): # I finally have found the real official gazette about changing back to # Western Standard Time on Sep 21 in Taiwan. It's Taiwan Governor-General # Bulletin No. 386 in Showa 20 years (1945), published on Sep 19, 1945. [1] ... # [It] abolishes Bulletin No. 207 in Showa 12 years (1937), which is a local # bulletin in Taiwan for that Ordinance No. 529. It also mentioned that 1am on # Sep 21, 1945 will be 12am on Sep 21. I think this bulletin is much more # official than the one I mentioned in my first mail, because it's from the # top-level government in Taiwan. If you're going to quote any resource, this # would be a good one. # [1] Taiwan Governor-General Gazette, No. 1018, Sep 19, 1945: # http://db2.th.gov.tw/db2/view/viewImg.php?imgcode=0072031018a&num=19&bgn=019&end=019&otherImg=&type=gener # From Yu-Cheng Chuang (2014-07-02): # In 1946, DST in Taiwan was from May 15 and ended on Sep 30. The info from # Central Weather Bureau website was not correct. # # Original Bulletin: # http://subtpg.tpg.gov.tw/og/image2.asp?f=03502F0AKM1AF # http://subtpg.tpg.gov.tw/og/image2.asp?f=0350300AKM1B0 (cont.) # # In 1947, DST in Taiwan was expanded to Oct 31. There is a backup of that # telegram announcement from Taiwan Province Government: # # http://subtpg.tpg.gov.tw/og/image2.asp?f=0360310AKZ431 # # Here is a brief translation: # # The Summer Time this year is adopted from midnight Apr 15 until Sep 20 # midnight. To save (energy?) consumption, we're expanding Summer Time # adoption till Oct 31 midnight. # # The Central Weather Bureau website didn't mention that, however it can # be found from historical government announcement database. # From Paul Eggert (2014-07-03): # As per Yu-Cheng Chuang, say that Taiwan was at UT +09 from 1937-10-01 # until 1945-09-21 at 01:00, overriding Shanks & Pottenger. # Likewise, use Yu-Cheng Chuang's data for DST in Taiwan. # Rule NAME FROM TO - IN ON AT SAVE LETTER/S Rule Taiwan 1946 only - May 15 0:00 1:00 D Rule Taiwan 1946 only - Oct 1 0:00 0 S Rule Taiwan 1947 only - Apr 15 0:00 1:00 D Rule Taiwan 1947 only - Nov 1 0:00 0 S Rule Taiwan 1948 1951 - May 1 0:00 1:00 D Rule Taiwan 1948 1951 - Oct 1 0:00 0 S Rule Taiwan 1952 only - Mar 1 0:00 1:00 D Rule Taiwan 1952 1954 - Nov 1 0:00 0 S Rule Taiwan 1953 1959 - Apr 1 0:00 1:00 D Rule Taiwan 1955 1961 - Oct 1 0:00 0 S Rule Taiwan 1960 1961 - Jun 1 0:00 1:00 D Rule Taiwan 1974 1975 - Apr 1 0:00 1:00 D Rule Taiwan 1974 1975 - Oct 1 0:00 0 S Rule Taiwan 1979 only - Jul 1 0:00 1:00 D Rule Taiwan 1979 only - Oct 1 0:00 0 S # Zone NAME STDOFF RULES FORMAT [UNTIL] # Taipei or Taibei or T'ai-pei Zone Asia/Taipei 8:06:00 - LMT 1896 Jan 1 8:00 - CST 1937 Oct 1 9:00 - JST 1945 Sep 21 1:00 8:00 Taiwan C%sT # Macau (Macao, Aomen) # # From P Chan (2018-05-10): # * LegisMac # http://legismac.safp.gov.mo/legismac/descqry/Descqry.jsf?lang=pt # A database for searching titles of legal documents of Macau in # Chinese and Portuguese. The term "HORÁRIO DE VERÃO" can be used for # searching decrees about summer time. # * Archives of Macao # http://www.archives.gov.mo/en/bo/ # It contains images of old official gazettes. # * The Macao Meteorological and Geophysical Bureau have a page listing the # summer time history. But it is not complete and has some mistakes. # http://www.smg.gov.mo/smg/geophysics/e_t_Summer%20Time.htm # Macau adopted GMT+8 on 30 Oct 1904 to follow Hong Kong. Clocks were # advanced by 25 minutes and 50 seconds. Which means the LMT used was # +7:34:10. As stated in the "Portaria No. 204" dated 21 October 1904 # and published in the Official Gazette on 29 October 1904. # http://igallery.icm.gov.mo/Images/Archives/BO/MO_AH_PUB_BO_1904_10/MO_AH_PUB_BO_1904_10_00025_Grey.JPG # # Therefore the 1911 decree of Portugal did not change time in Macau. # # From LegisMac, here is a list of decrees that changed the time ... # [Decree Gazette-no. date; titles omitted in this quotation] # DIL 732 BOCM 51 1941.12.20 # DIL 764 BOCM 9S 1942.04.30 # DIL 781 BOCM 21 1942.10.10 # PT 3434 BOCM 8S 1943.04.17 # PT 3504 BOCM 20 1943.09.25 # PT 3843 BOCM 39 1945.09.29 # PT 3961 BOCM 17 1946.04.27 # PT 4026 BOCM 39 1946.09.28 # PT 4153 BOCM 16 1947.04.10 # PT 4271 BOCM 48 1947.11.29 # PT 4374 BOCM 18 1948.05.01 # PT 4465 BOCM 44 1948.10.30 # PT 4590 BOCM 14 1949.04.02 # PT 4666 BOCM 44 1949.10.29 # PT 4771 BOCM 12 1950.03.25 # PT 4838 BOCM 43 1950.10.28 # PT 4946 BOCM 12 1951.03.24 # PT 5025 BO 43 1951.10.27 # PT 5149 BO 14 1952.04.05 # PT 5251 BO 43 1952.10.25 # PT 5366 BO 13 1953.03.28 # PT 5444 BO 44 1953.10.31 # PT 5540 BO 12 1954.03.20 # PT 5589 BO 44 1954.10.30 # PT 5676 BO 12 1955.03.19 # PT 5739 BO 45 1955.11.05 # PT 5823 BO 11 1956.03.17 # PT 5891 BO 44 1956.11.03 # PT 5981 BO 12 1957.03.23 # PT 6064 BO 43 1957.10.26 # PT 6172 BO 12 1958.03.22 # PT 6243 BO 43 1958.10.25 # PT 6341 BO 12 1959.03.21 # PT 6411 BO 43 1959.10.24 # PT 6514 BO 11 1960.03.12 # PT 6584 BO 44 1960.10.29 # PT 6721 BO 10 1961.03.11 # PT 6815 BO 43 1961.10.28 # PT 6947 BO 10 1962.03.10 # PT 7080 BO 43 1962.10.27 # PT 7218 BO 12 1963.03.23 # PT 7340 BO 43 1963.10.26 # PT 7491 BO 11 1964.03.14 # PT 7664 BO 43 1964.10.24 # PT 7846 BO 15 1965.04.10 # PT 7979 BO 42 1965.10.16 # PT 8146 BO 15 1966.04.09 # PT 8252 BO 41 1966.10.08 # PT 8429 BO 15 1967.04.15 # PT 8540 BO 41 1967.10.14 # PT 8735 BO 15 1968.04.13 # PT 8860 BO 41 1968.10.12 # PT 9035 BO 16 1969.04.19 # PT 9156 BO 42 1969.10.18 # PT 9328 BO 15 1970.04.11 # PT 9418 BO 41 1970.10.10 # PT 9587 BO 14 1971.04.03 # PT 9702 BO 41 1971.10.09 # PT 38-A/72 BO 14 1972.04.01 # PT 126-A/72 BO 41 1972.10.07 # PT 61/73 BO 14 1973.04.07 # PT 182/73 BO 40 1973.10.06 # PT 282/73 BO 51 1973.12.22 # PT 177/74 BO 41 1974.10.12 # PT 51/75 BO 15 1975.04.12 # PT 173/75 BO 41 1975.10.11 # PT 67/76/M BO 14 1976.04.03 # PT 169/76/M BO 41 1976.10.09 # PT 78/79/M BO 19 1979.05.12 # PT 166/79/M BO 42 1979.10.20 # Note that DIL 732 does not belong to "HORÁRIO DE VERÃO" according to # LegisMac.... Note that between 1942 and 1945, the time switched # between GMT+9 and GMT+10. Also in 1965 and 1965 the DST ended at 2:30am. # From Paul Eggert (2018-05-10): # The 1904 decree says that Macau changed from the meridian of # Fortaleza do Monte, presumably the basis for the 7:34:10 for LMT. # Rule NAME FROM TO - IN ON AT SAVE LETTER/S Rule Macau 1942 1943 - Apr 30 23:00 1:00 - Rule Macau 1942 only - Nov 17 23:00 0 - Rule Macau 1943 only - Sep 30 23:00 0 S Rule Macau 1946 only - Apr 30 23:00s 1:00 D Rule Macau 1946 only - Sep 30 23:00s 0 S Rule Macau 1947 only - Apr 19 23:00s 1:00 D Rule Macau 1947 only - Nov 30 23:00s 0 S Rule Macau 1948 only - May 2 23:00s 1:00 D Rule Macau 1948 only - Oct 31 23:00s 0 S Rule Macau 1949 1950 - Apr Sat>=1 23:00s 1:00 D Rule Macau 1949 1950 - Oct lastSat 23:00s 0 S Rule Macau 1951 only - Mar 31 23:00s 1:00 D Rule Macau 1951 only - Oct 28 23:00s 0 S Rule Macau 1952 1953 - Apr Sat>=1 23:00s 1:00 D Rule Macau 1952 only - Nov 1 23:00s 0 S Rule Macau 1953 1954 - Oct lastSat 23:00s 0 S Rule Macau 1954 1956 - Mar Sat>=17 23:00s 1:00 D Rule Macau 1955 only - Nov 5 23:00s 0 S Rule Macau 1956 1964 - Nov Sun>=1 03:30 0 S Rule Macau 1957 1964 - Mar Sun>=18 03:30 1:00 D Rule Macau 1965 1973 - Apr Sun>=16 03:30 1:00 D Rule Macau 1965 1966 - Oct Sun>=16 02:30 0 S Rule Macau 1967 1976 - Oct Sun>=16 03:30 0 S Rule Macau 1973 only - Dec 30 03:30 1:00 D Rule Macau 1975 1976 - Apr Sun>=16 03:30 1:00 D Rule Macau 1979 only - May 13 03:30 1:00 D Rule Macau 1979 only - Oct Sun>=16 03:30 0 S # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone Asia/Macau 7:34:10 - LMT 1904 Oct 30 8:00 - CST 1941 Dec 21 23:00 9:00 Macau %z 1945 Sep 30 24:00 8:00 Macau C%sT ############################################################################### # Cyprus # Milne says the Eastern Telegraph Company used 2:14:00. Stick with LMT. # IATA SSIM (1998-09) has Cyprus using EU rules for the first time. # From Paul Eggert (2016-09-09): # Yesterday's Cyprus Mail reports that Northern Cyprus followed Turkey's # lead and switched from +02/+03 to +03 year-round. # http://cyprus-mail.com/2016/09/08/two-time-zones-cyprus-turkey-will-not-turn-clocks-back-next-month/ # # From Even Scharning (2016-10-31): # Looks like the time zone split in Cyprus went through last night. # http://cyprus-mail.com/2016/10/30/cyprus-new-division-two-time-zones-now-reality/ # From Paul Eggert (2017-10-18): # Northern Cyprus will reinstate winter time on October 29, thus # staying in sync with the rest of Cyprus. See: Anastasiou A. # Cyprus to remain united in time. Cyprus Mail 2017-10-17. # https://cyprus-mail.com/2017/10/17/cyprus-remain-united-time/ # Rule NAME FROM TO - IN ON AT SAVE LETTER/S Rule Cyprus 1975 only - Apr 13 0:00 1:00 S Rule Cyprus 1975 only - Oct 12 0:00 0 - Rule Cyprus 1976 only - May 15 0:00 1:00 S Rule Cyprus 1976 only - Oct 11 0:00 0 - Rule Cyprus 1977 1980 - Apr Sun>=1 0:00 1:00 S Rule Cyprus 1977 only - Sep 25 0:00 0 - Rule Cyprus 1978 only - Oct 2 0:00 0 - Rule Cyprus 1979 1997 - Sep lastSun 0:00 0 - Rule Cyprus 1981 1998 - Mar lastSun 0:00 1:00 S # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone Asia/Nicosia 2:13:28 - LMT 1921 Nov 14 2:00 Cyprus EE%sT 1998 Sep 2:00 EUAsia EE%sT Zone Asia/Famagusta 2:15:48 - LMT 1921 Nov 14 2:00 Cyprus EE%sT 1998 Sep 2:00 EUAsia EE%sT 2016 Sep 8 3:00 - %z 2017 Oct 29 1:00u 2:00 EUAsia EE%sT # Georgia # From Paul Eggert (1994-11-19): # Today's _Economist_ (p 60) reports that Georgia moved its clocks forward # an hour recently, due to a law proposed by Zurab Murvanidze, # an MP who went on a hunger strike for 11 days to force discussion about it! # We have no details, but we'll guess they didn't move the clocks back in fall. # # From Mathew Englander, quoting AP (1996-10-23 13:05-04): # Instead of putting back clocks at the end of October, Georgia # will stay on daylight savings time this winter to save energy, # President Eduard Shevardnadze decreed Wednesday. # # From the BBC via Joseph S. Myers (2004-06-27): # # Georgia moved closer to Western Europe on Sunday... The former Soviet # republic has changed its time zone back to that of Moscow. As a result it # is now just four hours ahead of Greenwich Mean Time, rather than five hours # ahead. The switch was decreed by the pro-Western president of Georgia, # Mikheil Saakashvili, who said the change was partly prompted by the process # of integration into Europe. # From Teimuraz Abashidze (2005-11-07): # Government of Georgia ... decided to NOT CHANGE daylight savings time on # [Oct.] 30, as it was done before during last more than 10 years. # Currently, we are in fact GMT +4:00, as before 30 October it was GMT # +3:00.... The problem is, there is NO FORMAL LAW or governmental document # about it. As far as I can find, I was told, that there is no document, # because we just DIDN'T ISSUE document about switching to winter time.... # I don't know what can be done, especially knowing that some years ago our # DST rules where changed THREE TIMES during one month. # Milne 1899 says Tbilisi (Tiflis) time was 2:59:05.7. # Byalokoz 1919 says Georgia was 2:59:11. # Go with Byalokoz. # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone Asia/Tbilisi 2:59:11 - LMT 1880 2:59:11 - TBMT 1924 May 2 # Tbilisi Mean Time 3:00 - %z 1957 Mar 4:00 RussiaAsia %z 1991 Mar 31 2:00s 3:00 RussiaAsia %z 1992 3:00 E-EurAsia %z 1994 Sep lastSun 4:00 E-EurAsia %z 1996 Oct lastSun 4:00 1:00 %z 1997 Mar lastSun 4:00 E-EurAsia %z 2004 Jun 27 3:00 RussiaAsia %z 2005 Mar lastSun 2:00 4:00 - %z # East Timor # From Tim Parenti (2024-07-01): # The 1912-01-01 transition occurred at 00:00 new time, per the 1911-05-24 # Portuguese decree (see Europe/Lisbon). A provision in article 5(c) of the # decree prescribed that Timor "will keep counting time in harmony with # neighboring foreign colonies, [for] as long as they do not adopt the time # that belongs to them in [the Washington Convention] system." # See Indonesia for the 1945 transition. # From João Carrascalão, brother of the former governor of East Timor, in # East Timor may be late for its millennium # (1999-12-26/31): # Portugal tried to change the time forward in 1974 because the sun # rises too early but the suggestion raised a lot of problems with the # Timorese and I still don't think it would work today because it # conflicts with their way of life. # From Paul Eggert (2000-12-04): # We don't have any record of the above attempt. # Most likely our records are incomplete, but we have no better data. # From Manoel de Almeida e Silva, Deputy Spokesman for the UN Secretary-General # http://www.hri.org/news/world/undh/2000/00-08-16.undh.html # (2000-08-16): # The Cabinet of the East Timor Transition Administration decided # today to advance East Timor's time by one hour. The time change, # which will be permanent, with no seasonal adjustment, will happen at # midnight on Saturday, September 16. # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone Asia/Dili 8:22:20 - LMT 1911 Dec 31 16:00u 8:00 - %z 1942 Feb 21 23:00 9:00 - %z 1976 May 3 8:00 - %z 2000 Sep 17 0:00 9:00 - %z # India # British astronomer Henry Park Hollis disliked India Standard Time's offset: # "A new time system has been proposed for India, Further India, and Burmah. # The scheme suggested is that the times of the meridians 5½ and 6½ hours # east of Greenwich should be adopted in these territories. No reason is # given why hourly meridians five hours and six hours east should not be # chosen; a plan which would bring the time of India into harmony with # that of almost the whole of the civilised world." # Hollis HP. Universal Time, Longitudes, and Geodesy. Mon Not R Astron Soc. # 1905-02-10;65(4):405-6. https://doi.org/10.1093/mnras/65.4.382 # From Ian P. Beacock, in "A brief history of (modern) time", The Atlantic # https://www.theatlantic.com/technology/archive/2015/12/the-creation-of-modern-time/421419/ # (2015-12-22): # In January 1906, several thousand cotton-mill workers rioted on the # outskirts of Bombay.... They were protesting the proposed abolition of # local time in favor of Indian Standard Time.... Journalists called this # dispute the "Battle of the Clocks." It lasted nearly half a century. # From Paul Eggert (2017-04-20): # Good luck trying to nail down old timekeeping records in India. # "... in the nineteenth century ... Madras Observatory took its magnetic # measurements on Göttingen time, its meteorological measurements on Madras # (local) time, dropped its time ball on Greenwich (ocean navigator's) time, # and distributed civil (local time)." -- Bartky IR. Selling the true time: # 19th-century timekeeping in america. Stanford U Press (2000), 247 note 19. # "A more potent cause of resistance to the general adoption of the present # standard time lies in the fact that it is Madras time. The citizen of # Bombay, proud of being 'primus in Indis' and of Calcutta, equally proud of # his city being the Capital of India, and - for a part of the year - the Seat # of the Supreme Government, alike look down on Madras, and refuse to change # the time they are using, for that of what they regard as a benighted # Presidency; while Madras, having for long given the standard time to the # rest of India, would resist the adoption of any other Indian standard in its # place." -- Oldham RD. On Time in India: a suggestion for its improvement. # Proceedings of the Asiatic Society of Bengal (April 1899), 49-55. # # "In 1870 ... Madras time - 'now used by the telegraph and regulated from the # only government observatory' - was suggested as a standard railway time, # first to be adopted on the Great Indian Peninsular Railway (GIPR).... # Calcutta, Bombay, and Karachi, were to be allowed to continue with their # local time for civil purposes." - Prasad R. Tracks of Change: Railways and # Everyday Life in Colonial India. Cambridge University Press (2016), 145. # # Reed S, Low F. The Indian Year Book 1936-37. Bennett, Coleman, pp 27-8. # https://archive.org/details/in.ernet.dli.2015.282212 # This lists +052110 as Madras local time used in railways, and says that on # 1906-01-01 railways and telegraphs in India switched to +0530. Some # municipalities retained their former time, and the time in Calcutta # continued to depend on whether you were at the railway station or at # government offices. Government time was at +055320 (according to Shanks) or # at +0554 (according to the Indian Year Book). Railway time is more # appropriate for our purposes, as it was better documented, it is what we do # elsewhere (e.g., Europe/London before 1880), and after 1906 it was # consistent in the region now identified by Asia/Kolkata. So, use railway # time for 1870-1941. Shanks is our only (and dubious) source for the # 1941-1945 data. # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone Asia/Kolkata 5:53:28 - LMT 1854 Jun 28 # Kolkata 5:53:20 - HMT 1870 # Howrah Mean Time? 5:21:10 - MMT 1906 Jan 1 # Madras local time 5:30 - IST 1941 Oct 5:30 1:00 %z 1942 May 15 5:30 - IST 1942 Sep 5:30 1:00 %z 1945 Oct 15 5:30 - IST # Since 1970 the following are like Asia/Kolkata: # Andaman Is # Lakshadweep (Laccadive, Minicoy and Amindivi Is) # Nicobar Is # Indonesia # # From Paul Eggert (2014-09-06): # The 1876 Report of the Secretary of the [US] Navy, p 306 says that Batavia # civil time was 7:07:12.5. # # From Gwillim Law (2001-05-28), overriding Shanks & Pottenger: # http://www.sumatera-inc.com/go_to_invest/about_indonesia.asp#standtime # says that Indonesia's time zones changed on 1988-01-01. Looking at some # time zone maps, I think that must refer to Western Borneo (Kalimantan Barat # and Kalimantan Tengah) switching from UTC+8 to UTC+7. # # From Paul Eggert (2007-03-10): # Here is another correction to Shanks & Pottenger. # JohnTWB writes that Japanese forces did not surrender control in # Indonesia until 1945-09-01 00:00 at the earliest (in Jakarta) and # other formal surrender ceremonies were September 9, 11, and 13, plus # September 12 for the regional surrender to Mountbatten in Singapore. # These would be the earliest possible times for a change. # Régimes horaires pour le monde entier, by Henri Le Corre, (Éditions # Traditionnelles, 1987, Paris) says that Java and Madura switched # from UT +09 to +07:30 on 1945-09-23, and gives 1944-09-01 for Jayapura # (Hollandia). For now, assume all Indonesian locations other than Jayapura # switched on 1945-09-23. # # From Paul Eggert (2013-08-11): # Normally the tz database uses English-language abbreviations, but in # Indonesia it's typical to use Indonesian-language abbreviations even # when writing in English. For example, see the English-language # summary published by the Time and Frequency Laboratory of the # Research Center for Calibration, Instrumentation and Metrology, # Indonesia, (2006-09-29). # The time zone abbreviations and UT offsets are: # # WIB - +07 - Waktu Indonesia Barat (Indonesia western time) # WITA - +08 - Waktu Indonesia Tengah (Indonesia central time) # WIT - +09 - Waktu Indonesia Timur (Indonesia eastern time) # # Zone NAME STDOFF RULES FORMAT [UNTIL] # Java, Sumatra #STDOFF 7:07:12.5 Zone Asia/Jakarta 7:07:12 - LMT 1867 Aug 10 # Shanks & Pottenger say the next transition was at 1924 Jan 1 0:13, # but this must be a typo. 7:07:12 - BMT 1923 Dec 31 16:40u # Batavia 7:20 - %z 1932 Nov 7:30 - %z 1942 Mar 23 9:00 - %z 1945 Sep 23 7:30 - %z 1948 May 8:00 - %z 1950 May 7:30 - %z 1964 7:00 - WIB # west and central Borneo Zone Asia/Pontianak 7:17:20 - LMT 1908 May 7:17:20 - PMT 1932 Nov # Pontianak MT 7:30 - %z 1942 Jan 29 9:00 - %z 1945 Sep 23 7:30 - %z 1948 May 8:00 - %z 1950 May 7:30 - %z 1964 8:00 - WITA 1988 Jan 1 7:00 - WIB # Sulawesi, Lesser Sundas, east and south Borneo Zone Asia/Makassar 7:57:36 - LMT 1920 7:57:36 - MMT 1932 Nov # Macassar MT 8:00 - %z 1942 Feb 9 9:00 - %z 1945 Sep 23 8:00 - WITA # Maluku Islands, West Papua, Papua Zone Asia/Jayapura 9:22:48 - LMT 1932 Nov 9:00 - %z 1944 Sep 1 9:30 - %z 1964 9:00 - WIT # Iran # From Roozbeh Pournader (2022-05-30): # Here's an order from the Cabinet to the rest of the government to switch to # Tehran time, which is mentioned to be already at +03:30: # https://qavanin.ir/Law/TreeText/180138 # Just in case that goes away, I also saved a copy at archive.org: # https://web.archive.org/web/20220530111940/https://qavanin.ir/Law/TreeText/180138 # Here's my translation: # # "Circular on Matching the Hours of Governmental and Official Circles # in Provinces # Approved 1314/03/22 [=1935-06-13] # According to the ruling of the Honorable Cabinet, it is ordered that from # now on in all internal provinces of the country, governmental and official # circles set their time to match Tehran time (three hours and half before # Greenwich).... # # I still haven't found out when Tehran itself switched to +03:30.... # # From Paul Eggert (2022-06-05): # Although the above says Tehran was at +03:30 before 1935-06-13, we don't # know when it switched to +03:30. For now, use 1935-06-13 as the switch date. # Although most likely wrong, we have no better info. # From Roozbeh Pournader (2022-06-01): # This is from Kayhan newspaper, one of the major Iranian newspapers, from # March 20, 1978, page 2: # # "Pull the clocks 60 minutes forward # As we informed before, from the fourth day of the month Farvardin of the # new year [=1978-03-24], clocks will be pulled forward, and people's daily # work and life program will start one hour earlier than the current program. # On the 1st day of the month Farvardin of this year [=1977-03-21], they had # pulled the clocks forward by one hour, but in the month of Mehr # [=1977-09-23], the clocks were pulled back by 30 minutes. # In this way, from the 4th day of the month Farvardin, clocks will be ahead # of the previous years by one hour and a half. # According to the new program, during the night of 4th of Farvardin, when # the midnight, meaning 24 o'clock is announced, the hands of the clock must # be pulled forward by one hour and thus consider midnight 1 o'clock in the # forenoon." # # This implies that in September 1977, when the daylight savings time was # done with, Iran didn't go back to +03:30, but immediately to +04:00. # # # This is from the major Iranian newspaper Ettela'at, dated [1978-08-03]..., # page 32. It looks like they decided to get the clocks back to +4:00 # just in time for Ramadan that year: # # "Tomorrow Night, Pull the Clocks Back by One Hour # At 1 o'clock in the forenoon of Saturday 14 Mordad [=1978-08-05], the # clocks will be pulled one hour back and instead of 1 o'clock in the # forenoon, Radio Iran will announce 24 o'clock. # This decision was made in the Cabinet of Ministers meeting of 25 Tir # [=1978-07-16], [...] # At the beginning of the year 2537 [=March 1978: Iran was using a different # year number for a few years then, based on the Coronation of Cyrus the # Great], the country's official time was pulled forward by one hour and now # the official time is one hour and a half ahead compared to last year, # because in Farvardin of last year [=March 1977], the official time was # pulled forward one hour and this continued until the second half of last # year [=September 1977] until in the second half of last year the official # time was pulled back half an hour and that half hour still remains." # # This matches the time of the true noon published in the newspapers, as they # clearly go from +05:00 to +04:00 after that date (which happened during a # long weekend in Iran). # From Roozbeh Pournader (2022-05-31): # [Movahedi S. Cultural preconceptions of time: Can we use operational time # to meddle in God's Time? Comp Stud Soc Hist. 1985;27(3):385-400] # https://www.jstor.org/stable/178704 # Here's the quotes from the paper: # 1. '"Iran's official time keeper moved the clock one hour forward as from # March 22, 1977 (Farvardin 2, 2536) to make maximum use of daylight and save # in energy consumption. Thus Iran joined such other countries as Britain in # observing what is known as 'daylight saving.' The proposal was originally # put forward by the Ministry of Energy, in no way having any influence on # observing religious ceremonies. Moving time one hour forward in summer # means that at 11:00 o'clock on March 21, the official time was set as # midnight March 22. Then September 24 will actually begin one hour later # than the end of September 23 [...]." Iran's time base thus continued to be # Greenwich Mean Time plus three and one-half hours (plus four and one-half # hours in summer).' # # The article sources this from Iran Almanac and Book of Facts, 1977, Tehran: # Echo of Iran, which is on Google Books at # https://www.google.com/books/edition/Iran_Almanac_and_Book_of_Facts/9ybVAAAAMAAJ. # (I confirmed it by searching for snippets.) # # 2. "After the fall of the shah, the revolutionary government returned to # daylight-saving time (DST) on 26 May 1979." # # This seems to have been announced just one day in advance, on 25 May 1979. # # The change in 1977 clearly seems to be the first daylight savings effort in # Iran. But the article doesn't mention what happened in 1978 (which was # still during the shah's government), or how things continued in 1979 # onwards (which was during the Islamic Republic). # From Francis Santoni (2022-06-01): # for Iran and 1977 the effective change is only 20 October # (UIT No. 143 17.XI.1977) and not 23 September (UIT No. 141 13.IX.1977). # UIT is the Operational Bulletin of International Telecommunication Union. # From Roozbeh Pournader (2025-03-18): # ... the exact time of Iran's transition from +0400 to +0330 ... was Friday # 1357/8/19 AP=1978-11-10. Here's a newspaper clip from the Ettela'at # newspaper, dated 1357/8/14 AP=1978-11-05, translated from Persian # (at https://w.wiki/DUEY): # Following the government's decision about returning the official time # to the previous status, the spokesperson for the Ministry of Energy # announced today: At the hour 24 of Friday 19th of Aban (=1978-11-10), # the country's time will be pulled back half an hour. # # From Roozbeh Pournader (2003-03-15): # This is an English translation of what I just found (originally in Persian). # The Gregorian dates in brackets are mine: # # Official Newspaper No. 13548-1370/6/25 [1991-09-16] # No. 16760/T233 H 1370/6/10 [1991-09-01] # # The Rule About Change of the Official Time of the Country # # The Board of Ministers, in the meeting dated 1370/5/23 [1991-08-14], # based on the suggestion number 2221/D dated 1370/4/22 [1991-07-13] # of the Country's Organization for Official and Employment Affairs, # and referring to the law for equating the working hours of workers # and officers in the whole country dated 1359/4/23 [1980-07-14], and # for synchronizing the official times of the country, agreed that: # # The official time of the country will should move forward one hour # at the 24[:00] hours of the first day of Farvardin and should return # to its previous state at the 24[:00] hours of the 30th day of # Shahrivar. # # First Deputy to the President - Hassan Habibi # # From personal experience, that agrees with what has been followed # for at least the last 5 years. Before that, for a few years, the # date used was the first Thursday night of Farvardin and the last # Thursday night of Shahrivar, but I can't give exact dates.... # # From Roozbeh Pournader (2005-04-05): # The text of the Iranian law, in effect since 1925, clearly mentions # that the true solar year is the measure, and there is no arithmetic # leap year calculation involved. There has never been any serious # plan to change that law.... # # From Paul Eggert (2022-06-30): # Go with Pournader for 1935 through spring 1979, and for timestamps # after August 1991; go with with Shanks & Pottenger for other timestamps. # Go with Santoni's citation of the UIT for fall 1977, as 20 October 1977 # is 28 Mehr 1356, consistent with the "Mehr" in Pournader's source. # Assume that the UIT's "1930" is UTC, i.e., 24:00 local time. # # From Oscar van Vlijmen (2005-03-30), writing about future # discrepancies between cal-persia and the Iranian calendar: # For 2091 solar-longitude-after yields 2091-03-20 08:40:07.7 UT for # the vernal equinox and that gets so close to 12:00 some local # Iranian time that the definition of the correct location needs to be # known exactly, amongst other factors. 2157 is even closer: # 2157-03-20 08:37:15.5 UT. But the Gregorian year 2025 should give # no interpretation problem whatsoever. By the way, another instant # in the near future where there will be a discrepancy between # arithmetical and astronomical Iranian calendars will be in 2058: # vernal equinox on 2058-03-20 09:03:05.9 UT. The Java version of # Reingold's/Dershowitz' calculator gives correctly the Gregorian date # 2058-03-21 for 1 Farvardin 1437 (astronomical). # # From Steffen Thorsen (2006-03-22): # Several of my users have reported that Iran will not observe DST anymore: # http://www.irna.ir/en/news/view/line-17/0603193812164948.htm # # From Reuters (2007-09-16), with a heads-up from Jesper Nørgaard Welen: # ... the Guardian Council ... approved a law on Sunday to re-introduce # daylight saving time ... # https://uk.reuters.com/article/oilRpt/idUKBLA65048420070916 # # From Roozbeh Pournader (2007-11-05): # This is quoted from Official Gazette of the Islamic Republic of # Iran, Volume 63, No. 18242, dated Tuesday 1386/6/24 # [2007-10-16]. I am doing the best translation I can:... # The official time of the country will be moved forward for one hour # on the 24 hours of the first day of the month of Farvardin and will # be changed back to its previous state on the 24 hours of the # thirtieth day of Shahrivar. # # From Ali Mirjamali (2022-05-10): # Official IR News Agency announcement: irna.ir/xjJ3TT # ... # Highlights: DST will be cancelled for the next Iranian year 1402 # (i.e 2023-March-21) and forthcoming years. # # Rule NAME FROM TO - IN ON AT SAVE LETTER/S # Work around a bug in zic 2022a and earlier. Rule Iran 1910 only - Jan 1 00:00 0 - # Rule Iran 1977 only - Mar 21 23:00 1:00 - Rule Iran 1977 only - Oct 20 24:00 0 - Rule Iran 1978 only - Mar 24 24:00 1:00 - Rule Iran 1978 only - Aug 5 01:00 0 - Rule Iran 1979 only - May 26 24:00 1:00 - Rule Iran 1979 only - Sep 18 24:00 0 - Rule Iran 1980 only - Mar 20 24:00 1:00 - Rule Iran 1980 only - Sep 22 24:00 0 - Rule Iran 1991 only - May 2 24:00 1:00 - Rule Iran 1992 1995 - Mar 21 24:00 1:00 - Rule Iran 1991 1995 - Sep 21 24:00 0 - Rule Iran 1996 only - Mar 20 24:00 1:00 - Rule Iran 1996 only - Sep 20 24:00 0 - Rule Iran 1997 1999 - Mar 21 24:00 1:00 - Rule Iran 1997 1999 - Sep 21 24:00 0 - Rule Iran 2000 only - Mar 20 24:00 1:00 - Rule Iran 2000 only - Sep 20 24:00 0 - Rule Iran 2001 2003 - Mar 21 24:00 1:00 - Rule Iran 2001 2003 - Sep 21 24:00 0 - Rule Iran 2004 only - Mar 20 24:00 1:00 - Rule Iran 2004 only - Sep 20 24:00 0 - Rule Iran 2005 only - Mar 21 24:00 1:00 - Rule Iran 2005 only - Sep 21 24:00 0 - Rule Iran 2008 only - Mar 20 24:00 1:00 - Rule Iran 2008 only - Sep 20 24:00 0 - Rule Iran 2009 2011 - Mar 21 24:00 1:00 - Rule Iran 2009 2011 - Sep 21 24:00 0 - Rule Iran 2012 only - Mar 20 24:00 1:00 - Rule Iran 2012 only - Sep 20 24:00 0 - Rule Iran 2013 2015 - Mar 21 24:00 1:00 - Rule Iran 2013 2015 - Sep 21 24:00 0 - Rule Iran 2016 only - Mar 20 24:00 1:00 - Rule Iran 2016 only - Sep 20 24:00 0 - Rule Iran 2017 2019 - Mar 21 24:00 1:00 - Rule Iran 2017 2019 - Sep 21 24:00 0 - Rule Iran 2020 only - Mar 20 24:00 1:00 - Rule Iran 2020 only - Sep 20 24:00 0 - Rule Iran 2021 2022 - Mar 21 24:00 1:00 - Rule Iran 2021 2022 - Sep 21 24:00 0 - # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone Asia/Tehran 3:25:44 - LMT 1916 3:25:44 - TMT 1935 Jun 13 # Tehran Mean Time 3:30 Iran %z 1977 Oct 20 24:00 4:00 Iran %z 1978 Nov 10 24:00 3:30 Iran %z # Iraq # # From Jonathan Lennox (2000-06-12): # An article in this week's Economist ("Inside the Saddam-free zone", p. 50 in # the U.S. edition) on the Iraqi Kurds contains a paragraph: # "The three northern provinces ... switched their clocks this spring and # are an hour ahead of Baghdad." # # But Rives McDow (2000-06-18) quotes a contact in Iraqi-Kurdistan as follows: # In the past, some Kurdish nationalists, as a protest to the Iraqi # Government, did not adhere to daylight saving time. They referred # to daylight saving as Saddam time. But, as of today, the time zone # in Iraqi-Kurdistan is on standard time with Baghdad, Iraq. # # So we'll ignore the Economist's claim. # From Steffen Thorsen (2008-03-10): # The cabinet in Iraq abolished DST last week, according to the following # news sources (in Arabic): # http://www.aljeeran.net/wesima_articles/news-20080305-98602.html # http://www.aswataliraq.info/look/article.tpl?id=2047&IdLanguage=17&IdPublication=4&NrArticle=71743&NrIssue=1&NrSection=10 # # We have published a short article in English about the change: # https://www.timeanddate.com/news/time/iraq-dumps-daylight-saving.html # Rule NAME FROM TO - IN ON AT SAVE LETTER/S Rule Iraq 1982 only - May 1 0:00 1:00 - Rule Iraq 1982 1984 - Oct 1 0:00 0 - Rule Iraq 1983 only - Mar 31 0:00 1:00 - Rule Iraq 1984 1985 - Apr 1 0:00 1:00 - Rule Iraq 1985 1990 - Sep lastSun 1:00s 0 - Rule Iraq 1986 1990 - Mar lastSun 1:00s 1:00 - # IATA SSIM (1991/1996) says Apr 1 12:01am UTC; guess the ':01' is a typo. # Shanks & Pottenger say Iraq did not observe DST 1992/1997; ignore this. # Rule Iraq 1991 2007 - Apr 1 3:00s 1:00 - Rule Iraq 1991 2007 - Oct 1 3:00s 0 - # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone Asia/Baghdad 2:57:40 - LMT 1890 2:57:36 - BMT 1918 # Baghdad Mean Time? 3:00 - %z 1982 May 3:00 Iraq %z ############################################################################### # Israel # For more info about the motivation for DST in Israel, see: # Barak Y. Israel's Daylight Saving Time controversy. Israel Affairs. # 2020-08-11. https://doi.org/10.1080/13537121.2020.1806564 # From Ephraim Silverberg (2001-01-11): # # I coined "IST/IDT" circa 1988. Until then there were three # different abbreviations in use: # # JST Jerusalem Standard Time [Danny Braniss, Hebrew University] # IZT Israel Zonal (sic) Time [Prof. Haim Papo, Technion] # EEST Eastern Europe Standard Time [used by almost everyone else] # # Since timezones should be called by country and not capital cities, # I ruled out JST. As Israel is in Asia Minor and not Eastern Europe, # EEST was equally unacceptable. Since "zonal" was not compatible with # any other timezone abbreviation, I felt that 'IST' was the way to go # and, indeed, it has received almost universal acceptance in timezone # settings in Israeli computers. # # In any case, I am happy to share timezone abbreviations with India, # high on my favorite-country list (and not only because my wife's # family is from India). # From P Chan (2020-10-27), with corrections: # # 1940-1946 Supplement No. 2 to the Palestine Gazette # # issue page Order No. dated start end note # 1 1010 729 67 of 1940 1940-05-22 1940-05-31* 1940-09-30* revoked by #2 # 2 1013 758 73 of 1940 1940-05-31 1940-05-31 1940-09-30 # 3 1055 1574 196 of 1940 1940-11-06 1940-11-16 1940-12-31 # 4 1066 1811 208 of 1940 1940-12-17 1940-12-31 1941-12-31 # 5 1156 1967 116 of 1941 1941-12-16 1941-12-31 1942-12-31* amended by #6 # 6 1228 1608 86 of 1942 1942-10-14 1941-12-31 1942-10-31 # 7 1256 279 21 of 1943 1943-03-18 1943-03-31 1943-10-31 # 8 1323 249 19 of 1944 1944-03-13 1944-03-31 1944-10-31 # 9 1402 328 20 of 1945 1945-04-05 1945-04-15 1945-10-31 #10 1487 596 14 of 1946 1946-04-04 1946-04-15 1946-10-31 # # 1948 Iton Rishmi (Official Gazette of the Provisional Government) # # issue page dated start end #11 2 7 1948-05-20 1948-05-22 1948-10-31* # ^This moved timezone to +04, replaced by #12 from 1948-08-31 24:00 GMT. #12 17 (Annex B) 84 1948-08-22 1948-08-31 1948-10-31 # # 1949-2000 Kovetz HaTakanot (Collection of Regulations) # # issue page dated start end note #13 6 133 1949-03-23 1949-04-30 1949-10-31 #14 80 755 1950-03-17 1950-04-15 1950-09-14 #15 164 782 1951-03-22 1951-03-31 1951-09-29* amended by #16 #16 206 1940 1951-09-23 ---------- 1951-10-22* amended by #17 #17 212 78 1951-10-19 ---------- 1951-11-10 #18 254 652 1952-03-03 1952-04-19 1952-09-27* amended by #19 #19 300 11 1952-09-15 ---------- 1952-10-18 #20 348 817 1953-03-03 1953-04-11 1953-09-12 #21 420 385 1954-02-17 1954-06-12 1954-09-11 #22 497 548 1955-01-14 1955-06-11 1955-09-10 #23 591 608 1956-03-12 1956-06-02 1956-09-29 #24 680 957 1957-02-08 1957-04-27 1957-09-21 #25 3192 1418 1974-06-28 1974-07-06 1974-10-12 #26 3322 1389 1975-04-03 1975-04-19 1975-08-30 #27 4146 2089 1980-07-15 1980-08-02 1980-09-13 #28 4604 1081 1984-02-22 1984-05-05* 1984-08-25* revoked by #29 #29 4619 1312 1984-04-06 1984-05-05 1984-08-25 #30 4744 475 1984-12-23 1985-04-13 1985-09-14* amended by #31 #31 4851 1848 1985-08-18 ---------- 1985-08-31 #32 4932 899 1986-04-22 1986-05-17 1986-09-06 #33 5013 580 1987-02-15 1987-04-18* 1987-08-22* revoked by #34 #34 5021 744 1987-03-30 1987-04-14 1987-09-12 #35 5096 659 1988-02-14 1988-04-09 1988-09-03 #36 5167 514 1989-02-03 1989-04-29 1989-09-02 #37 5248 375 1990-01-23 1990-03-24 1990-08-25 #38 5335 612 1991-02-10 1991-03-09* 1991-08-31 amended by #39 # 1992-03-28 1992-09-05 #39 5339 709 1991-03-04 1991-03-23 ---------- #40 5506 503 1993-02-18 1993-04-02 1993-09-05 # 1994-04-01 1994-08-28 # 1995-03-31 1995-09-03 #41 5731 438 1996-01-01 1996-03-14 1996-09-15 # 1997-03-13* 1997-09-18* overridden by 1997 Temp Prov # 1998-03-19* 1998-09-17* revoked by #42 #42 5853 1243 1997-09-18 1998-03-19 1998-09-05 #43 5937 77 1998-10-18 1999-04-02 1999-09-03 # 2000-04-14* 2000-09-15* revoked by #44 # 2001-04-13* 2001-09-14* revoked by #44 #44 6024 39 2000-03-14 2000-04-14 2000-10-22* overridden by 2000 Temp Prov # 2001-04-06* 2001-10-10* overridden by 2000 Temp Prov # 2002-03-29* 2002-10-29* overridden by 2000 Temp Prov # # These are laws enacted by the Knesset since the Minister could only alter the # transition dates at least six months in advanced under the 1992 Law. # dated start end # 1997 Temporary Provisions 1997-03-06 1997-03-20 1997-09-13 # 2000 Temporary Provisions 2000-07-28 ---------- 2000-10-06 # 2001-04-09 2001-09-24 # 2002-03-29 2002-10-07 # 2003-03-28 2003-10-03 # 2004-04-07 2004-09-22 # Note: # Transition times in 1940-1957 (#1-#24) were midnight GMT, # in 1974-1998 (#25-#42 and the 1997 Temporary Provisions) were midnight, # in 1999-April 2000 (#43,#44) were 02:00, # in the 2000 Temporary Provisions were 01:00. # # ----------------------------------------------------------------------------- # Links: # 1 https://findit.library.yale.edu/images_layout/view?parentoid=15537490&increment=687 # 2 https://findit.library.yale.edu/images_layout/view?parentoid=15537490&increment=716 # 3 https://findit.library.yale.edu/images_layout/view?parentoid=15537491&increment=721 # 4 https://findit.library.yale.edu/images_layout/view?parentoid=15537491&increment=958 # 5 https://findit.library.yale.edu/images_layout/view?parentoid=15537502&increment=558 # 6 https://findit.library.yale.edu/images_layout/view?parentoid=15537511&increment=105 # 7 https://findit.library.yale.edu/images_layout/view?parentoid=15537516&increment=278 # 8 https://findit.library.yale.edu/images_layout/view?parentoid=15537522&increment=248 # 9 https://findit.library.yale.edu/images_layout/view?parentoid=15537530&increment=329 #10 https://findit.library.yale.edu/images_layout/view?parentoid=15537537&increment=601 #11 https://www.nevo.co.il/law_word/law12/er-002.pdf#page=3 #12 https://www.nevo.co.il/law_word/law12/er-017-t2.pdf#page=4 #13 https://www.nevo.co.il/law_word/law06/tak-0006.pdf#page=3 #14 https://www.nevo.co.il/law_word/law06/tak-0080.pdf#page=7 #15 https://www.nevo.co.il/law_word/law06/tak-0164.pdf#page=10 #16 https://www.nevo.co.il/law_word/law06/tak-0206.pdf#page=4 #17 https://www.nevo.co.il/law_word/law06/tak-0212.pdf#page=2 #18 https://www.nevo.co.il/law_word/law06/tak-0254.pdf#page=4 #19 https://www.nevo.co.il/law_word/law06/tak-0300.pdf#page=5 #20 https://www.nevo.co.il/law_word/law06/tak-0348.pdf#page=3 #21 https://www.nevo.co.il/law_word/law06/tak-0420.pdf#page=5 #22 https://www.nevo.co.il/law_word/law06/tak-0497.pdf#page=10 #23 https://www.nevo.co.il/law_word/law06/tak-0591.pdf#page=6 #24 https://www.nevo.co.il/law_word/law06/tak-0680.pdf#page=3 #25 https://www.nevo.co.il/law_word/law06/tak-3192.pdf#page=2 #26 https://www.nevo.co.il/law_word/law06/tak-3322.pdf#page=5 #27 https://www.nevo.co.il/law_word/law06/tak-4146.pdf#page=2 #28 https://www.nevo.co.il/law_word/law06/tak-4604.pdf#page=7 #29 https://www.nevo.co.il/law_word/law06/tak-4619.pdf#page=2 #30 https://www.nevo.co.il/law_word/law06/tak-4744.pdf#page=11 #31 https://www.nevo.co.il/law_word/law06/tak-4851.pdf#page=2 #32 https://www.nevo.co.il/law_word/law06/tak-4932.pdf#page=19 #33 https://www.nevo.co.il/law_word/law06/tak-5013.pdf#page=8 #34 https://www.nevo.co.il/law_word/law06/tak-5021.pdf#page=8 #35 https://www.nevo.co.il/law_word/law06/tak-5096.pdf#page=3 #36 https://www.nevo.co.il/law_word/law06/tak-5167.pdf#page=2 #37 https://www.nevo.co.il/law_word/law06/tak-5248.pdf#page=7 #38 https://www.nevo.co.il/law_word/law06/tak-5335.pdf#page=6 #39 https://www.nevo.co.il/law_word/law06/tak-5339.pdf#page=7 #40 https://www.nevo.co.il/law_word/law06/tak-5506.pdf#page=19 #41 https://www.nevo.co.il/law_word/law06/tak-5731.pdf#page=2 #42 https://www.nevo.co.il/law_word/law06/tak-5853.pdf#page=3 #43 https://www.nevo.co.il/law_word/law06/tak-5937.pdf#page=9 #44 https://www.nevo.co.il/law_word/law06/tak-6024.pdf#page=4 # # Time Determination (Temporary Provisions) Law, 1997 # https://www.nevo.co.il/law_html/law19/p201_003.htm # # Time Determination (Temporary Provisions) Law, 2000 # https://www.nevo.co.il/law_html/law19/p201_004.htm # # Time Determination Law, 1992 and amendments # https://www.nevo.co.il/law_html/law01/p201_002.htm # https://main.knesset.gov.il/Activity/Legislation/Laws/Pages/LawPrimary.aspx?lawitemid=2001174 # From Paul Eggert (2020-10-27): # Several of the midnight transitions mentioned above are ambiguous; # are they 00:00, 00:00s, 24:00, or 24:00s? When resolving these ambiguities, # try to minimize changes from previous tzdb versions, for lack of better info. # Commentary from previous versions is included below, to help explain this. # Rule NAME FROM TO - IN ON AT SAVE LETTER/S Rule Zion 1940 only - May 31 24:00u 1:00 D Rule Zion 1940 only - Sep 30 24:00u 0 S Rule Zion 1940 only - Nov 16 24:00u 1:00 D Rule Zion 1942 1946 - Oct 31 24:00u 0 S Rule Zion 1943 1944 - Mar 31 24:00u 1:00 D Rule Zion 1945 1946 - Apr 15 24:00u 1:00 D Rule Zion 1948 only - May 22 24:00u 2:00 DD Rule Zion 1948 only - Aug 31 24:00u 1:00 D Rule Zion 1948 1949 - Oct 31 24:00u 0 S Rule Zion 1949 only - Apr 30 24:00u 1:00 D Rule Zion 1950 only - Apr 15 24:00u 1:00 D Rule Zion 1950 only - Sep 14 24:00u 0 S Rule Zion 1951 only - Mar 31 24:00u 1:00 D Rule Zion 1951 only - Nov 10 24:00u 0 S Rule Zion 1952 only - Apr 19 24:00u 1:00 D Rule Zion 1952 only - Oct 18 24:00u 0 S Rule Zion 1953 only - Apr 11 24:00u 1:00 D Rule Zion 1953 only - Sep 12 24:00u 0 S Rule Zion 1954 only - Jun 12 24:00u 1:00 D Rule Zion 1954 only - Sep 11 24:00u 0 S Rule Zion 1955 only - Jun 11 24:00u 1:00 D Rule Zion 1955 only - Sep 10 24:00u 0 S Rule Zion 1956 only - Jun 2 24:00u 1:00 D Rule Zion 1956 only - Sep 29 24:00u 0 S Rule Zion 1957 only - Apr 27 24:00u 1:00 D Rule Zion 1957 only - Sep 21 24:00u 0 S Rule Zion 1974 only - Jul 6 24:00 1:00 D Rule Zion 1974 only - Oct 12 24:00 0 S Rule Zion 1975 only - Apr 19 24:00 1:00 D Rule Zion 1975 only - Aug 30 24:00 0 S # From Alois Treindl (2019-03-06): # http://www.moin.gov.il/Documents/שעון%20קיץ/clock-50-years-7-2014.pdf # From Isaac Starkman (2019-03-06): # Summer time was in that period in 1980 and 1984, see # https://www.ynet.co.il/articles/0,7340,L-3951073,00.html # You can of course read it in translation. # I checked the local newspapers for that years. # It started on midnight and end at 01.00 am. # From Paul Eggert (2019-03-06): # Also see this thread about the moin.gov.il URL: # https://mm.icann.org/pipermail/tz/2018-November/027194.html Rule Zion 1980 only - Aug 2 24:00s 1:00 D Rule Zion 1980 only - Sep 13 24:00s 0 S Rule Zion 1984 only - May 5 24:00s 1:00 D Rule Zion 1984 only - Aug 25 24:00s 0 S Rule Zion 1985 only - Apr 13 24:00 1:00 D Rule Zion 1985 only - Aug 31 24:00 0 S Rule Zion 1986 only - May 17 24:00 1:00 D Rule Zion 1986 only - Sep 6 24:00 0 S Rule Zion 1987 only - Apr 14 24:00 1:00 D Rule Zion 1987 only - Sep 12 24:00 0 S # From Avigdor Finkelstein (2014-03-05): # I check the Parliament (Knesset) records and there it's stated that the # [1988] transition should take place on Saturday night, when the Sabbath # ends and changes to Sunday. Rule Zion 1988 only - Apr 9 24:00 1:00 D Rule Zion 1988 only - Sep 3 24:00 0 S # From Ephraim Silverberg # (1997-03-04, 1998-03-16, 1998-12-28, 2000-01-17, 2000-07-25, 2004-12-22, # and 2005-02-17): # According to the Office of the Secretary General of the Ministry of # Interior, there is NO set rule for Daylight-Savings/Standard time changes. # One thing is entrenched in law, however: that there must be at least 150 # days of daylight savings time annually. From 1993-1998, the change to # daylight savings time was on a Friday morning from midnight IST to # 1 a.m IDT; up until 1998, the change back to standard time was on a # Saturday night from midnight daylight savings time to 11 p.m. standard # time. 1996 is an exception to this rule where the change back to standard # time took place on Sunday night instead of Saturday night to avoid # conflicts with the Jewish New Year. In 1999, the change to # daylight savings time was still on a Friday morning but from # 2 a.m. IST to 3 a.m. IDT; furthermore, the change back to standard time # was also on a Friday morning from 2 a.m. IDT to 1 a.m. IST for # 1999 only. In the year 2000, the change to daylight savings time was # similar to 1999, but although the change back will be on a Friday, it # will take place from 1 a.m. IDT to midnight IST. Starting in 2001, all # changes to/from will take place at 1 a.m. old time, but now there is no # rule as to what day of the week it will take place in as the start date # (except in 2003) is the night after the Passover Seder (i.e. the eve # of the 16th of Nisan in the lunar Hebrew calendar) and the end date # (except in 2002) is three nights before Yom Kippur [Day of Atonement] # (the eve of the 7th of Tishrei in the lunar Hebrew calendar). # Rule NAME FROM TO - IN ON AT SAVE LETTER/S Rule Zion 1989 only - Apr 29 24:00 1:00 D Rule Zion 1989 only - Sep 2 24:00 0 S Rule Zion 1990 only - Mar 24 24:00 1:00 D Rule Zion 1990 only - Aug 25 24:00 0 S Rule Zion 1991 only - Mar 23 24:00 1:00 D Rule Zion 1991 only - Aug 31 24:00 0 S Rule Zion 1992 only - Mar 28 24:00 1:00 D Rule Zion 1992 only - Sep 5 24:00 0 S Rule Zion 1993 only - Apr 2 0:00 1:00 D Rule Zion 1993 only - Sep 5 0:00 0 S # The dates for 1994-1995 were obtained from Office of the Spokeswoman for the # Ministry of Interior, Jerusalem, Israel. The spokeswoman can be reached by # calling the office directly at 972-2-6701447 or 972-2-6701448. # Rule NAME FROM TO - IN ON AT SAVE LETTER/S Rule Zion 1994 only - Apr 1 0:00 1:00 D Rule Zion 1994 only - Aug 28 0:00 0 S Rule Zion 1995 only - Mar 31 0:00 1:00 D Rule Zion 1995 only - Sep 3 0:00 0 S # The dates for 1996 were determined by the Minister of Interior of the # time, Haim Ramon. The official announcement regarding 1996-1998 # (with the dates for 1997-1998 no longer being relevant) can be viewed at: # # ftp://ftp.cs.huji.ac.il/pub/tz/announcements/1996-1998.ramon.ps.gz # # The dates for 1997-1998 were altered by his successor, Rabbi Eli Suissa. # # The official announcements for the years 1997-1999 can be viewed at: # # ftp://ftp.cs.huji.ac.il/pub/tz/announcements/YYYY.ps.gz # # where YYYY is the relevant year. # Rule NAME FROM TO - IN ON AT SAVE LETTER/S Rule Zion 1996 only - Mar 14 24:00 1:00 D Rule Zion 1996 only - Sep 15 24:00 0 S Rule Zion 1997 only - Mar 20 24:00 1:00 D Rule Zion 1997 only - Sep 13 24:00 0 S Rule Zion 1998 only - Mar 20 0:00 1:00 D Rule Zion 1998 only - Sep 6 0:00 0 S Rule Zion 1999 only - Apr 2 2:00 1:00 D Rule Zion 1999 only - Sep 3 2:00 0 S # The Knesset Interior Committee has changed the dates for 2000 for # the third time in just over a year and have set new dates for the # years 2001-2004 as well. # # The official announcement for the start date of 2000 can be viewed at: # # ftp://ftp.cs.huji.ac.il/pub/tz/announcements/2000-start.ps.gz # # The official announcement for the end date of 2000 and the dates # for the years 2001-2004 can be viewed at: # # ftp://ftp.cs.huji.ac.il/pub/tz/announcements/2000-2004.ps.gz # Rule NAME FROM TO - IN ON AT SAVE LETTER/S Rule Zion 2000 only - Apr 14 2:00 1:00 D Rule Zion 2000 only - Oct 6 1:00 0 S Rule Zion 2001 only - Apr 9 1:00 1:00 D Rule Zion 2001 only - Sep 24 1:00 0 S Rule Zion 2002 only - Mar 29 1:00 1:00 D Rule Zion 2002 only - Oct 7 1:00 0 S Rule Zion 2003 only - Mar 28 1:00 1:00 D Rule Zion 2003 only - Oct 3 1:00 0 S Rule Zion 2004 only - Apr 7 1:00 1:00 D Rule Zion 2004 only - Sep 22 1:00 0 S # The proposed law agreed upon by the Knesset Interior Committee on # 2005-02-14 is that, for 2005 and beyond, DST starts at 02:00 the # last Friday before April 2nd (i.e. the last Friday in March or April # 1st itself if it falls on a Friday) and ends at 02:00 on the Saturday # night _before_ the fast of Yom Kippur. # # Those who can read Hebrew can view the announcement at: # # ftp://ftp.cs.huji.ac.il/pub/tz/announcements/2005+beyond.ps # Rule NAME FROM TO - IN ON AT SAVE LETTER/S Rule Zion 2005 2012 - Apr Fri<=1 2:00 1:00 D Rule Zion 2005 only - Oct 9 2:00 0 S Rule Zion 2006 only - Oct 1 2:00 0 S Rule Zion 2007 only - Sep 16 2:00 0 S Rule Zion 2008 only - Oct 5 2:00 0 S Rule Zion 2009 only - Sep 27 2:00 0 S Rule Zion 2010 only - Sep 12 2:00 0 S Rule Zion 2011 only - Oct 2 2:00 0 S Rule Zion 2012 only - Sep 23 2:00 0 S # From Ephraim Silverberg (2020-10-26): # The current time law (2013) from the State of Israel can be viewed # (in Hebrew) at: # ftp://ftp.cs.huji.ac.il/pub/tz/israel/announcements/2013+law.pdf # It translates to: # Every year, in the period from the Friday before the last Sunday in # the month of March at 02:00 a.m. until the last Sunday of the month # of October at 02:00 a.m., Israel Time will be advanced an additional # hour such that it will be UTC+3. # Rule NAME FROM TO - IN ON AT SAVE LETTER/S Rule Zion 2013 max - Mar Fri>=23 2:00 1:00 D Rule Zion 2013 max - Oct lastSun 2:00 0 S # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone Asia/Jerusalem 2:20:54 - LMT 1880 2:20:40 - JMT 1918 # Jerusalem Mean Time? 2:00 Zion I%sT ############################################################################### # Japan # '9:00' and 'JST' is from Guy Harris. # From Paul Eggert (2020-01-19): # Starting in the 7th century, Japan generally followed an ancient Chinese # timekeeping system that divided night and day into six hours each, # with hour length depending on season. In 1873 the government # started requiring the use of a Western style 24-hour clock. See: # Yulia Frumer, "Making Time: Astronomical Time Measurement in Tokugawa Japan" # . As the tzdb code and # data support only 24-hour clocks, its tables model timestamps before # 1873 using Western-style local mean time. # From Hideyuki Suzuki (1998-11-09): # 'Tokyo' usually stands for the former location of Tokyo Astronomical # Observatory: 139° 44' 40.90" E (9h 18m 58.727s), 35° 39' 16.0" N. # This data is from 'Rika Nenpyou (Chronological Scientific Tables) 1996' # edited by National Astronomical Observatory of Japan.... # JST (Japan Standard Time) has been used since 1888-01-01 00:00 (JST). # The law is enacted on 1886-07-07. # From Hideyuki Suzuki (1998-11-16): # The ordinance No. 51 (1886) established "standard time" in Japan, # which stands for the time on 135° E. # In the ordinance No. 167 (1895), "standard time" was renamed to "central # standard time". And the same ordinance also established "western standard # time", which stands for the time on 120° E.... But "western standard # time" was abolished in the ordinance No. 529 (1937). In the ordinance No. # 167, there is no mention regarding for what place western standard time is # standard.... # # I wrote "ordinance" above, but I don't know how to translate. # In Japanese it's "chokurei", which means ordinance from emperor. # From Yu-Cheng Chuang (2013-07-12): # ...the Meiji Emperor announced Ordinance No. 167 of Meiji Year 28 "The clause # about standard time" ... The adoption began from Jan 1, 1896. # https://ja.wikisource.org/wiki/標準時ニ關スル件_(公布時) # # ...the Showa Emperor announced Ordinance No. 529 of Showa Year 12 ... which # means the whole Japan territory, including later occupations, adopt Japan # Central Time (UT+9). The adoption began on Oct 1, 1937. # https://ja.wikisource.org/wiki/明治二十八年勅令第百六十七號標準時ニ關スル件中改正ノ件 # From Paul Eggert (1995-03-06): # Today's _Asahi Evening News_ (page 4) reports that Japan had # daylight saving between 1948 and 1951, but "the system was discontinued # because the public believed it would lead to longer working hours." # From Mayumi Negishi in the 2005-08-10 Japan Times: # http://www.japantimes.co.jp/cgi-bin/getarticle.pl5?nn20050810f2.htm # Occupation authorities imposed daylight-saving time on Japan on # [1948-05-01].... But lack of prior debate and the execution of # daylight-saving time just three days after the bill was passed generated # deep hatred of the concept.... The Diet unceremoniously passed a bill to # dump the unpopular system in October 1951, less than a month after the San # Francisco Peace Treaty was signed. (A government poll in 1951 showed 53% # of the Japanese wanted to scrap daylight-saving time, as opposed to 30% who # wanted to keep it.) # From Takayuki Nikai (2018-01-19): # The source of information is Japanese law. # http://www.shugiin.go.jp/internet/itdb_housei.nsf/html/houritsu/00219480428029.htm # http://www.shugiin.go.jp/internet/itdb_housei.nsf/html/houritsu/00719500331039.htm # ... In summary, it is written as follows. From 24:00 on the first Saturday # in May, until 0:00 on the day after the second Saturday in September. # From Phake Nick (2018-09-27): # [T]he webpage authored by National Astronomical Observatory of Japan # https://eco.mtk.nao.ac.jp/koyomi/wiki/BBFEB9EF2FB2C6BBFEB9EF.html # ... mentioned that using Showa 23 (year 1948) as example, 13pm of September # 11 in summer time will equal to 0am of September 12 in standard time. # It cited a document issued by the Liaison Office which briefly existed # during the postwar period of Japan, where the detail on implementation # of the summer time is described in the document. # https://eco.mtk.nao.ac.jp/koyomi/wiki/BBFEB9EF2FB2C6BBFEB9EFB2C6BBFEB9EFA4CEBCC2BBDCA4CBA4C4A4A4A4C6.pdf # The text in the document do instruct a fall back to occur at # September 11, 13pm in summer time, while ordinary citizens can # change the clock before they sleep. # # From Paul Eggert (2018-09-27): # This instruction is equivalent to "Sat>=8 25:00", so use that. zic treats # it like "Sun>=9 01:00", which is not quite the same but is the best we can # do in any POSIX or C platform. The "25:00" assumes zic from 2007 or later, # which should be safe now. # Rule NAME FROM TO - IN ON AT SAVE LETTER/S Rule Japan 1948 only - May Sat>=1 24:00 1:00 D Rule Japan 1948 1951 - Sep Sat>=8 25:00 0 S Rule Japan 1949 only - Apr Sat>=1 24:00 1:00 D Rule Japan 1950 1951 - May Sat>=1 24:00 1:00 D # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone Asia/Tokyo 9:18:59 - LMT 1887 Dec 31 15:00u 9:00 Japan J%sT # Since 1938, all Japanese possessions have been like Asia/Tokyo, # except that Truk (Chuuk), Ponape (Pohnpei), and Jaluit (Kosrae) did not # switch from +10 to +09 until 1941-04-01; see the 'australasia' file. # Jordan # # From # Jordan Week (1999-07-01) via Steffen Thorsen (1999-09-09): # Clocks in Jordan were forwarded one hour on Wednesday at midnight, # in accordance with the government's decision to implement summer time # all year round. # # From # Jordan Week (1999-09-30) via Steffen Thorsen (1999-11-09): # Winter time starts today Thursday, 30 September. Clocks will be turned back # by one hour. This is the latest government decision and it's final! # The decision was taken because of the increase in working hours in # government's departments from six to seven hours. # # From Paul Eggert (2005-11-22): # Starting 2003 transitions are from Steffen Thorsen's web site timeanddate.com. # # From Steffen Thorsen (2005-11-23): # For Jordan I have received multiple independent user reports every year # about DST end dates, as the end-rule is different every year. # # From Steffen Thorsen (2006-10-01), after a heads-up from Hilal Malawi: # http://www.petranews.gov.jo/nepras/2006/Sep/05/4000.htm # "Jordan will switch to winter time on Friday, October 27". # # From Steffen Thorsen (2009-04-02): # This single one might be good enough, (2009-03-24, Arabic): # http://petra.gov.jo/Artical.aspx?Lng=2&Section=8&Artical=95279 # # Google's translation: # # > The Council of Ministers decided in 2002 to adopt the principle of timely # > submission of the summer at 60 minutes as of midnight on the last Thursday # > of the month of March of each year. # # So - this means the midnight between Thursday and Friday since 2002. # From Arthur David Olson (2009-04-06): # We still have Jordan switching to DST on Thursdays in 2000 and 2001. # From Steffen Thorsen (2012-10-25): # Yesterday the government in Jordan announced that they will not # switch back to standard time this winter, so the will stay on DST # until about the same time next year (at least). # http://www.petra.gov.jo/Public_News/Nws_NewsDetails.aspx?NewsID=88950 # From Steffen Thorsen (2013-12-11): # Jordan Times and other sources say that Jordan is going back to # UTC+2 on 2013-12-19 at midnight: # http://jordantimes.com/govt-decides-to-switch-back-to-wintertime # Official, in Arabic: # http://www.petra.gov.jo/public_news/Nws_NewsDetails.aspx?Menu_ID=&Site_Id=2&lang=1&NewsID=133230&CatID=14 # ... Our background/permalink about it # https://www.timeanddate.com/news/time/jordan-reverses-dst-decision.html # ... # http://www.petra.gov.jo/Public_News/Nws_NewsDetails.aspx?lang=2&site_id=1&NewsID=133313&Type=P # ... says midnight for the coming one and 1:00 for the ones in the future # (and they will use DST again next year, using the normal schedule). # From Paul Eggert (2013-12-11): # As Steffen suggested, consider the past 21-month experiment to be DST. # From Steffen Thorsen (2021-09-24): # The Jordanian Government announced yesterday that they will start DST # in February instead of March: # https://petra.gov.jo/Include/InnerPage.jsp?ID=37683&lang=en&name=en_news (English) # https://petra.gov.jo/Include/InnerPage.jsp?ID=189969&lang=ar&name=news (Arabic) # From the Arabic version, it seems to say it would be at midnight # (assume 24:00) on the last Thursday in February, starting from 2022. # From Issam Al-Zuwairi (2022-10-05): # The Council of Ministers in Jordan decided Wednesday 5th October 2022, # that daylight saving time (DST) will be throughout the year.... # # From Brian Inglis (2022-10-06): # https://petra.gov.jo/Include/InnerPage.jsp?ID=45567&lang=en&name=en_news # # From Paul Eggert (2022-10-05): # Like Syria, model this as a transition from EEST +03 (DST) to plain +03 # (non-DST) at the point where DST would otherwise have ended. # Rule NAME FROM TO - IN ON AT SAVE LETTER/S Rule Jordan 1973 only - Jun 6 0:00 1:00 S Rule Jordan 1973 1975 - Oct 1 0:00 0 - Rule Jordan 1974 1977 - May 1 0:00 1:00 S Rule Jordan 1976 only - Nov 1 0:00 0 - Rule Jordan 1977 only - Oct 1 0:00 0 - Rule Jordan 1978 only - Apr 30 0:00 1:00 S Rule Jordan 1978 only - Sep 30 0:00 0 - Rule Jordan 1985 only - Apr 1 0:00 1:00 S Rule Jordan 1985 only - Oct 1 0:00 0 - Rule Jordan 1986 1988 - Apr Fri>=1 0:00 1:00 S Rule Jordan 1986 1990 - Oct Fri>=1 0:00 0 - Rule Jordan 1989 only - May 8 0:00 1:00 S Rule Jordan 1990 only - Apr 27 0:00 1:00 S Rule Jordan 1991 only - Apr 17 0:00 1:00 S Rule Jordan 1991 only - Sep 27 0:00 0 - Rule Jordan 1992 only - Apr 10 0:00 1:00 S Rule Jordan 1992 1993 - Oct Fri>=1 0:00 0 - Rule Jordan 1993 1998 - Apr Fri>=1 0:00 1:00 S Rule Jordan 1994 only - Sep Fri>=15 0:00 0 - Rule Jordan 1995 1998 - Sep Fri>=15 0:00s 0 - Rule Jordan 1999 only - Jul 1 0:00s 1:00 S Rule Jordan 1999 2002 - Sep lastFri 0:00s 0 - Rule Jordan 2000 2001 - Mar lastThu 0:00s 1:00 S Rule Jordan 2002 2012 - Mar lastThu 24:00 1:00 S Rule Jordan 2003 only - Oct 24 0:00s 0 - Rule Jordan 2004 only - Oct 15 0:00s 0 - Rule Jordan 2005 only - Sep lastFri 0:00s 0 - Rule Jordan 2006 2011 - Oct lastFri 0:00s 0 - Rule Jordan 2013 only - Dec 20 0:00 0 - Rule Jordan 2014 2021 - Mar lastThu 24:00 1:00 S Rule Jordan 2014 2022 - Oct lastFri 0:00s 0 - Rule Jordan 2022 only - Feb lastThu 24:00 1:00 S # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone Asia/Amman 2:23:44 - LMT 1931 2:00 Jordan EE%sT 2022 Oct 28 0:00s 3:00 - %z # Kazakhstan # From Kazakhstan Embassy's News Bulletin No. 11 # (2005-03-21): # The Government of Kazakhstan passed a resolution March 15 abolishing # daylight saving time citing lack of economic benefits and health # complications coupled with a decrease in productivity. # # From Branislav Kojic (in Astana) via Gwillim Law (2005-06-28): # ... what happened was that the former Kazakhstan Eastern time zone # was "blended" with the Central zone. Therefore, Kazakhstan now has # two time zones, and difference between them is one hour. The zone # closer to UTC is the former Western zone (probably still called the # same), encompassing four provinces in the west: Aqtöbe, Atyraū, # Mangghystaū, and West Kazakhstan. The other zone encompasses # everything else.... I guess that would make Kazakhstan time zones # de jure UTC+5 and UTC+6 respectively. # From Stepan Golosunov (2016-03-27): # Review of the linked documents from http://adilet.zan.kz/ # produced the following data for post-1991 Kazakhstan: # # 0. Act of the Cabinet of Ministers of the USSR # from 1991-02-04 No. 20 # http://pravo.gov.ru/proxy/ips/?docbody=&nd=102010545 # removed the extra hour ("decree time") on the territory of the USSR # starting with the last Sunday of March 1991. # It also allowed (but not mandated) Kazakh SSR, Kirghiz SSR, Tajik SSR, # Turkmen SSR and Uzbek SSR to not have "summer" time. # # The 1992-01-13 act also refers to the act of the Cabinet of Ministers # of the Kazakh SSR from 1991-03-20 No. 170 "About the act of the Cabinet # of Ministers of the USSR from 1991-02-04 No. 20" but I didn't found its # text. # # According to Izvestia newspaper No. 68 (23334) from 1991-03-20 # -- page 6; available at http://libinfo.org/newsr/newsr2574.djvu via # http://libinfo.org/index.php?id=58564 -- on 1991-03-31 at 2:00 during # transition to "summer" time: # Republic of Georgia, Latvian SSR, Lithuanian SSR, SSR Moldova, # Estonian SSR; Komi ASSR; Kaliningrad oblast; Nenets autonomous okrug # were to move clocks 1 hour forward. # Kazakh SSR (excluding Uralsk oblast); Republic of Kyrgyzstan, Tajik # SSR; Andijan, Jizzakh, Namangan, Sirdarya, Tashkent, Fergana oblasts # of the Uzbek SSR were to move clocks 1 hour backwards. # Other territories were to not move clocks. # When the "summer" time would end on 1991-09-29, clocks were to be # moved 1 hour backwards on the territory of the USSR excluding # Kazakhstan, Kirghizia, Uzbekistan, Turkmenia, Tajikistan. # # Apparently there were last minute changes. Apparently Kazakh act No. 170 # was one of such changes. # # https://ru.wikipedia.org/wiki/Декретное_время # claims that Sovetskaya Rossiya newspaper on 1991-03-29 published that # Nenets autonomous okrug, Komi and Kazakhstan (excluding Uralsk oblast) # were to not move clocks and Uralsk oblast was to move clocks # forward; on 1991-09-29 Kazakhstan was to move clocks backwards. # (Probably there were changes even after that publication. There is an # article claiming that Kaliningrad oblast decided on 1991-03-29 to not # move clocks.) # # This implies that on 1991-03-31 Asia/Oral remained on +04/+05 while # the rest of Kazakhstan switched from +06/+07 to +05/06 or from +05/06 # to +04/+05. It's unclear how Qyzylorda oblast moved into the fifth # time belt. (By switching from +04/+05 to +05/+06 on 1991-09-29?) ... # # 1. Act of the Cabinet of Ministers of the Republic of Kazakhstan # from 1992-01-13 No. 28 # http://adilet.zan.kz/rus/docs/P920000028_ # (text includes modification from the 1996 act) # introduced new rules for calculation of time, mirroring Russian # 1992-01-08 act. It specified that time would be calculated # according to time belts plus extra hour ("decree time"), moved clocks # on the whole territory of Kazakhstan 1 hour forward on 1992-01-19 at # 2:00, specified DST rules. It acknowledged that Kazakhstan was # located in the fourth and the fifth time belts and specified the # border between them to be located east of Qostanay and Aktyubinsk # oblasts (notably including Turgai and Qyzylorda oblasts into the fifth # time belt). # # This means switch on 1992-01-19 at 2:00 from +04/+05 to +05/+06 for # Asia/Aqtau, Asia/Aqtobe, Asia/Oral, Atyraū and Qostanay oblasts; from # +05/+06 to +06/+07 for Asia/Almaty and Asia/Qyzylorda (and Arkalyk).... # # 2. Act of the Cabinet of Ministers of the Republic of Kazakhstan # from 1992-03-27 No. 284 # http://adilet.zan.kz/rus/docs/P920000284_ # cancels extra hour ("decree time") for Uralsk and Qyzylorda oblasts # since the last Sunday of March 1992, while keeping them in the fourth # and the fifth time belts respectively. # # 3. Order of the Prime Minister of the Republic of Kazakhstan # from 1994-09-23 No. 384 # http://adilet.zan.kz/rus/docs/R940000384_ # cancels the extra hour ("decree time") on the territory of Mangghystaū # oblast since the last Sunday of September 1994 (saying that time on # the territory would correspond to the third time belt as a # result).... # # 4. Act of the Government of the Republic of Kazakhstan # from 1996-05-08 No. 575 # http://adilet.zan.kz/rus/docs/P960000575_ # amends the 1992-01-13 act to end summer time in October instead # of September, mirroring identical Russian change from 1996-04-23 act. # # 5. Act of the Government of the Republic of Kazakhstan # from 1999-03-26 No. 305 # http://adilet.zan.kz/rus/docs/P990000305_ # cancels the extra hour ("decree time") for Atyraū oblast since the # last Sunday of March 1999 while retaining the oblast in the fourth # time belt. # # This means change from +05/+06 to +04/+05.... # # 6. Act of the Government of the Republic of Kazakhstan # from 2000-11-23 No. 1749 # http://adilet.zan.kz/rus/archive/docs/P000001749_/23.11.2000 # replaces the previous five documents. # # The only changes I noticed are in definition of the border between the # fourth and the fifth time belts. They account for changes in spelling # and administrative division (splitting of Turgai oblast in 1997 # probably changed time in territories incorporated into Qostanay oblast # (including Arkalyk) from +06/+07 to +05/+06) and move Qyzylorda oblast # from being in the fifth time belt and not using decree time into the # fourth time belt (no change in practice). # # 7. Act of the Government of the Republic of Kazakhstan # from 2003-12-29 No. 1342 # http://adilet.zan.kz/rus/docs/P030001342_ # modified the 2000-11-23 act. No relevant changes, apparently. # # 8. Act of the Government of the Republic of Kazakhstan # from 2004-07-20 No. 775 # http://adilet.zan.kz/rus/archive/docs/P040000775_/20.07.2004 # modified the 2000-11-23 act to move Qostanay and Qyzylorda oblasts into # the fifth time belt and add Aktobe oblast to the list of regions not # using extra hour ("decree time"), leaving Kazakhstan with only 2 time # zones (+04/+05 and +06/+07). The changes were to be implemented # during DST transitions in 2004 and 2005 but the acts got radically # amended before implementation happened. # # 9. Act of the Government of the Republic of Kazakhstan # from 2004-09-15 No. 1059 # http://adilet.zan.kz/rus/docs/P040001059_ # modified the 2000-11-23 act to remove exceptions from the "decree time" # (leaving Kazakhstan in +05/+06 and +06/+07 zones), amended the # 2004-07-20 act to implement changes for Atyraū, West Kazakhstan, # Qostanay, Qyzylorda and Mangghystaū oblasts by not moving clocks # during the 2004 transition to "winter" time. # # This means transition from +04/+05 to +05/+06 for Atyraū oblast (no # zone currently), Asia/Oral, Asia/Aqtau and transition from +05/+06 to # +06/+07 for Qostanay oblast (Qostanay and Arkalyk, no zones currently) # and Asia/Qyzylorda on 2004-10-31 at 3:00.... # # 10. Act of the Government of the Republic of Kazakhstan # from 2005-03-15 No. 231 # http://adilet.zan.kz/rus/docs/P050000231_ # removes DST provisions from the 2000-11-23 act, removes most of the # (already implemented) provisions from the 2004-07-20 and 2004-09-15 # acts, comes into effect 10 days after official publication. # The only practical effect seems to be the abolition of the summer # time. # # Unamended version of the act of the Government of the Russian Federation # No. 23 from 1992-01-08 [See 'europe' file for details]. # Kazakh 1992-01-13 act appears to provide the same rules and 1992-03-27 # act was to be enacted on the last Sunday of March 1992. # From Stepan Golosunov (2016-11-08): # Turgai reorganization should affect only southern part of Qostanay # oblast. Which should probably be separated into Asia/Arkalyk zone. # (There were also 1970, 1988 and 1990 Turgai oblast reorganizations # according to wikipedia.) # # [For Qostanay] http://www.ng.kz/gazeta/195/hranit/ # suggests that clocks were to be moved 40 minutes backwards on # 1920-01-01 to the fourth time belt. But I do not understand # how that could happen.... # # [For Atyrau and Oral] 1919 decree # (http://www.worldtimezone.com/dst_news/dst_news_russia-1919-02-08.html # and in Byalokoz) lists Ural river (plus 10 versts on its left bank) in # the third time belt (before 1930 this means +03). # From Alexander Konzurovski (2018-12-20): # (Asia/Qyzylorda) is changing its time zone from UTC+6 to UTC+5 # effective December 21st, 2018.... # http://adilet.zan.kz/rus/docs/P1800000817 (russian language). # From Zhanbolat Raimbekov (2024-01-19): # Kazakhstan (all parts) switching to UTC+5 on March 1, 2024 # https://www.gov.kz/memleket/entities/mti/press/news/details/688998?lang=ru # [in Russian] # (2024-01-20): https://primeminister.kz/ru/decisions/19012024-20 # # From Alexander Krivenyshev (2024-01-19): # According to a different news and the official web site for the Ministry of # Trade and Integration of the Republic of Kazakhstan: # https://en.inform.kz/news/kazakhstan-to-switch-to-single-hour-zone-mar-1-54ad0b/ # Zone NAME STDOFF RULES FORMAT [UNTIL] # # Almaty (formerly Alma-Ata), representing most locations in Kazakhstan # This includes Abai/Abay (ISO 3166-2 code KZ-10), Aqmola/Akmola (KZ-11), # Almaty (KZ-19), Almaty city (KZ-75), Astana city (KZ-71), # East Kazakhstan (KZ-63), Jambyl/Zhambyl (KZ-31), Jetisu/Zhetysu (KZ-33), # Karaganda (KZ-35), North Kazakhstan (KZ-59), Pavlodar (KZ-55), # Shymkent city (KZ-79), Turkistan (KZ-61), and Ulytau (KZ-62). Zone Asia/Almaty 5:07:48 - LMT 1924 May 2 # or Alma-Ata 5:00 - %z 1930 Jun 21 6:00 RussiaAsia %z 1991 Mar 31 2:00s 5:00 RussiaAsia %z 1992 Jan 19 2:00s 6:00 RussiaAsia %z 2004 Oct 31 2:00s 6:00 - %z 2024 Mar 1 0:00 5:00 - %z # Qyzylorda (aka Kyzylorda, Kizilorda, Kzyl-Orda, etc.) (KZ-43) Zone Asia/Qyzylorda 4:21:52 - LMT 1924 May 2 4:00 - %z 1930 Jun 21 5:00 - %z 1981 Apr 1 5:00 1:00 %z 1981 Oct 1 6:00 - %z 1982 Apr 1 5:00 RussiaAsia %z 1991 Mar 31 2:00s 4:00 RussiaAsia %z 1991 Sep 29 2:00s 5:00 RussiaAsia %z 1992 Jan 19 2:00s 6:00 RussiaAsia %z 1992 Mar 29 2:00s 5:00 RussiaAsia %z 2004 Oct 31 2:00s 6:00 - %z 2018 Dec 21 0:00 5:00 - %z # Qostanay (aka Kostanay, Kustanay) (KZ-39) # The 1991/2 rules are unclear partly because of the 1997 Turgai # reorganization. Zone Asia/Qostanay 4:14:28 - LMT 1924 May 2 4:00 - %z 1930 Jun 21 5:00 - %z 1981 Apr 1 5:00 1:00 %z 1981 Oct 1 6:00 - %z 1982 Apr 1 5:00 RussiaAsia %z 1991 Mar 31 2:00s 4:00 RussiaAsia %z 1992 Jan 19 2:00s 5:00 RussiaAsia %z 2004 Oct 31 2:00s 6:00 - %z 2024 Mar 1 0:00 5:00 - %z # Aqtöbe (aka Aktobe, formerly Aktyubinsk) (KZ-15) Zone Asia/Aqtobe 3:48:40 - LMT 1924 May 2 4:00 - %z 1930 Jun 21 5:00 - %z 1981 Apr 1 5:00 1:00 %z 1981 Oct 1 6:00 - %z 1982 Apr 1 5:00 RussiaAsia %z 1991 Mar 31 2:00s 4:00 RussiaAsia %z 1992 Jan 19 2:00s 5:00 RussiaAsia %z 2004 Oct 31 2:00s 5:00 - %z # Mangghystaū (KZ-47) # Aqtau was not founded until 1963, but it represents an inhabited region, # so include timestamps before 1963. Zone Asia/Aqtau 3:21:04 - LMT 1924 May 2 4:00 - %z 1930 Jun 21 5:00 - %z 1981 Oct 1 6:00 - %z 1982 Apr 1 5:00 RussiaAsia %z 1991 Mar 31 2:00s 4:00 RussiaAsia %z 1992 Jan 19 2:00s 5:00 RussiaAsia %z 1994 Sep 25 2:00s 4:00 RussiaAsia %z 2004 Oct 31 2:00s 5:00 - %z # Atyraū (KZ-23) is like Mangghystaū except it switched from # +04/+05 to +05/+06 in spring 1999, not fall 1994. Zone Asia/Atyrau 3:27:44 - LMT 1924 May 2 3:00 - %z 1930 Jun 21 5:00 - %z 1981 Oct 1 6:00 - %z 1982 Apr 1 5:00 RussiaAsia %z 1991 Mar 31 2:00s 4:00 RussiaAsia %z 1992 Jan 19 2:00s 5:00 RussiaAsia %z 1999 Mar 28 2:00s 4:00 RussiaAsia %z 2004 Oct 31 2:00s 5:00 - %z # West Kazakhstan (KZ-27) # From Paul Eggert (2016-03-18): # The 1989 transition is from USSR act No. 227 (1989-03-14). Zone Asia/Oral 3:25:24 - LMT 1924 May 2 # or Ural'sk 3:00 - %z 1930 Jun 21 5:00 - %z 1981 Apr 1 5:00 1:00 %z 1981 Oct 1 6:00 - %z 1982 Apr 1 5:00 RussiaAsia %z 1989 Mar 26 2:00s 4:00 RussiaAsia %z 1992 Jan 19 2:00s 5:00 RussiaAsia %z 1992 Mar 29 2:00s 4:00 RussiaAsia %z 2004 Oct 31 2:00s 5:00 - %z # Kyrgyzstan (Kirgizstan) # Transitions through 1991 are from Shanks & Pottenger. # From Paul Eggert (2005-08-15): # According to an article dated today in the Kyrgyzstan Development Gateway # http://eng.gateway.kg/cgi-bin/page.pl?id=1&story_name=doc9979.shtml # Kyrgyzstan is canceling the daylight saving time system. I take the article # to mean that they will leave their clocks at 6 hours ahead of UTC. # From Malik Abdugaliev (2005-09-21): # Our government cancels daylight saving time 6th of August 2005. # From 2005-08-12 our GMT-offset is +6, w/o any daylight saving. # Rule NAME FROM TO - IN ON AT SAVE LETTER/S Rule Kyrgyz 1992 1996 - Apr Sun>=7 0:00s 1:00 - Rule Kyrgyz 1992 1996 - Sep lastSun 0:00 0 - Rule Kyrgyz 1997 2005 - Mar lastSun 2:30 1:00 - Rule Kyrgyz 1997 2004 - Oct lastSun 2:30 0 - # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone Asia/Bishkek 4:58:24 - LMT 1924 May 2 5:00 - %z 1930 Jun 21 6:00 RussiaAsia %z 1991 Mar 31 2:00s 5:00 RussiaAsia %z 1991 Aug 31 2:00 5:00 Kyrgyz %z 2005 Aug 12 6:00 - %z ############################################################################### # Korea (North and South) # From Annie I. Bang (2006-07-10): # http://www.koreaherald.com/view.php?ud=200607100012 # Korea ran a daylight saving program from 1949-61 but stopped it # during the 1950-53 Korean War. The system was temporarily enforced # between 1987 and 1988 ... # From Sanghyuk Jung (2014-10-29): # https://mm.icann.org/pipermail/tz/2014-October/021830.html # According to the Korean Wikipedia # https://ko.wikipedia.org/wiki/한국_표준시 # [oldid=12896437 2014-09-04 08:03 UTC] # DST in Republic of Korea was as follows.... And I checked old # newspapers in Korean, all articles correspond with data in Wikipedia. # For example, the article in 1948 (Korean Language) proved that DST # started at June 1 in that year. For another example, the article in # 1988 said that DST started at 2:00 AM in that year. # From Phake Nick (2018-10-27): # 1. According to official announcement from Korean government, the DST end # date in South Korea should be # 1955-09-08 without specifying time # http://theme.archives.go.kr/next/common/viewEbook.do?singleData=N&archiveEventId=0027977557 # 1956-09-29 without specifying time # http://theme.archives.go.kr/next/common/viewEbook.do?singleData=N&archiveEventId=0027978341 # 1957-09-21 24 o'clock # http://theme.archives.go.kr/next/common/viewEbook.do?singleData=N&archiveEventId=0027979690#3 # 1958-09-20 24 o'clock # http://theme.archives.go.kr/next/common/viewEbook.do?singleData=N&archiveEventId=0027981189 # 1959-09-19 24 o'clock # http://theme.archives.go.kr/next/common/viewEbook.do?singleData=N&archiveEventId=0027982974#2 # 1960-09-17 24 o'clock # http://theme.archives.go.kr/next/common/viewEbook.do?singleData=N&archiveEventId=0028044104 # ... # 2.... https://namu.wiki/w/대한민국%20표준시 ... [says] # when Korea was using GMT+8:30 as standard time, the international # aviation/marine/meteorological industry in the country refused to # follow and continued to use GMT+9:00 for interoperability. # Rule NAME FROM TO - IN ON AT SAVE LETTER/S Rule ROK 1948 only - Jun 1 0:00 1:00 D Rule ROK 1948 only - Sep 12 24:00 0 S Rule ROK 1949 only - Apr 3 0:00 1:00 D Rule ROK 1949 1951 - Sep Sat>=7 24:00 0 S Rule ROK 1950 only - Apr 1 0:00 1:00 D Rule ROK 1951 only - May 6 0:00 1:00 D Rule ROK 1955 only - May 5 0:00 1:00 D Rule ROK 1955 only - Sep 8 24:00 0 S Rule ROK 1956 only - May 20 0:00 1:00 D Rule ROK 1956 only - Sep 29 24:00 0 S Rule ROK 1957 1960 - May Sun>=1 0:00 1:00 D Rule ROK 1957 1960 - Sep Sat>=17 24:00 0 S Rule ROK 1987 1988 - May Sun>=8 2:00 1:00 D Rule ROK 1987 1988 - Oct Sun>=8 3:00 0 S # From Paul Eggert (2016-08-23): # The Korean Wikipedia entry gives the following sources for UT offsets: # # 1908: Official Journal Article No. 3994 (decree No. 5) # 1912: Governor-General of Korea Official Gazette Issue No. 367 # (Announcement No. 338) # 1954: Presidential Decree No. 876 (1954-03-17) # 1961: Law No. 676 (1961-08-07) # # (Another source "1987: Law No. 3919 (1986-12-31)" was in the 2014-10-30 # edition of the Korean Wikipedia entry.) # # I guessed that time zone abbreviations through 1945 followed the same # rules as discussed under Taiwan, with nominal switches from JST to KST # when the respective cities were taken over by the Allies after WWII. # # For Pyongyang, guess no changes from World War II until 2015, as we # have no information otherwise. # From Steffen Thorsen (2015-08-07): # According to many news sources, North Korea is going to change to # the 8:30 time zone on August 15, one example: # http://www.bbc.com/news/world-asia-33815049 # # From Paul Eggert (2015-08-15): # Bells rang out midnight (00:00) Friday as part of the celebrations. See: # Talmadge E. North Korea celebrates new time zone, 'Pyongyang Time' # http://news.yahoo.com/north-korea-celebrates-time-zone-pyongyang-time-164038128.html # There is no common English-language abbreviation for this time zone. # Use KST, as that's what we already use for 1954-1961 in ROK. # From Kang Seonghoon (2018-04-29): # North Korea will revert its time zone from UTC+8:30 (PYT; Pyongyang # Time) back to UTC+9 (KST; Korea Standard Time). # # From Seo Sanghyeon (2018-04-30): # Rodong Sinmun 2018-04-30 announced Pyongyang Time transition plan. # https://www.nknews.org/kcna/wp-content/uploads/sites/5/2018/04/rodong-2018-04-30.pdf # ... the transition date is 2018-05-05 ... Citation should be Decree # No. 2232 of April 30, 2018, of the Presidium of the Supreme People's # Assembly, as published in Rodong Sinmun. # From Tim Parenti (2018-04-29): # It appears to be the front page story at the top in the right-most column. # # From Paul Eggert (2018-05-04): # The BBC reported that the transition was from 23:30 to 24:00 today. # https://www.bbc.com/news/world-asia-44010705 # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone Asia/Seoul 8:27:52 - LMT 1908 Apr 1 8:30 - KST 1912 Jan 1 9:00 - JST 1945 Sep 8 9:00 ROK K%sT 1954 Mar 21 8:30 ROK K%sT 1961 Aug 10 9:00 ROK K%sT Zone Asia/Pyongyang 8:23:00 - LMT 1908 Apr 1 8:30 - KST 1912 Jan 1 9:00 - JST 1945 Aug 24 9:00 - KST 2015 Aug 15 00:00 8:30 - KST 2018 May 4 23:30 9:00 - KST # Lebanon # # From Saadallah Itani (2023-03-23): # Lebanon ... announced today delay of Spring forward from March 25 to April 20. # # From Paul Eggert (2023-03-27): # This announcement was by the Lebanese caretaker prime minister Najib Mikati. # https://www.mtv.com.lb/en/News/Local/1352516/lebanon-postpones-daylight-saving-time-adoption # A video was later leaked to the media of parliament speaker Nabih Berri # asking Mikati to postpone DST to aid observance of Ramadan, Mikati objecting # that this would cause problems such as scheduling airline flights, to which # Berri interjected, "What flights?" # # The change was controversial and led to a partly-sectarian divide. # Many Lebanese institutions, including the education ministry, the Maronite # church, and two news channels LCBI and MTV, ignored the announcement and # went ahead with the long-scheduled spring-forward on March 25/26, some # arguing that the prime minister had not followed the law because the change # had not been approved by the cabinet. Google went with the announcement; # Apple ignored it. At least one bank followed the announcement for its doors, # but ignored the announcement in internal computer systems. # Beirut international airport listed two times for each departure. # Dan Azzi wrote "My view is that this whole thing is a Dumb and Dumber movie." # Eventually the prime minister backed down, said the cabinet had decided to # stick with its 1998 decision, and that DST would begin midnight March 29/30. # https://www.nna-leb.gov.lb/en/miscellaneous/604093/lebanon-has-two-times-of-day-amid-daylight-savings # https://www.cnbc.com/2023/03/27/lebanon-in-two-different-time-zones-as-government-disagrees-on-daylight-savings.html # # Although we could model the chaos with two Zones, that would likely cause # more trouble than it would cure. Since so many manual clocks and # computer-based timestamps ignored the announcement, stick with official # cabinet resolutions in the data while recording the prime minister's # announcement as a comment. This is how we treated a similar situation in # Rio de Janeiro in spring 1993. # # Rule NAME FROM TO - IN ON AT SAVE LETTER/S Rule Lebanon 1920 only - Mar 28 0:00 1:00 S Rule Lebanon 1920 only - Oct 25 0:00 0 - Rule Lebanon 1921 only - Apr 3 0:00 1:00 S Rule Lebanon 1921 only - Oct 3 0:00 0 - Rule Lebanon 1922 only - Mar 26 0:00 1:00 S Rule Lebanon 1922 only - Oct 8 0:00 0 - Rule Lebanon 1923 only - Apr 22 0:00 1:00 S Rule Lebanon 1923 only - Sep 16 0:00 0 - Rule Lebanon 1957 1961 - May 1 0:00 1:00 S Rule Lebanon 1957 1961 - Oct 1 0:00 0 - Rule Lebanon 1972 only - Jun 22 0:00 1:00 S Rule Lebanon 1972 1977 - Oct 1 0:00 0 - Rule Lebanon 1973 1977 - May 1 0:00 1:00 S Rule Lebanon 1978 only - Apr 30 0:00 1:00 S Rule Lebanon 1978 only - Sep 30 0:00 0 - Rule Lebanon 1984 1987 - May 1 0:00 1:00 S Rule Lebanon 1984 1991 - Oct 16 0:00 0 - Rule Lebanon 1988 only - Jun 1 0:00 1:00 S Rule Lebanon 1989 only - May 10 0:00 1:00 S Rule Lebanon 1990 1992 - May 1 0:00 1:00 S Rule Lebanon 1992 only - Oct 4 0:00 0 - Rule Lebanon 1993 max - Mar lastSun 0:00 1:00 S Rule Lebanon 1993 1998 - Sep lastSun 0:00 0 - Rule Lebanon 1999 max - Oct lastSun 0:00 0 - # This one-time rule, announced by the prime minister first for April 21 # then for March 30, is commented out for reasons described above. #Rule Lebanon 2023 only - Mar 30 0:00 1:00 S # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone Asia/Beirut 2:22:00 - LMT 1880 2:00 Lebanon EE%sT # Brunei # Malaysia (eastern) # # Rule NAME FROM TO - IN ON AT SAVE LETTER/S Rule NBorneo 1935 1941 - Sep 14 0:00 0:20 - Rule NBorneo 1935 1941 - Dec 14 0:00 0 - # # For peninsular Malaysia see Asia/Singapore. # # Sabah & Sarawak # From Paul Eggert (2014-08-12): # The data entries here are mostly from Shanks & Pottenger, but the 1942, 1945 # and 1982 transition dates are from Mok Ly Yng. # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone Asia/Kuching 7:21:20 - LMT 1926 Mar 7:30 - %z 1933 8:00 NBorneo %z 1942 Feb 16 9:00 - %z 1945 Sep 12 8:00 - %z # Maldives # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone Indian/Maldives 4:54:00 - LMT 1880 # Malé 4:54:00 - MMT 1960 # Malé Mean Time 5:00 - %z # Mongolia # Shanks & Pottenger say that Mongolia has three time zones, but # The USNO (1995-12-21) and the CIA map Standard Time Zones of the World # (2005-03) both say that it has just one. # From Oscar van Vlijmen (1999-12-11): # General Information Mongolia # (1999-09) # "Time: Mongolia has two time zones. Three westernmost provinces of # Bayan-Ölgii, Uvs, and Hovd are one hour earlier than the capital city, and # the rest of the country follows the Ulaanbaatar time, which is UTC/GMT plus # eight hours." # From Rives McDow (1999-12-13): # Mongolia discontinued the use of daylight savings time in 1999; 1998 # being the last year it was implemented. The dates of implementation I am # unsure of, but most probably it was similar to Russia, except for the time # of implementation may have been different.... # Some maps in the past have indicated that there was an additional time # zone in the eastern part of Mongolia, including the provinces of Dornod, # Sükhbaatar, and possibly Khentii. # From Paul Eggert (1999-12-15): # Naming and spelling is tricky in Mongolia. # We'll use Hovd (also spelled Chovd and Khovd) to represent the west zone; # the capital of the Hovd province is sometimes called Hovd, sometimes Dund-Us, # and sometimes Jirgalanta (with variant spellings), but the name Hovd # is good enough for our purposes. # From Rives McDow (2001-05-13): # In addition to Mongolia starting daylight savings as reported earlier # (adopted DST on 2001-04-27 02:00 local time, ending 2001-09-28), # there are three time zones. # # Provinces [at 7:00]: Bayan-Ölgii, Uvs, Khovd, Zavkhan, Govi-Altai # Provinces [at 8:00]: Khövsgöl, Bulgan, Arkhangai, Khentii, Töv, # Bayankhongor, Övörkhangai, Dundgovi, Dornogovi, Ömnögovi # Provinces [at 9:00]: Dornod, Sükhbaatar # # [The province of Selenge is omitted from the above lists.] # From Ganbold Ts., Ulaanbaatar (2004-04-17): # Daylight saving occurs at 02:00 local time last Saturday of March. # It will change back to normal at 02:00 local time last Saturday of # September.... As I remember this rule was changed in 2001. # # From Paul Eggert (2004-04-17): # For now, assume Rives McDow's informant got confused about Friday vs # Saturday, and that his 2001 dates should have 1 added to them. # From Paul Eggert (2005-07-26): # We have wildly conflicting information about Mongolia's time zones. # Bill Bonnet (2005-05-19) reports that the US Embassy in Ulaanbaatar says # there is only one time zone and that DST is observed, citing Microsoft # Windows XP as the source. Risto Nykänen (2005-05-16) reports that # travelmongolia.org says there are two time zones (UT +07, +08) with no DST. # Oscar van Vlijmen (2005-05-20) reports that the Mongolian Embassy in # Washington, DC says there are two time zones, with DST observed. # He also found # http://ubpost.mongolnews.mn/index.php?subaction=showcomments&id=1111634894&archive=&start_from=&ucat=1& # which also says that there is DST, and which has a comment by "Toddius" # (2005-03-31 06:05 +0700) saying "Mongolia actually has 3.5 time zones. # The West (OLGII) is +7 GMT, most of the country is ULAT is +8 GMT # and some Eastern provinces are +9 GMT but Sükhbaatar Aimag is SUHK +8.5 GMT. # The SUKH timezone is new this year, it is one of the few things the # parliament passed during the tumultuous winter session." # For now, let's ignore this information, until we have more confirmation. # From Ganbold Ts. (2007-02-26): # Parliament of Mongolia has just changed the daylight-saving rule in February. # They decided not to adopt daylight-saving time.... # http://www.mongolnews.mn/index.php?module=unuudur&sec=view&id=15742 # From Deborah Goldsmith (2008-03-30): # We received a bug report claiming that the tz database UTC offset for # Asia/Choibalsan (GMT+09:00) is incorrect, and that it should be GMT # +08:00 instead. Different sources appear to disagree with the tz # database on this, e.g.: # # https://www.timeanddate.com/worldclock/city.html?n=1026 # http://www.worldtimeserver.com/current_time_in_MN.aspx # # both say GMT+08:00. # From Steffen Thorsen (2008-03-31): # eznis airways, which operates several domestic flights, has a flight # schedule here: # http://www.eznis.com/Container.jsp?id=112 # (click the English flag for English) # # There it appears that flights between Choibalsan and Ulaanbaatar arrive # about 1:35 - 1:50 hours later in local clock time, no matter the # direction, while Ulaanbaatar-Khovd takes 2 hours in the Eastern # direction and 3:35 back, which indicates that Ulaanbaatar and Khovd are # in different time zones (like we know about), while Choibalsan and # Ulaanbaatar are in the same time zone (correction needed). # From Arthur David Olson (2008-05-19): # Assume that Choibalsan is indeed offset by 8:00. # From Heitor David Pinto (2024-06-23): # Sources about time zones in Mongolia seem to list one of two conflicting # configurations. The first configuration, mentioned in a comment to the TZ # database in 1999, citing a Mongolian government website, lists the provinces # of Bayan-Ölgii, Khovd and Uvs in UTC+7, and the rest of the country in # UTC+8. The second configuration, mentioned in a comment to the database in # 2001, lists Bayan-Ölgii, Khovd, Uvs, Govi-Altai and Zavkhan in UTC+7, Dornod # and Sükhbaatar in UTC+9, and the rest of the country in UTC+8. # # The first configuration is still mentioned by several Mongolian travel # agencies: # https://www.adventurerider.mn/en/page/about_mongolia # http://www.naturetours.mn/nt/mongolia.php # https://www.newjuulchin.mn/web/content/7506?unique=fa24a0f6e96e022a3578ee5195ac879638c734ce # # It also matches these flight schedules in 2013: # http://web.archive.org/web/20130722023600/https://www.hunnuair.com/en/timetabled # The flight times imply that the airports of Uliastai (Zavkhan), Choibalsan # (Dornod) and Altai (Govi-Altai) are in the same time zone as Ulaanbaatar, # and Khovd is one hour behind.... # # The second configuration was mentioned by an official of the Mongolian # standards agency in an interview in 2014: https://ikon.mn/n/9v6 # And it's still listed by the Mongolian aviation agency: # https://ais.mn/files/aip/eAIP/2023-12-25/html/eSUP/ZM-eSUP-23-04-en-MN.html # # ... I believe that the first configuration is what is actually observed in # Mongolia and has been so all along, at least since 1999. The second # configuration closely matches the ideal time zone boundaries at 97.5° E and # 112.5° E but it doesn't seem to be used in practice. # From Ganbold Tsagaankhuu (2015-03-10): # It seems like yesterday Mongolian Government meeting has concluded to use # daylight saving time in Mongolia.... Starting at 2:00AM of last Saturday of # March 2015, daylight saving time starts. And 00:00AM of last Saturday of # September daylight saving time ends. Source: # http://zasag.mn/news/view/8969 # Rule NAME FROM TO - IN ON AT SAVE LETTER/S Rule Mongol 1983 1984 - Apr 1 0:00 1:00 - Rule Mongol 1983 only - Oct 1 0:00 0 - # Shanks & Pottenger and IATA SSIM say 1990s switches occurred at 00:00, # but McDow says the 2001 switches occurred at 02:00. Also, IATA SSIM # (1996-09) says 1996-10-25. Go with Shanks & Pottenger through 1998. # # Shanks & Pottenger say that the Sept. 1984 through Sept. 1990 switches # in Choibalsan (more precisely, in Dornod and Sükhbaatar) took place # at 02:00 standard time, not at 00:00 local time as in the rest of # the country. That would be odd, and possibly is a result of their # correction of 02:00 (in the previous edition) not being done correctly # in the latest edition; so ignore it for now. # From Ganbold Tsagaankhuu (2017-02-09): # Mongolian Government meeting has concluded today to cancel daylight # saving time adoption in Mongolia. Source: http://zasag.mn/news/view/16192 Rule Mongol 1985 1998 - Mar lastSun 0:00 1:00 - Rule Mongol 1984 1998 - Sep lastSun 0:00 0 - # IATA SSIM (1999-09) says Mongolia no longer observes DST. Rule Mongol 2001 only - Apr lastSat 2:00 1:00 - Rule Mongol 2001 2006 - Sep lastSat 2:00 0 - Rule Mongol 2002 2006 - Mar lastSat 2:00 1:00 - Rule Mongol 2015 2016 - Mar lastSat 2:00 1:00 - Rule Mongol 2015 2016 - Sep lastSat 0:00 0 - # Zone NAME STDOFF RULES FORMAT [UNTIL] # Hovd, a.k.a. Chovd, Dund-Us, Dzhargalant, Khovd, Jirgalanta Zone Asia/Hovd 6:06:36 - LMT 1905 Aug 6:00 - %z 1978 7:00 Mongol %z # Ulaanbaatar, a.k.a. Ulan Bataar, Ulan Bator, Urga Zone Asia/Ulaanbaatar 7:07:32 - LMT 1905 Aug 7:00 - %z 1978 8:00 Mongol %z # Nepal # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone Asia/Kathmandu 5:41:16 - LMT 1920 5:30 - %z 1986 5:45 - %z # Pakistan # From Rives McDow (2002-03-13): # I have been advised that Pakistan has decided to adopt dst on a # TRIAL basis for one year, starting 00:01 local time on April 7, 2002 # and ending at 00:01 local time October 6, 2002. This is what I was # told, but I believe that the actual time of change may be 00:00; the # 00:01 was to make it clear which day it was on. # From Paul Eggert (2002-03-15): # Jesper Nørgaard found this URL: # http://www.pak.gov.pk/public/news/app/app06_dec.htm # (dated 2001-12-06) which says that the Cabinet adopted a scheme "to # advance the clocks by one hour on the night between the first # Saturday and Sunday of April and revert to the original position on # 15th October each year". This agrees with McDow's 04-07 at 00:00, # but disagrees about the October transition, and makes it sound like # it's not on a trial basis. Also, the "between the first Saturday # and Sunday of April" phrase, if taken literally, means that the # transition takes place at 00:00 on the first Sunday on or after 04-02. # From Paul Eggert (2003-02-09): # DAWN reported on 2002-10-05 # that 2002 DST ended that day at midnight. Go with McDow for now. # From Steffen Thorsen (2003-03-14): # According to http://www.dawn.com/2003/03/07/top15.htm # there will be no DST in Pakistan this year: # # ISLAMABAD, March 6: Information and Media Development Minister Sheikh # Rashid Ahmed on Thursday said the cabinet had reversed a previous # decision to advance clocks by one hour in summer and put them back by # one hour in winter with the aim of saving light hours and energy. # # The minister told a news conference that the experiment had rather # shown 8 per cent higher consumption of electricity. # From Alex Krivenyshev (2008-05-15): # # Here is an article that Pakistan plan to introduce Daylight Saving Time # on June 1, 2008 for 3 months. # # "... The federal cabinet on Wednesday announced a new conservation plan to # help reduce load shedding by approving the closure of commercial centres at # 9pm and moving clocks forward by one hour for the next three months. ...." # # http://www.worldtimezone.com/dst_news/dst_news_pakistan01.html # http://www.dailytimes.com.pk/default.asp?page=2008\05\15\story_15-5-2008_pg1_4 # From Arthur David Olson (2008-05-19): # XXX--midnight transitions is a guess; 2008 only is a guess. # From Alexander Krivenyshev (2008-08-28): # Pakistan government has decided to keep the watches one-hour advanced # for another 2 months - plan to return to Standard Time on October 31 # instead of August 31. # # http://www.worldtimezone.com/dst_news/dst_news_pakistan02.html # http://dailymailnews.com/200808/28/news/dmbrn03.html # From Alexander Krivenyshev (2009-04-08): # Based on previous media reports that "... proposed plan to # advance clocks by one hour from May 1 will cause disturbance # to the working schedules rather than bringing discipline in # official working." # http://www.thenews.com.pk/daily_detail.asp?id=171280 # # recent news that instead of May 2009 - Pakistan plan to # introduce DST from April 15, 2009 # # FYI: Associated Press Of Pakistan # April 08, 2009 # Cabinet okays proposal to advance clocks by one hour from April 15 # http://www.app.com.pk/en_/index.php?option=com_content&task=view&id=73043&Itemid=1 # http://www.worldtimezone.com/dst_news/dst_news_pakistan05.html # # .... # The Federal Cabinet on Wednesday approved the proposal to # advance clocks in the country by one hour from April 15 to # conserve energy" # From Steffen Thorsen (2009-09-17): # "The News International," Pakistan reports that: "The Federal # Government has decided to restore the previous time by moving the # clocks backward by one hour from October 1. A formal announcement to # this effect will be made after the Prime Minister grants approval in # this regard." # http://www.thenews.com.pk/updates.asp?id=87168 # From Alexander Krivenyshev (2009-09-28): # According to Associated Press Of Pakistan, it is confirmed that # Pakistan clocks across the country would be turned back by an hour from # October 1, 2009. # # "Clocks to go back one hour from 1 Oct" # http://www.app.com.pk/en_/index.php?option=com_content&task=view&id=86715&Itemid=2 # http://www.worldtimezone.com/dst_news/dst_news_pakistan07.htm # # From Steffen Thorsen (2009-09-29): # Now they seem to have changed their mind, November 1 is the new date: # http://www.thenews.com.pk/top_story_detail.asp?Id=24742 # "The country's clocks will be reversed by one hour on November 1. # Officials of Federal Ministry for Interior told this to Geo News on # Monday." # # And more importantly, it seems that these dates will be kept every year: # "It has now been decided that clocks will be wound forward by one hour # on April 15 and reversed by an hour on November 1 every year without # obtaining prior approval, the officials added." # # We have confirmed this year's end date with both with the Ministry of # Water and Power and the Pakistan Electric Power Company: # https://www.timeanddate.com/news/time/pakistan-ends-dst09.html # From Christoph Göhre (2009-10-01): # [T]he German Consulate General in Karachi reported me today that Pakistan # will go back to standard time on 1st of November. # From Steffen Thorsen (2010-03-26): # Steffen Thorsen wrote: # > On Thursday (2010-03-25) it was announced that DST would start in # > Pakistan on 2010-04-01. # > # > Then today, the president said that they might have to revert the # > decision if it is not supported by the parliament. So at the time # > being, it seems unclear if DST will be actually observed or not - but # > April 1 could be a more likely date than April 15. # Now, it seems that the decision to not observe DST in final: # # "Govt Withdraws Plan To Advance Clocks" # http://www.apakistannews.com/govt-withdraws-plan-to-advance-clocks-172041 # # "People laud PM's announcement to end DST" # http://www.app.com.pk/en_/index.php?option=com_content&task=view&id=99374&Itemid=2 # Rule NAME FROM TO - IN ON AT SAVE LETTER/S Rule Pakistan 2002 only - Apr Sun>=2 0:00 1:00 S Rule Pakistan 2002 only - Oct Sun>=2 0:00 0 - Rule Pakistan 2008 only - Jun 1 0:00 1:00 S Rule Pakistan 2008 2009 - Nov 1 0:00 0 - Rule Pakistan 2009 only - Apr 15 0:00 1:00 S # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone Asia/Karachi 4:28:12 - LMT 1907 5:30 - %z 1942 Sep 5:30 1:00 %z 1945 Oct 15 5:30 - %z 1951 Sep 30 5:00 - %z 1971 Mar 26 5:00 Pakistan PK%sT # Pakistan Time # Palestine # From Amos Shapir (1998-02-15): # # From 1917 until 1948-05-15, all of Palestine, including the parts now # known as the Gaza Strip and the West Bank, was under British rule. # Therefore the rules given for Israel for that period, apply there too... # # The Gaza Strip was under Egyptian rule between 1948-05-15 until 1967-06-05 # (except a short occupation by Israel from 1956-11 till 1957-03, but no # time zone was affected then). It was never formally annexed to Egypt, # though. # # The rest of Palestine was under Jordanian rule at that time, formally # annexed in 1950 as the West Bank (and the word "Trans" was dropped from # the country's previous name of "the Hashemite Kingdom of the # Trans-Jordan"). So the rules for Jordan for that time apply. Major # towns in that area are Nablus (Shchem), El-Halil (Hebron), Ramallah, and # East Jerusalem. # # Both areas were occupied by Israel in June 1967, but not annexed (except # for East Jerusalem). They were on Israel time since then; there might # have been a Military Governor's order about time zones, but I'm not aware # of any (such orders may have been issued semi-annually whenever summer # time was in effect, but maybe the legal aspect of time was just neglected). # # The Palestinian Authority was established in 1993, and got hold of most # towns in the West Bank and Gaza by 1995. I know that in order to # demonstrate...independence, they have been switching to # summer time and back on a different schedule than Israel's, but I don't # know when this was started, or what algorithm is used (most likely the # Jordanian one). # # To summarize, the table should probably look something like that: # # Area \ when | 1918-1947 | 1948-1967 | 1967-1995 | 1996- # ------------+-----------+-----------+-----------+----------- # Israel | Zion | Zion | Zion | Zion # West bank | Zion | Jordan | Zion | Jordan # Gaza | Zion | Egypt | Zion | Jordan # # I guess more info may be available from the PA's web page (if/when they # have one). # From Paul Eggert (2006-03-22): # Shanks & Pottenger write that Gaza did not observe DST until 1957, but go # with Shapir and assume that it observed DST from 1940 through 1947, # and that it used Jordanian rules starting in 1996. # We don't yet need a separate entry for the West Bank, since # the only differences between it and Gaza that we know about # occurred before our cutoff date of 1970. # However, as we get more information, we may need to add entries # for parts of the West Bank as they transitioned from Israel's rules # to Palestine's rules. # From IINS News Service - Israel - 1998-03-23 10:38:07 Israel time, # forwarded by Ephraim Silverberg: # # Despite the fact that Israel changed over to daylight savings time # last week, the PLO Authority (PA) has decided not to turn its clocks # one-hour forward at this time. As a sign of independence from Israeli rule, # the PA has decided to implement DST in April. # From Paul Eggert (1999-09-20): # Daoud Kuttab writes in Holiday havoc # http://www.jpost.com/com/Archive/22.Apr.1999/Opinion/Article-2.html # (Jerusalem Post, 1999-04-22) that # the Palestinian National Authority changed to DST on 1999-04-15. # I vaguely recall that they switch back in October (sorry, forgot the source). # For now, let's assume that the spring switch was at 24:00, # and that they switch at 0:00 on the 3rd Fridays of April and October. # From Paul Eggert (2005-11-22): # Starting 2004 transitions are from Steffen Thorsen's web site timeanddate.com. # From Steffen Thorsen (2005-11-23): # A user from Gaza reported that Gaza made the change early because of # the Ramadan. Next year Ramadan will be even earlier, so I think # there is a good chance next year's end date will be around two weeks # earlier - the same goes for Jordan. # From Steffen Thorsen (2006-08-17): # I was informed by a user in Bethlehem that in Bethlehem it started the # same day as Israel, and after checking with other users in the area, I # was informed that they started DST one day after Israel. I was not # able to find any authoritative sources at the time, nor details if # Gaza changed as well, but presumed Gaza to follow the same rules as # the West Bank. # From Steffen Thorsen (2006-09-26): # according to the Palestine News Network (2006-09-19): # http://english.pnn.ps/index.php?option=com_content&task=view&id=596&Itemid=5 # > The Council of Ministers announced that this year its winter schedule # > will begin early, as of midnight Thursday. It is also time to turn # > back the clocks for winter. Friday will begin an hour late this week. # I guess it is likely that next year's date will be moved as well, # because of the Ramadan. # From Jesper Nørgaard Welen (2007-09-18): # According to Steffen Thorsen's web site the Gaza Strip and the rest of the # Palestinian territories left DST early on 13.th. of September at 2:00. # From Paul Eggert (2007-09-20): # My understanding is that Gaza and the West Bank disagree even over when # the weekend is (Thursday+Friday versus Friday+Saturday), so I'd be a bit # surprised if they agreed about DST. But for now, assume they agree. # For lack of better information, predict that future changes will be # the 2nd Thursday of September at 02:00. # From Alexander Krivenyshev (2008-08-28): # Here is an article, that Mideast running on different clocks at Ramadan. # # Gaza Strip (as Egypt) ended DST at midnight Thursday (Aug 28, 2008), while # the West Bank will end Daylight Saving Time at midnight Sunday (Aug 31, 2008). # # http://www.guardian.co.uk/world/feedarticle/7759001 # http://www.abcnews.go.com/International/wireStory?id=5676087 # http://www.worldtimezone.com/dst_news/dst_news_gazastrip01.html # From Alexander Krivenyshev (2009-03-26): # According to the Palestine News Network (arabic.pnn.ps), Palestinian # government decided to start Daylight Time on Thursday night March # 26 and continue until the night of 27 September 2009. # # (in Arabic) # http://arabic.pnn.ps/index.php?option=com_content&task=view&id=50850 # # (English translation) # http://www.worldtimezone.com/dst_news/dst_news_westbank01.html # From Steffen Thorsen (2009-08-31): # Palestine's Council of Ministers announced that they will revert back to # winter time on Friday, 2009-09-04. # # One news source: # http://www.safa.ps/ara/?action=showdetail&seid=4158 # (Palestinian press agency, Arabic), # Google translate: "Decided that the Palestinian government in Ramallah # headed by Salam Fayyad, the start of work in time for the winter of # 2009, starting on Friday approved the fourth delay Sept. clock sixty # minutes per hour as of Friday morning." # # We are not sure if Gaza will do the same, last year they had a different # end date, we will keep this page updated: # https://www.timeanddate.com/news/time/westbank-gaza-dst-2009.html # From Alexander Krivenyshev (2009-09-02): # Seems that Gaza Strip will go back to Winter Time same date as West Bank. # # According to Palestinian Ministry Of Interior, West Bank and Gaza Strip plan # to change time back to Standard time on September 4, 2009. # # "Winter time unite the West Bank and Gaza" # (from Palestinian National Authority): # http://www.moi.gov.ps/en/?page=633167343250594025&nid=11505 # http://www.worldtimezone.com/dst_news/dst_news_gazastrip02.html # From Alexander Krivenyshev (2010-03-19): # According to Voice of Palestine DST will last for 191 days, from March # 26, 2010 till "the last Sunday before the tenth day of Tishri # (October), each year" (October 03, 2010?) # # http://palvoice.org/forums/showthread.php?t=245697 # (in Arabic) # http://www.worldtimezone.com/dst_news/dst_news_westbank03.html # From Steffen Thorsen (2010-03-24): # ...Ma'an News Agency reports that Hamas cabinet has decided it will # start one day later, at 12:01am. Not sure if they really mean 12:01am or # noon though: # # http://www.maannews.net/eng/ViewDetails.aspx?ID=271178 # (Ma'an News Agency) # "At 12:01am Friday, clocks in Israel and the West Bank will change to # 1:01am, while Gaza clocks will change at 12:01am Saturday morning." # From Steffen Thorsen (2010-08-11): # According to several sources, including # http://www.maannews.net/eng/ViewDetails.aspx?ID=306795 # the clocks were set back one hour at 2010-08-11 00:00:00 local time in # Gaza and the West Bank. # Some more background info: # https://www.timeanddate.com/news/time/westbank-gaza-end-dst-2010.html # From Steffen Thorsen (2011-08-26): # Gaza and the West Bank did go back to standard time in the beginning of # August, and will now enter daylight saving time again on 2011-08-30 # 00:00 (so two periods of DST in 2011). The pause was because of # Ramadan. # # http://www.maannews.net/eng/ViewDetails.aspx?ID=416217 # Additional info: # https://www.timeanddate.com/news/time/palestine-dst-2011.html # From Alexander Krivenyshev (2011-08-27): # According to the article in The Jerusalem Post: # "...Earlier this month, the Palestinian government in the West Bank decided to # move to standard time for 30 days, during Ramadan. The Palestinians in the # Gaza Strip accepted the change and also moved their clocks one hour back. # The Hamas government said on Saturday that it won't observe summertime after # the Muslim feast of Id al-Fitr, which begins on Tuesday..." # ... # https://www.jpost.com/MiddleEast/Article.aspx?id=235650 # http://www.worldtimezone.com/dst_news/dst_news_gazastrip05.html # The rules for Egypt are stolen from the 'africa' file. # From Steffen Thorsen (2011-09-30): # West Bank did end Daylight Saving Time this morning/midnight (2011-09-30 # 00:00). # So West Bank and Gaza now have the same time again. # # Many sources, including: # http://www.maannews.net/eng/ViewDetails.aspx?ID=424808 # From Steffen Thorsen (2012-03-26): # Palestinian news sources tell that both Gaza and West Bank will start DST # on Friday (Thursday midnight, 2012-03-29 24:00). # Some of many sources in Arabic: # http://www.samanews.com/index.php?act=Show&id=122638 # # http://safa.ps/details/news/74352/بدء-التوقيت-الصيفي-بالضفة-وغزة-ليلة-الجمعة.html # # Our brief summary: # https://www.timeanddate.com/news/time/gaza-west-bank-dst-2012.html # From Steffen Thorsen (2013-03-26): # The following news sources tells that Palestine will "start daylight saving # time from midnight on Friday, March 29, 2013" (translated). # [These are in Arabic and are for Gaza and for Ramallah, respectively.] # http://www.samanews.com/index.php?act=Show&id=154120 # http://safa.ps/details/news/99844/رام-الله-بدء-التوقيت-الصيفي-29-الجاري.html # From Steffen Thorsen (2013-09-24): # The Gaza and West Bank are ending DST Thursday at midnight # (2013-09-27 00:00:00) (one hour earlier than last year...). # This source in English, says "that winter time will go into effect # at midnight on Thursday in the West Bank and Gaza Strip": # http://english.wafa.ps/index.php?action=detail&id=23246 # official source...: # http://www.palestinecabinet.gov.ps/ar/Views/ViewDetails.aspx?pid=1252 # From Steffen Thorsen (2015-03-03): # Sources such as http://www.alquds.com/news/article/view/id/548257 # and https://www.raya.ps/ar/news/890705.html say Palestine areas will # start DST on 2015-03-28 00:00 which is one day later than expected. # # From Paul Eggert (2015-03-03): # https://www.timeanddate.com/time/change/west-bank/ramallah?year=2014 # says that the fall 2014 transition was Oct 23 at 24:00. # From Hannah Kreitem (2016-03-09): # http://www.palestinecabinet.gov.ps/WebSite/ar/ViewDetails?ID=31728 # [Google translation]: "The Council also decided to start daylight # saving in Palestine as of one o'clock on Saturday morning, # 2016-03-26, to provide the clock 60 minutes ahead." # From Sharef Mustafa (2016-10-19): # [T]he Palestinian cabinet decision (Mar 8th 2016) published on # http://www.palestinecabinet.gov.ps/WebSite/Upload/Decree/GOV_17/16032016134830.pdf # states that summer time will end on Oct 29th at 01:00. # From Sharef Mustafa (2018-03-16): # Palestine summer time will start on Mar 24th 2018 ... # http://www.palestinecabinet.gov.ps/Website/AR/NDecrees/ViewFile.ashx?ID=e7a42ab7-ee23-435a-b9c8-a4f7e81f3817 # From Even Scharning (2019-03-23): # http://pnn.ps/news/401130 # http://palweather.ps/ar/node/50136.html # # From Sharif Mustafa (2019-03-26): # The Palestinian cabinet announced today that the switch to DST will # be on Fri Mar 29th 2019 by advancing the clock by 60 minutes. # http://palestinecabinet.gov.ps/Website/AR/NDecrees/ViewFile.ashx?ID=e54e9ea1-50ee-4137-84df-0d6c78da259b # # From Even Scharning (2019-04-10): # Our source in Palestine said it happened Friday 29 at 00:00 local time.... # From Sharef Mustafa (2019-10-18): # Palestine summer time will end on midnight Oct 26th 2019 ... # # From Steffen Thorsen (2020-10-20): # Some sources such as these say, and display on clocks, that DST ended at # midnight last year... # https://www.amad.ps/ar/post/320006 # # From Tim Parenti (2020-10-20): # The report of the Palestinian Cabinet meeting of 2019-10-14 confirms # a decision on (translated): "The start of the winter time in Palestine, by # delaying the clock by sixty minutes, starting from midnight on Friday / # Saturday corresponding to 26/10/2019." # http://www.palestinecabinet.gov.ps/portal/meeting/details/43948 # From Sharef Mustafa (2020-10-20): # As per the palestinian cabinet announcement yesterday , the day light saving # shall [end] on Oct 24th 2020 at 01:00AM by delaying the clock by 60 minutes. # http://www.palestinecabinet.gov.ps/portal/Meeting/Details/51584 # From Pierre Cashon (2020-10-20): # The summer time this year started on March 28 at 00:00. # https://wafa.ps/ar_page.aspx?id=GveQNZa872839351758aGveQNZ # http://www.palestinecabinet.gov.ps/portal/meeting/details/50284 # The winter time in 2015 started on October 23 at 01:00. # https://wafa.ps/ar_page.aspx?id=CgpCdYa670694628582aCgpCdY # http://www.palestinecabinet.gov.ps/portal/meeting/details/27583 # From P Chan (2021-10-18): # http://wafa.ps/Pages/Details/34701 # Palestine winter time will start from midnight 2021-10-29 (Thursday-Friday). # # From Heba Hemad, Palestine Ministry of Telecom & IT (2021-10-20): # ... winter time will begin in Palestine from Friday 10-29, 01:00 AM # by 60 minutes backwards. # # From Tim Parenti (2021-10-25), per Paul Eggert (2021-10-24): # Guess future fall transitions at 01:00 on the Friday preceding October's # last Sunday (i.e., Fri>=23), as this is more consistent with recent practice. # From Heba Hamad (2022-03-10): # summer time will begin in Palestine from Sunday 03-27-2022, 00:00 AM. # From Heba Hamad (2022-08-30): # winter time will begin in Palestine from Saturday 10-29, 02:00 AM by # 60 minutes backwards. Also the state of Palestine adopted the summer # and winter time for the years: 2023,2024,2025,2026 ... # https://mm.icann.org/pipermail/tz/attachments/20220830/9f024566/Time-0001.pdf # (2022-08-31): ... the Saturday before the last Sunday in March and October # at 2:00 AM ,for the years from 2023 to 2026. # (2022-09-05): https://mtit.pna.ps/Site/New/1453 # From Heba Hamad (2023-03-22): # ... summer time will begin in Palestine from Saturday 04-29-2023, # 02:00 AM by 60 minutes forward. # From Heba Hemad (2023-10-09): # ... winter time will begin in Palestine from Saturday 10-28-2023, # 02:00 AM by 60 minutes back. # # From Heba Hamad (2024-01-25): # the summer time for the years 2024,2025 will begin in Palestine # from Saturday at 02:00 AM by 60 minutes forward as shown below: # year date # 2024 2024-04-20 # 2025 2025-04-12 # # From Paul Eggert (2024-01-25): # For now, guess that spring and fall transitions will normally # continue to use 2022's rules, that during DST Palestine will switch # to standard time at 02:00 the last Saturday before Ramadan and back # to DST at 02:00 the second Saturday after Ramadan, and that # if the normal spring-forward or fall-back transition occurs during # Ramadan the former is delayed and the latter advanced. # To implement this, I predicted Ramadan-oriented transition dates for # 2026 through 2086 by running the following program under GNU Emacs 29.2, # with the results integrated by hand into the table below. # Predictions after 2086 are approximated without Ramadan. # # (let ((islamic-year 1447)) # (require 'cal-islam) # (while (< islamic-year 1510) # (let ((a (calendar-islamic-to-absolute (list 9 1 islamic-year))) # (b (+ 1 (calendar-islamic-to-absolute (list 10 1 islamic-year)))) # (saturday 6)) # (while (/= saturday (mod (setq a (1- a)) 7))) # (while (/= saturday (mod b 7)) # (setq b (1+ b))) # (setq b (+ 7 b)) # (setq a (calendar-gregorian-from-absolute a)) # (setq b (calendar-gregorian-from-absolute b)) # (insert # (format # (concat "Rule Palestine\t%d\tonly\t-\t%s\t%2d\t2:00\t0\t-\n" # "Rule Palestine\t%d\tonly\t-\t%s\t%2d\t2:00\t1:00\tS\n") # (car (cdr (cdr a))) (calendar-month-name (car a) t) (car (cdr a)) # (car (cdr (cdr b))) (calendar-month-name (car b) t) (car (cdr b))))) # (setq islamic-year (+ 1 islamic-year)))) # Rule NAME FROM TO - IN ON AT SAVE LETTER/S Rule EgyptAsia 1957 only - May 10 0:00 1:00 S Rule EgyptAsia 1957 1958 - Oct 1 0:00 0 - Rule EgyptAsia 1958 only - May 1 0:00 1:00 S Rule EgyptAsia 1959 1967 - May 1 1:00 1:00 S Rule EgyptAsia 1959 1965 - Sep 30 3:00 0 - Rule EgyptAsia 1966 only - Oct 1 3:00 0 - Rule Palestine 1999 2005 - Apr Fri>=15 0:00 1:00 S Rule Palestine 1999 2003 - Oct Fri>=15 0:00 0 - Rule Palestine 2004 only - Oct 1 1:00 0 - Rule Palestine 2005 only - Oct 4 2:00 0 - Rule Palestine 2006 2007 - Apr 1 0:00 1:00 S Rule Palestine 2006 only - Sep 22 0:00 0 - Rule Palestine 2007 only - Sep 13 2:00 0 - Rule Palestine 2008 2009 - Mar lastFri 0:00 1:00 S Rule Palestine 2008 only - Sep 1 0:00 0 - Rule Palestine 2009 only - Sep 4 1:00 0 - Rule Palestine 2010 only - Mar 26 0:00 1:00 S Rule Palestine 2010 only - Aug 11 0:00 0 - Rule Palestine 2011 only - Apr 1 0:01 1:00 S Rule Palestine 2011 only - Aug 1 0:00 0 - Rule Palestine 2011 only - Aug 30 0:00 1:00 S Rule Palestine 2011 only - Sep 30 0:00 0 - Rule Palestine 2012 2014 - Mar lastThu 24:00 1:00 S Rule Palestine 2012 only - Sep 21 1:00 0 - Rule Palestine 2013 only - Sep 27 0:00 0 - Rule Palestine 2014 only - Oct 24 0:00 0 - Rule Palestine 2015 only - Mar 28 0:00 1:00 S Rule Palestine 2015 only - Oct 23 1:00 0 - Rule Palestine 2016 2018 - Mar Sat<=30 1:00 1:00 S Rule Palestine 2016 2018 - Oct Sat<=30 1:00 0 - Rule Palestine 2019 only - Mar 29 0:00 1:00 S Rule Palestine 2019 only - Oct Sat<=30 0:00 0 - Rule Palestine 2020 2021 - Mar Sat<=30 0:00 1:00 S Rule Palestine 2020 only - Oct 24 1:00 0 - Rule Palestine 2021 only - Oct 29 1:00 0 - Rule Palestine 2022 only - Mar 27 0:00 1:00 S Rule Palestine 2022 2035 - Oct Sat<=30 2:00 0 - Rule Palestine 2023 only - Apr 29 2:00 1:00 S Rule Palestine 2024 only - Apr 20 2:00 1:00 S Rule Palestine 2025 only - Apr 12 2:00 1:00 S Rule Palestine 2026 2054 - Mar Sat<=30 2:00 1:00 S Rule Palestine 2036 only - Oct 18 2:00 0 - Rule Palestine 2037 only - Oct 10 2:00 0 - Rule Palestine 2038 only - Sep 25 2:00 0 - Rule Palestine 2039 only - Sep 17 2:00 0 - Rule Palestine 2040 only - Sep 1 2:00 0 - Rule Palestine 2040 only - Oct 20 2:00 1:00 S Rule Palestine 2040 2067 - Oct Sat<=30 2:00 0 - Rule Palestine 2041 only - Aug 24 2:00 0 - Rule Palestine 2041 only - Oct 5 2:00 1:00 S Rule Palestine 2042 only - Aug 16 2:00 0 - Rule Palestine 2042 only - Sep 27 2:00 1:00 S Rule Palestine 2043 only - Aug 1 2:00 0 - Rule Palestine 2043 only - Sep 19 2:00 1:00 S Rule Palestine 2044 only - Jul 23 2:00 0 - Rule Palestine 2044 only - Sep 3 2:00 1:00 S Rule Palestine 2045 only - Jul 15 2:00 0 - Rule Palestine 2045 only - Aug 26 2:00 1:00 S Rule Palestine 2046 only - Jun 30 2:00 0 - Rule Palestine 2046 only - Aug 18 2:00 1:00 S Rule Palestine 2047 only - Jun 22 2:00 0 - Rule Palestine 2047 only - Aug 3 2:00 1:00 S Rule Palestine 2048 only - Jun 6 2:00 0 - Rule Palestine 2048 only - Jul 25 2:00 1:00 S Rule Palestine 2049 only - May 29 2:00 0 - Rule Palestine 2049 only - Jul 10 2:00 1:00 S Rule Palestine 2050 only - May 21 2:00 0 - Rule Palestine 2050 only - Jul 2 2:00 1:00 S Rule Palestine 2051 only - May 6 2:00 0 - Rule Palestine 2051 only - Jun 24 2:00 1:00 S Rule Palestine 2052 only - Apr 27 2:00 0 - Rule Palestine 2052 only - Jun 8 2:00 1:00 S Rule Palestine 2053 only - Apr 12 2:00 0 - Rule Palestine 2053 only - May 31 2:00 1:00 S Rule Palestine 2054 only - Apr 4 2:00 0 - Rule Palestine 2054 only - May 23 2:00 1:00 S Rule Palestine 2055 only - May 8 2:00 1:00 S Rule Palestine 2056 only - Apr 29 2:00 1:00 S Rule Palestine 2057 only - Apr 14 2:00 1:00 S Rule Palestine 2058 only - Apr 6 2:00 1:00 S Rule Palestine 2059 max - Mar Sat<=30 2:00 1:00 S Rule Palestine 2068 only - Oct 20 2:00 0 - Rule Palestine 2069 only - Oct 12 2:00 0 - Rule Palestine 2070 only - Oct 4 2:00 0 - Rule Palestine 2071 only - Sep 19 2:00 0 - Rule Palestine 2072 only - Sep 10 2:00 0 - Rule Palestine 2072 only - Oct 22 2:00 1:00 S Rule Palestine 2072 max - Oct Sat<=30 2:00 0 - Rule Palestine 2073 only - Sep 2 2:00 0 - Rule Palestine 2073 only - Oct 14 2:00 1:00 S Rule Palestine 2074 only - Aug 18 2:00 0 - Rule Palestine 2074 only - Oct 6 2:00 1:00 S Rule Palestine 2075 only - Aug 10 2:00 0 - Rule Palestine 2075 only - Sep 21 2:00 1:00 S Rule Palestine 2076 only - Jul 25 2:00 0 - Rule Palestine 2076 only - Sep 12 2:00 1:00 S Rule Palestine 2077 only - Jul 17 2:00 0 - Rule Palestine 2077 only - Sep 4 2:00 1:00 S Rule Palestine 2078 only - Jul 9 2:00 0 - Rule Palestine 2078 only - Aug 20 2:00 1:00 S Rule Palestine 2079 only - Jun 24 2:00 0 - Rule Palestine 2079 only - Aug 12 2:00 1:00 S Rule Palestine 2080 only - Jun 15 2:00 0 - Rule Palestine 2080 only - Jul 27 2:00 1:00 S Rule Palestine 2081 only - Jun 7 2:00 0 - Rule Palestine 2081 only - Jul 19 2:00 1:00 S Rule Palestine 2082 only - May 23 2:00 0 - Rule Palestine 2082 only - Jul 11 2:00 1:00 S Rule Palestine 2083 only - May 15 2:00 0 - Rule Palestine 2083 only - Jun 26 2:00 1:00 S Rule Palestine 2084 only - Apr 29 2:00 0 - Rule Palestine 2084 only - Jun 17 2:00 1:00 S Rule Palestine 2085 only - Apr 21 2:00 0 - Rule Palestine 2085 only - Jun 9 2:00 1:00 S Rule Palestine 2086 only - Apr 13 2:00 0 - Rule Palestine 2086 only - May 25 2:00 1:00 S # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone Asia/Gaza 2:17:52 - LMT 1900 Oct 2:00 Zion EET/EEST 1948 May 15 2:00 EgyptAsia EE%sT 1967 Jun 5 2:00 Zion I%sT 1996 2:00 Jordan EE%sT 1999 2:00 Palestine EE%sT 2008 Aug 29 0:00 2:00 - EET 2008 Sep 2:00 Palestine EE%sT 2010 2:00 - EET 2010 Mar 27 0:01 2:00 Palestine EE%sT 2011 Aug 1 2:00 - EET 2012 2:00 Palestine EE%sT Zone Asia/Hebron 2:20:23 - LMT 1900 Oct 2:00 Zion EET/EEST 1948 May 15 2:00 EgyptAsia EE%sT 1967 Jun 5 2:00 Zion I%sT 1996 2:00 Jordan EE%sT 1999 2:00 Palestine EE%sT # Paracel Is # no information # Philippines # From Paul Eggert (2024-01-21): # The Spanish initially used American (west-of-Greenwich) time. # It is unknown what time Manila kept when the British occupied it from # 1762-10-06 through 1764-04; for now assume it kept American time. # On 1844-08-16, Narciso Clavería, governor-general of the # Philippines, issued a proclamation announcing that 1844-12-30 was to # be immediately followed by 1845-01-01; see R.H. van Gent's # History of the International Date Line # https://webspace.science.uu.nl/~gent0113/idl/idl_philippines.htm # From P Chan (2021-05-10): # Here's a fairly comprehensive article in Japanese: # https://wiki.suikawiki.org/n/Philippine%20Time # (2021-05-16): # According to the references listed in the article, # the periods that the Philippines (Manila) observed DST or used +9 are: # # 1936-10-31 24:00 to 1937-01-15 24:00 # (Proclamation No. 104, Proclamation No. 126) # 1941-12-15 24:00 to 1945-11-30 24:00 # (Proclamation No. 789, Proclamation No. 20) # 1954-04-11 24:00 to 1954-06-04 24:00 # (Proclamation No. 13, Proclamation No. 33) # 1977-03-27 24:00 to 1977-09-21 24:00 # (Proclamation No. 1629, Proclamation No. 1641) # 1990-05-21 00:00 to 1990-07-28 24:00 # (National Emergency Memorandum Order No. 17, Executive Order No. 415) # # Proclamation No. 104 ... October 30, 1936 # https://www.officialgazette.gov.ph/1936/10/30/proclamation-no-104-s-1936/ # Proclamation No. 126 ... January 15, 1937 # https://www.officialgazette.gov.ph/1937/01/15/proclamation-no-126-s-1937/ # Proclamation No. 789 ... December 13, 1941 # https://www.officialgazette.gov.ph/1941/12/13/proclamation-no-789-s-1941/ # Proclamation No. 20 ... November 11, 1945 # https://www.officialgazette.gov.ph/1945/11/11/proclamation-no-20-s-1945/ # Proclamation No. 13 ... April 6, 1954 # https://www.officialgazette.gov.ph/1954/04/06/proclamation-no-13-s-1954/ # Proclamation No. 33 ... June 3, 1954 # https://www.officialgazette.gov.ph/1954/06/03/proclamation-no-33-s-1954/ # Proclamation No. 1629 ... March 25, 1977 # https://www.officialgazette.gov.ph/1977/03/25/proclamation-no-1629-s-1977/ # Proclamation No. 1641 ...May 26, 1977 # https://www.officialgazette.gov.ph/1977/05/26/proclamation-no-1641-s-1977/ # National Emergency Memorandum Order No. 17 ... May 2, 1990 # https://www.officialgazette.gov.ph/1990/05/02/national-emergency-memorandum-order-no-17-s-1990/ # Executive Order No. 415 ... July 20, 1990 # https://www.officialgazette.gov.ph/1990/07/20/executive-order-no-415-s-1990/ # # During WWII, Proclamation No. 789 fixed two periods of DST. The first period # was set to continue only until January 31, 1942. But Manila was occupied by # the Japanese earlier in the month.... # # For the date of the adoption of standard time, Shank[s] gives 1899-05-11. # The article is not able to state the basis of that. I guess it was based on # a US War Department Circular issued on that date. # https://books.google.com/books?id=JZ1PAAAAYAAJ&pg=RA3-PA8 # # However, according to other sources, standard time was adopted on # 1899-09-06. Also, the LMT was GMT+8:03:52 # https://books.google.com/books?id=MOYIAQAAIAAJ&pg=PA521 # https://books.google.com/books?id=lSnqqatpYikC&pg=PA21 # # From Paul Eggert (2024-09-05): # The penultimate URL in P Chan's email refers to page 521 of # Selga M, The Time Service in the Philippines. # Proc Pan-Pacific Science Congress. Vol. 1 (1923), 519-532. # It says, "The change from the meridian 120° 58' 04" to the 120th implied a # change of 3 min. 52s.26 in time; consequently on 6th September, 1899, # Manila Observatory gave the noon signal 3 min. 52s.26 later than before". # # Wikipedia says the US declared Manila liberated on March 4, 1945; # this doesn't affect clocks, just our time zone abbreviation and DST flag. # From Paul Goyette (2018-06-15) with URLs updated by Guy Harris (2024-02-15): # In the Philippines, there is a national law, Republic Act No. 10535 # which declares the official time here as "Philippine Standard Time". # The act [1] even specifies use of PST as the abbreviation, although # the FAQ provided by PAGASA [2] uses the "acronym PhST to distinguish # it from the Pacific Standard Time (PST)." # [1] https://www.officialgazette.gov.ph/2013/05/15/republic-act-no-10535/ # [2] https://prsd.pagasa.dost.gov.ph/index.php/28-astronomy/302-philippine-standard-time # # From Paul Eggert (2018-06-19): # I surveyed recent news reports, and my impression is that "PST" is # more popular among reliable English-language news sources. This is # not just a measure of Google hit counts: it's also the sizes and # influence of the sources. There is no current abbreviation for DST, # so use "PDT", the usual American style. # Rule NAME FROM TO - IN ON AT SAVE LETTER/S Rule Phil 1936 only - Oct 31 24:00 1:00 D Rule Phil 1937 only - Jan 15 24:00 0 S Rule Phil 1941 only - Dec 15 24:00 1:00 D # The following three rules were canceled by Japan: #Rule Phil 1942 only - Jan 31 24:00 0 S #Rule Phil 1942 only - Mar 1 0:00 1:00 D #Rule Phil 1942 only - Jun 30 24:00 0 S Rule Phil 1945 only - Nov 30 24:00 0 S Rule Phil 1954 only - Apr 11 24:00 1:00 D Rule Phil 1954 only - Jun 4 24:00 0 S Rule Phil 1977 only - Mar 27 24:00 1:00 D Rule Phil 1977 only - Sep 21 24:00 0 S Rule Phil 1990 only - May 21 0:00 1:00 D Rule Phil 1990 only - Jul 28 24:00 0 S # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone Asia/Manila -15:56:08 - LMT 1844 Dec 31 8:03:52 - LMT 1899 Sep 6 4:00u 8:00 Phil P%sT 1942 Feb 11 24:00 9:00 - JST 1945 Mar 4 8:00 Phil P%sT # Bahrain # Qatar # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone Asia/Qatar 3:26:08 - LMT 1920 # Al Dawhah / Doha 4:00 - %z 1972 Jun 3:00 - %z # Kuwait # Saudi Arabia # Yemen # # Japan's year-round bases in Antarctica match this since 1970. # # From Paul Eggert (2018-08-29): # Time in Saudi Arabia and other countries in the Arabian peninsula was not # standardized until 1968 or so; we don't know exactly when, and possibly it # has never been made official. Richard P Hunt, in "Islam city yielding to # modern times", New York Times (1961-04-09), p 20, wrote that only airlines # observed standard time, and that people in Jeddah mostly observed quasi-solar # time, doing so by setting their watches at sunrise to 6 o'clock (or to 12 # o'clock for "Arab" time). # # Timekeeping differed depending on who you were and which part of Saudi # Arabia you were in. In 1969, Elias Antar wrote that although a common # practice had been to set one's watch to 12:00 (i.e., midnight) at sunset - # which meant that the time on one side of a mountain could differ greatly from # the time on the other side - many foreigners set their watches to 6pm # instead, while airlines instead used UTC +03 (except in Dhahran, where they # used UTC +04), Aramco used UTC +03 with DST, and the Trans-Arabian Pipe Line # Company used Aramco time in eastern Saudi Arabia and airline time in western. # (The American Military Aid Advisory Group used plain UTC.) Antar writes, # "A man named Higgins, so the story goes, used to run a local power # station. One day, the whole thing became too much for Higgins and he # assembled his staff and laid down the law. 'I've had enough of this,' he # shrieked. 'It is now 12 o'clock Higgins Time, and from now on this station is # going to run on Higgins Time.' And so, until last year, it did." See: # Antar E. Dinner at When? Saudi Aramco World, 1969 March/April. 2-3. # http://archive.aramcoworld.com/issue/196902/dinner.at.when.htm # Also see: Antar EN. Arabian flying is confusing. # Port Angeles (WA) Evening News. 1965-03-10. page 3. # # The TZ database cannot represent quasi-solar time; airline time is the best # we can do. The 1946 foreign air news digest of the U.S. Civil Aeronautics # Board (OCLC 42299995) reported that the "... Arabian Government, inaugurated # a weekly Dhahran-Cairo service, via the Saudi Arabian cities of Riyadh and # Jidda, on March 14, 1947". Shanks & Pottenger guessed 1950; go with the # earlier date. # # Shanks & Pottenger also state that until 1968-05-01 Saudi Arabia had two # time zones; the other zone, at UT +04, was in the far eastern part of # the country. Presumably this is documenting airline time. Ignore this, # as it's before our 1970 cutoff. # # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone Asia/Riyadh 3:06:52 - LMT 1947 Mar 14 3:00 - %z # Singapore # taken from Mok Ly Yng (2003-10-30) # https://web.archive.org/web/20190822231045/http://www.math.nus.edu.sg/~mathelmr/teaching/timezone.html # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone Asia/Singapore 6:55:25 - LMT 1901 Jan 1 6:55:25 - SMT 1905 Jun 1 # Singapore M.T. 7:00 - %z 1933 Jan 1 7:00 0:20 %z 1936 Jan 1 7:20 - %z 1941 Sep 1 7:30 - %z 1942 Feb 16 9:00 - %z 1945 Sep 12 7:30 - %z 1981 Dec 31 16:00u 8:00 - %z # Spratly Is # no information # Sri Lanka # From Paul Eggert (2013-02-21): # Milne says "Madras mean time use from May 1, 1898. Prior to this Colombo # mean time, 5h. 4m. 21.9s. F., was used." But 5:04:21.9 differs considerably # from Colombo's meridian 5:19:24, so for now ignore Milne and stick with # Shanks and Pottenger. # From Paul Eggert (1996-09-03): # "Sri Lanka advances clock by an hour to avoid blackout" # (, 1996-05-24, # no longer available as of 1999-08-17) # reported "the country's standard time will be put forward by one hour at # midnight Friday (1830 GMT) 'in the light of the present power crisis'." # # From Dharmasiri Senanayake, Sri Lanka Media Minister (1996-10-24), as quoted # by Shamindra in Daily News - Hot News Section # (1996-10-26): # With effect from 12.30 a.m. on 26th October 1996 # Sri Lanka will be six (06) hours ahead of GMT. # From Jesper Nørgaard Welen (2006-04-14), quoting Sri Lanka News Online # (2006-04-13): # 0030 hrs on April 15, 2006 (midnight of April 14, 2006 +30 minutes) # at present, become 2400 hours of April 14, 2006 (midnight of April 14, 2006). # From Peter Apps and Ranga Sirila of Reuters (2006-04-12) in: # http://today.reuters.co.uk/news/newsArticle.aspx?type=scienceNews&storyID=2006-04-12T172228Z_01_COL295762_RTRIDST_0_SCIENCE-SRILANKA-TIME-DC.XML # [The Tamil Tigers] never accepted the original 1996 time change and simply # kept their clocks set five and a half hours ahead of Greenwich Mean # Time (GMT), in line with neighbor India. # From Paul Eggert (2006-04-18): # People who live in regions under Tamil control can use [TZ='Asia/Kolkata'], # as that zone has agreed with the Tamil areas since our cutoff date of 1970. # From Sadika Sumanapala (2016-10-19): # According to http://www.sltime.org (maintained by Measurement Units, # Standards & Services Department, Sri Lanka) abbreviation for Sri Lanka # standard time is SLST. # # From Paul Eggert (2016-10-18): # "SLST" seems to be reasonably recent and rarely used outside time # zone nerd sources. I searched Google News and found three uses of # it in the International Business Times of India in February and # March of this year when discussing cricket match times, but nothing # since then (though there has been a lot of cricket) and nothing in # other English-language news sources. Our old abbreviation "LKT" is # even worse. For now, let's use a numeric abbreviation; we can # switch to "SLST" if it catches on. # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone Asia/Colombo 5:19:24 - LMT 1880 5:19:32 - MMT 1906 # Moratuwa Mean Time 5:30 - %z 1942 Jan 5 5:30 0:30 %z 1942 Sep 5:30 1:00 %z 1945 Oct 16 2:00 5:30 - %z 1996 May 25 0:00 6:30 - %z 1996 Oct 26 0:30 6:00 - %z 2006 Apr 15 0:30 5:30 - %z # Syria # Rule NAME FROM TO - IN ON AT SAVE LETTER/S Rule Syria 1920 1923 - Apr Sun>=15 2:00 1:00 S Rule Syria 1920 1923 - Oct Sun>=1 2:00 0 - Rule Syria 1962 only - Apr 29 2:00 1:00 S Rule Syria 1962 only - Oct 1 2:00 0 - Rule Syria 1963 1965 - May 1 2:00 1:00 S Rule Syria 1963 only - Sep 30 2:00 0 - Rule Syria 1964 only - Oct 1 2:00 0 - Rule Syria 1965 only - Sep 30 2:00 0 - Rule Syria 1966 only - Apr 24 2:00 1:00 S Rule Syria 1966 1976 - Oct 1 2:00 0 - Rule Syria 1967 1978 - May 1 2:00 1:00 S Rule Syria 1977 1978 - Sep 1 2:00 0 - Rule Syria 1983 1984 - Apr 9 2:00 1:00 S Rule Syria 1983 1984 - Oct 1 2:00 0 - Rule Syria 1986 only - Feb 16 2:00 1:00 S Rule Syria 1986 only - Oct 9 2:00 0 - Rule Syria 1987 only - Mar 1 2:00 1:00 S Rule Syria 1987 1988 - Oct 31 2:00 0 - Rule Syria 1988 only - Mar 15 2:00 1:00 S Rule Syria 1989 only - Mar 31 2:00 1:00 S Rule Syria 1989 only - Oct 1 2:00 0 - Rule Syria 1990 only - Apr 1 2:00 1:00 S Rule Syria 1990 only - Sep 30 2:00 0 - Rule Syria 1991 only - Apr 1 0:00 1:00 S Rule Syria 1991 1992 - Oct 1 0:00 0 - Rule Syria 1992 only - Apr 8 0:00 1:00 S Rule Syria 1993 only - Mar 26 0:00 1:00 S Rule Syria 1993 only - Sep 25 0:00 0 - # IATA SSIM (1998-02) says 1998-04-02; # (1998-09) says 1999-03-29 and 1999-09-29; (1999-02) says 1999-04-02, # 2000-04-02, and 2001-04-02; (1999-09) says 2000-03-31 and 2001-03-31; # (2006) says 2006-03-31 and 2006-09-22; # for now ignore all these claims and go with Shanks & Pottenger, # except for the 2006-09-22 claim (which seems right for Ramadan). Rule Syria 1994 1996 - Apr 1 0:00 1:00 S Rule Syria 1994 2005 - Oct 1 0:00 0 - Rule Syria 1997 1998 - Mar lastMon 0:00 1:00 S Rule Syria 1999 2006 - Apr 1 0:00 1:00 S # From Stephen Colebourne (2006-09-18): # According to IATA data, Syria will change DST on 21st September [21:00 UTC] # this year [only].... This is probably related to Ramadan, like Egypt. Rule Syria 2006 only - Sep 22 0:00 0 - # From Paul Eggert (2007-03-29): # Today the AP reported "Syria will switch to summertime at midnight Thursday." # http://www.iht.com/articles/ap/2007/03/29/africa/ME-GEN-Syria-Time-Change.php Rule Syria 2007 only - Mar lastFri 0:00 1:00 S # From Jesper Nørgaard (2007-10-27): # The sister center ICARDA of my work CIMMYT is confirming that Syria DST will # not take place 1st November at 0:00 o'clock but 1st November at 24:00 or # rather Midnight between Thursday and Friday. This does make more sense than # having it between Wednesday and Thursday (two workdays in Syria) since the # weekend in Syria is not Saturday and Sunday, but Friday and Saturday. So now # it is implemented at midnight of the last workday before weekend... # # From Steffen Thorsen (2007-10-27): # Jesper Nørgaard Welen wrote: # # > "Winter local time in Syria will be observed at midnight of Thursday 1 # > November 2007, and the clock will be put back 1 hour." # # I found confirmation on this in this gov.sy-article (Arabic): # http://wehda.alwehda.gov.sy/_print_veiw.asp?FileName=12521710520070926111247 # # which using Google's translate tools says: # Council of Ministers also approved the commencement of work on # identifying the winter time as of Friday, 2/11/2007 where the 60th # minute delay at midnight Thursday 1/11/2007. Rule Syria 2007 only - Nov Fri>=1 0:00 0 - # From Stephen Colebourne (2008-03-17): # For everyone's info, I saw an IATA time zone change for [Syria] for # this month (March 2008) in the last day or so.... # Country Time Standard --- DST Start --- --- DST End --- DST # Name Zone Variation Time Date Time Date # Variation # Syrian Arab # Republic SY +0200 2200 03APR08 2100 30SEP08 +0300 # 2200 02APR09 2100 30SEP09 +0300 # 2200 01APR10 2100 30SEP10 +0300 # From Arthur David Olson (2008-03-17): # Here's a link to English-language coverage by the Syrian Arab News # Agency (SANA)... # http://www.sana.sy/eng/21/2008/03/11/165173.htm # ...which reads (in part) "The Cabinet approved the suggestion of the # Ministry of Electricity to begin daylight savings time on Friday April # 4th, advancing clocks one hour ahead on midnight of Thursday April 3rd." # Since Syria is two hours east of UTC, the 2200 and 2100 transition times # shown above match up with midnight in Syria. # From Arthur David Olson (2008-03-18): # My best guess at a Syrian rule is "the Friday nearest April 1"; # coding that involves either using a "Mar Fri>=29" construct that old time zone # compilers can't handle or having multiple Rules (a la Israel). # For now, use "Apr Fri>=1", and go with IATA on a uniform Sep 30 end. # From Steffen Thorsen (2008-10-07): # Syria has now officially decided to end DST on 2008-11-01 this year, # according to the following article in the Syrian Arab News Agency (SANA). # # The article is in Arabic, and seems to tell that they will go back to # winter time on 2008-11-01 at 00:00 local daylight time (delaying/setting # clocks back 60 minutes). # # http://sana.sy/ara/2/2008/10/07/195459.htm # From Steffen Thorsen (2009-03-19): # Syria will start DST on 2009-03-27 00:00 this year according to many sources, # two examples: # # http://www.sana.sy/eng/21/2009/03/17/217563.htm # (English, Syrian Arab News # Agency) # http://thawra.alwehda.gov.sy/_View_news2.asp?FileName=94459258720090318012209 # (Arabic, gov-site) # # We have not found any sources saying anything about when DST ends this year. # # Our summary # https://www.timeanddate.com/news/time/syria-dst-starts-march-27-2009.html # From Steffen Thorsen (2009-10-27): # The Syrian Arab News Network on 2009-09-29 reported that Syria will # revert back to winter (standard) time on midnight between Thursday # 2009-10-29 and Friday 2009-10-30: # http://www.sana.sy/ara/2/2009/09/29/247012.htm (Arabic) # From Arthur David Olson (2009-10-28): # We'll see if future DST switching times turn out to be end of the last # Thursday of the month or the start of the last Friday of the month or # something else. For now, use the start of the last Friday. # From Steffen Thorsen (2010-03-17): # The "Syrian News Station" reported on 2010-03-16 that the Council of # Ministers has decided that Syria will start DST on midnight Thursday # 2010-04-01: (midnight between Thursday and Friday): # http://sns.sy/sns/?path=news/read/11421 (Arabic) # From Steffen Thorsen (2012-03-26): # Today, Syria's government announced that they will start DST early on Friday # (00:00). This is a bit earlier than the past two years. # # From Syrian Arab News Agency, in Arabic: # http://www.sana.sy/ara/2/2012/03/26/408215.htm # # Our brief summary: # https://www.timeanddate.com/news/time/syria-dst-2012.html # From Steffen Thorsen (2022-10-05): # Syria is adopting year-round DST, starting this autumn.... # From https://www.enabbaladi.net/archives/607812 # "This [the decision] came after the weekly government meeting today, # Tuesday 4 October ..." # # From Paul Eggert (2022-10-05): # Like Jordan, model this as a transition from EEST +03 (DST) to plain +03 # (non-DST) at the point where DST would otherwise have ended. Rule Syria 2008 only - Apr Fri>=1 0:00 1:00 S Rule Syria 2008 only - Nov 1 0:00 0 - Rule Syria 2009 only - Mar lastFri 0:00 1:00 S Rule Syria 2010 2011 - Apr Fri>=1 0:00 1:00 S Rule Syria 2012 2022 - Mar lastFri 0:00 1:00 S Rule Syria 2009 2022 - Oct lastFri 0:00 0 - # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone Asia/Damascus 2:25:12 - LMT 1920 # Dimashq 2:00 Syria EE%sT 2022 Oct 28 0:00 3:00 - %z # Tajikistan # From Shanks & Pottenger. # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone Asia/Dushanbe 4:35:12 - LMT 1924 May 2 5:00 - %z 1930 Jun 21 6:00 RussiaAsia %z 1991 Mar 31 2:00s 5:00 1:00 %z 1991 Sep 9 2:00s 5:00 - %z # Cambodia # Christmas I # Laos # Thailand # Vietnam (northern) # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone Asia/Bangkok 6:42:04 - LMT 1880 6:42:04 - BMT 1920 Apr # Bangkok Mean Time 7:00 - %z # Turkmenistan # From Shanks & Pottenger. # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone Asia/Ashgabat 3:53:32 - LMT 1924 May 2 # or Ashkhabad 4:00 - %z 1930 Jun 21 5:00 RussiaAsia %z 1991 Mar 31 2:00 4:00 RussiaAsia %z 1992 Jan 19 2:00 5:00 - %z # Oman # Réunion # Seychelles # United Arab Emirates # # The Crozet Is also observe Réunion time; see the 'antarctica' file. # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone Asia/Dubai 3:41:12 - LMT 1920 4:00 - %z # Uzbekistan # Byalokoz 1919 says Uzbekistan was 4:27:53. # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone Asia/Samarkand 4:27:53 - LMT 1924 May 2 4:00 - %z 1930 Jun 21 5:00 - %z 1981 Apr 1 5:00 1:00 %z 1981 Oct 1 6:00 - %z 1982 Apr 1 5:00 RussiaAsia %z 1992 5:00 - %z # Milne says Tashkent was 4:37:10.8. #STDOFF 4:37:10.8 Zone Asia/Tashkent 4:37:11 - LMT 1924 May 2 5:00 - %z 1930 Jun 21 6:00 RussiaAsia %z 1991 Mar 31 2:00 5:00 RussiaAsia %z 1992 5:00 - %z # Vietnam (southern) # From Paul Eggert (2014-10-04): # Milne gives 7:16:56 for the meridian of Saigon in 1899, as being # used in Lower Laos, Cambodia, and Annam. But this is quite a ways # from Saigon's location. For now, ignore this and stick with Shanks # and Pottenger for LMT before 1906. # From Arthur David Olson (2008-03-18): # The English-language name of Vietnam's most populous city is "Ho Chi Minh # City"; use Ho_Chi_Minh below to avoid a name of more than 14 characters. # From Paul Eggert (2024-01-14) after a 2014 heads-up from Trần Ngọc Quân # and a 2024-01-14 heads-up from Đoàn Trần Công Danh: # Trần Tiến Bình's authoritative book "Lịch Việt Nam: thế kỷ XX-XXI (1901-2100)" # (Nhà xuất bản Văn Hoá - Thông Tin, Hanoi, 2005), pp 49-50, # is quoted verbatim in: # http://www.thoigian.com.vn/?mPage=P80D01 # is translated by Brian Inglis in: # https://mm.icann.org/pipermail/tz/2014-October/021654.html # and is the basis for the information below. # # The 1906 transition was effective July 1 and standardized Indochina to # Phù Liễn Observatory, legally 104° 17' 17" east of Paris. # It's unclear whether this meant legal Paris Mean Time (00:09:21) or # the Paris Meridian; for now guess the former and round the exact # 07:06:30.1333... to 07:06:30.13 as the legal spec used 66 2/3 ms precision. # which is used below even though the modern-day Phù Liễn Observatory # is closer to 07:06:31. Abbreviate Phù Liễn Mean Time as PLMT. # # The following transitions occurred in Indochina in general (before 1954) # and in South Vietnam in particular (after 1954): # To 07:00 on 1911-05-01. # To 08:00 on 1942-12-31 at 23:00. # To 09:00 on 1945-03-14 at 23:00. # To 07:00 on 1945-09-02 in Vietnam. # To 08:00 on 1947-04-01 in French-controlled Indochina. # To 07:00 on 1955-07-01 in South Vietnam. # To 08:00 on 1959-12-31 at 23:00 in South Vietnam. # To 07:00 on 1975-06-13 in South Vietnam. # # Trần cites the following sources; it's unclear which supplied the info above. # # Hoàng Xuân Hãn: "Lịch và lịch Việt Nam". Tập san Khoa học Xã hội, # No. 9, Paris, February 1982. # # Lê Thành Lân: "Lịch và niên biểu lịch sử hai mươi thế kỷ (0001-2010)", # NXB Thống kê, Hanoi, 2000. # # Lê Thành Lân: "Lịch hai thế kỷ (1802-2010) và các lịch vĩnh cửu", # NXB Thuận Hoá, Huế, 1995. # # Here is the decision for the September 1945 transition: # Võ Nguyên Giáp, Việt Nam Dân Quốc Công Báo, No. 1 (1945-09-29), page 13 # http://baochi.nlv.gov.vn/baochi/cgi-bin/baochi?a=d&d=JwvzO19450929.2.5&dliv=none # It says that on 1945-09-01 at 24:00, Vietnam moved back two hours, to +07. # It also mentions a 1945-03-29 decree (by a Japanese Governor-General) # to set the time zone to +09, but does not say whether that decree # merely legalized an earlier change to +09. # # July 1955 transition: # Ngô Đình Diệm, Công Báo Việt Nam, No. 92 (1955-07-02), page 1780-1781 # Ordinance (Dụ) No. 46 (1955-06-25) # http://ddsnext.crl.edu/titles/32341#?c=0&m=29&s=0&cv=4&r=0&xywh=-89%2C342%2C1724%2C1216 # It says that on 1955-07-01 at 01:00, South Vietnam moved back 1 hour (to +07). # # December 1959 transition: # Ngô Đình Diệm, Công Báo Việt Nam Cộng Hòa, 1960 part 1 (1960-01-02), page 62 # Decree (Sắc lệnh) No. 362-TTP (1959-12-30) # http://ddsnext.crl.edu/titles/32341#?c=0&m=138&s=0&cv=793&r=0&xywh=-54%2C1504%2C1705%2C1202 # It says that on 1959-12-31 at 23:00, South Vietnam moved forward 1 hour (to +08). # Zone NAME STDOFF RULES FORMAT [UNTIL] #STDOFF 7:06:30.13 Zone Asia/Ho_Chi_Minh 7:06:30 - LMT 1906 Jul 1 7:06:30 - PLMT 1911 May 1 # Phù Liễn MT 7:00 - %z 1942 Dec 31 23:00 8:00 - %z 1945 Mar 14 23:00 9:00 - %z 1945 Sep 1 24:00 7:00 - %z 1947 Apr 1 8:00 - %z 1955 Jul 1 01:00 7:00 - %z 1959 Dec 31 23:00 8:00 - %z 1975 Jun 13 7:00 - %z # From Paul Eggert (2019-02-19): # # The Ho Chi Minh entry suffices for most purposes as it agrees with all of # Vietnam since 1975-06-13. Presumably clocks often changed in south Vietnam # in the early 1970s as locations changed hands during the war; however the # details are unknown and would likely be too voluminous for this database. # # For timestamps in north Vietnam back to 1970 (the tzdb cutoff), # use Asia/Bangkok; see the VN entries in the file zone1970.tab. # For timestamps before 1970, see Asia/Hanoi in the file 'backzone'. pr0m1th3as-datatypes-9c9a8d3/inst/tzdata/australasia000066400000000000000000003004431522766574100225610ustar00rootroot00000000000000# tzdb data for Australasia and environs, and for much of the Pacific # This file is in the public domain, so clarified as of # 2009-05-17 by Arthur David Olson. # This file also includes Pacific islands. # Notes are at the end of this file ############################################################################### # Australia # Please see the notes below for the controversy about "EST" versus "AEST" etc. # Rule NAME FROM TO - IN ON AT SAVE LETTER/S Rule Aus 1917 only - Jan 1 2:00s 1:00 D Rule Aus 1917 only - Mar lastSun 2:00s 0 S Rule Aus 1942 only - Jan 1 2:00s 1:00 D Rule Aus 1942 only - Mar lastSun 2:00s 0 S Rule Aus 1942 only - Sep 27 2:00s 1:00 D Rule Aus 1943 1944 - Mar lastSun 2:00s 0 S Rule Aus 1943 only - Oct 3 2:00s 1:00 D # Zone NAME STDOFF RULES FORMAT [UNTIL] # Northern Territory Zone Australia/Darwin 8:43:20 - LMT 1895 Feb 9:00 - ACST 1899 May 9:30 Aus AC%sT # Western Australia # # Rule NAME FROM TO - IN ON AT SAVE LETTER/S Rule AW 1974 only - Oct lastSun 2:00s 1:00 D Rule AW 1975 only - Mar Sun>=1 2:00s 0 S Rule AW 1983 only - Oct lastSun 2:00s 1:00 D Rule AW 1984 only - Mar Sun>=1 2:00s 0 S Rule AW 1991 only - Nov 17 2:00s 1:00 D Rule AW 1992 only - Mar Sun>=1 2:00s 0 S Rule AW 2006 only - Dec 3 2:00s 1:00 D Rule AW 2007 2009 - Mar lastSun 2:00s 0 S Rule AW 2007 2008 - Oct lastSun 2:00s 1:00 D Zone Australia/Perth 7:43:24 - LMT 1895 Dec 8:00 Aus AW%sT 1943 Jul 8:00 AW AW%sT Zone Australia/Eucla 8:35:28 - LMT 1895 Dec 8:45 Aus %z 1943 Jul 8:45 AW %z # Queensland # # From Alex Livingston (1996-11-01): # I have heard or read more than once that some resort islands off the coast # of Queensland chose to keep observing daylight-saving time even after # Queensland ceased to. # # From Paul Eggert (1996-11-22): # IATA SSIM (1993-02/1994-09) say that the Holiday Islands (Hayman, Lindeman, # Hamilton) observed DST for two years after the rest of Queensland stopped. # Hamilton is the largest, but there is also a Hamilton in Victoria, # so use Lindeman. # # From J William Piggott (2016-02-20): # There is no location named Holiday Islands in Queensland Australia; holiday # islands is a colloquial term used globally. Hayman and Lindeman are at the # north and south extremes of the Whitsunday Islands archipelago, and # Hamilton is in between; it is reasonable to believe that this time zone # applies to all of the Whitsundays. # http://www.australia.gov.au/about-australia/australian-story/austn-islands # # Rule NAME FROM TO - IN ON AT SAVE LETTER/S Rule AQ 1971 only - Oct lastSun 2:00s 1:00 D Rule AQ 1972 only - Feb lastSun 2:00s 0 S Rule AQ 1989 1991 - Oct lastSun 2:00s 1:00 D Rule AQ 1990 1992 - Mar Sun>=1 2:00s 0 S Rule Holiday 1992 1993 - Oct lastSun 2:00s 1:00 D Rule Holiday 1993 1994 - Mar Sun>=1 2:00s 0 S Zone Australia/Brisbane 10:12:08 - LMT 1895 10:00 Aus AE%sT 1971 10:00 AQ AE%sT Zone Australia/Lindeman 9:55:56 - LMT 1895 10:00 Aus AE%sT 1971 10:00 AQ AE%sT 1992 Jul 10:00 Holiday AE%sT # South Australia # Rule NAME FROM TO - IN ON AT SAVE LETTER/S Rule AS 1971 1985 - Oct lastSun 2:00s 1:00 D Rule AS 1986 only - Oct 19 2:00s 1:00 D Rule AS 1987 2007 - Oct lastSun 2:00s 1:00 D Rule AS 1972 only - Feb 27 2:00s 0 S Rule AS 1973 1985 - Mar Sun>=1 2:00s 0 S Rule AS 1986 1990 - Mar Sun>=15 2:00s 0 S Rule AS 1991 only - Mar 3 2:00s 0 S Rule AS 1992 only - Mar 22 2:00s 0 S Rule AS 1993 only - Mar 7 2:00s 0 S Rule AS 1994 only - Mar 20 2:00s 0 S Rule AS 1995 2005 - Mar lastSun 2:00s 0 S Rule AS 2006 only - Apr 2 2:00s 0 S Rule AS 2007 only - Mar lastSun 2:00s 0 S Rule AS 2008 max - Apr Sun>=1 2:00s 0 S Rule AS 2008 max - Oct Sun>=1 2:00s 1:00 D # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone Australia/Adelaide 9:14:20 - LMT 1895 Feb 9:00 - ACST 1899 May 9:30 Aus AC%sT 1971 9:30 AS AC%sT # Tasmania # # From Paul Eggert (2005-08-16): # http://www.bom.gov.au/climate/averages/tables/dst_times.shtml # says King Island didn't observe DST from WWII until late 1971. # # Rule NAME FROM TO - IN ON AT SAVE LETTER/S Rule AT 1916 only - Oct Sun>=1 2:00s 1:00 D Rule AT 1917 only - Mar lastSun 2:00s 0 S Rule AT 1917 1918 - Oct Sun>=22 2:00s 1:00 D Rule AT 1918 1919 - Mar Sun>=1 2:00s 0 S Rule AT 1967 only - Oct Sun>=1 2:00s 1:00 D Rule AT 1968 only - Mar Sun>=29 2:00s 0 S Rule AT 1968 1985 - Oct lastSun 2:00s 1:00 D Rule AT 1969 1971 - Mar Sun>=8 2:00s 0 S Rule AT 1972 only - Feb lastSun 2:00s 0 S Rule AT 1973 1981 - Mar Sun>=1 2:00s 0 S Rule AT 1982 1983 - Mar lastSun 2:00s 0 S Rule AT 1984 1986 - Mar Sun>=1 2:00s 0 S Rule AT 1986 only - Oct Sun>=15 2:00s 1:00 D Rule AT 1987 1990 - Mar Sun>=15 2:00s 0 S Rule AT 1987 only - Oct Sun>=22 2:00s 1:00 D Rule AT 1988 1990 - Oct lastSun 2:00s 1:00 D Rule AT 1991 1999 - Oct Sun>=1 2:00s 1:00 D Rule AT 1991 2005 - Mar lastSun 2:00s 0 S Rule AT 2000 only - Aug lastSun 2:00s 1:00 D Rule AT 2001 max - Oct Sun>=1 2:00s 1:00 D Rule AT 2006 only - Apr Sun>=1 2:00s 0 S Rule AT 2007 only - Mar lastSun 2:00s 0 S Rule AT 2008 max - Apr Sun>=1 2:00s 0 S # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone Australia/Hobart 9:49:16 - LMT 1895 Sep 10:00 AT AE%sT 1919 Oct 24 10:00 Aus AE%sT 1967 10:00 AT AE%sT # Victoria # Rule NAME FROM TO - IN ON AT SAVE LETTER/S Rule AV 1971 1985 - Oct lastSun 2:00s 1:00 D Rule AV 1972 only - Feb lastSun 2:00s 0 S Rule AV 1973 1985 - Mar Sun>=1 2:00s 0 S Rule AV 1986 1990 - Mar Sun>=15 2:00s 0 S Rule AV 1986 1987 - Oct Sun>=15 2:00s 1:00 D Rule AV 1988 1999 - Oct lastSun 2:00s 1:00 D Rule AV 1991 1994 - Mar Sun>=1 2:00s 0 S Rule AV 1995 2005 - Mar lastSun 2:00s 0 S Rule AV 2000 only - Aug lastSun 2:00s 1:00 D Rule AV 2001 2007 - Oct lastSun 2:00s 1:00 D Rule AV 2006 only - Apr Sun>=1 2:00s 0 S Rule AV 2007 only - Mar lastSun 2:00s 0 S Rule AV 2008 max - Apr Sun>=1 2:00s 0 S Rule AV 2008 max - Oct Sun>=1 2:00s 1:00 D # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone Australia/Melbourne 9:39:52 - LMT 1895 Feb 10:00 Aus AE%sT 1971 10:00 AV AE%sT # New South Wales # Rule NAME FROM TO - IN ON AT SAVE LETTER/S Rule AN 1971 1985 - Oct lastSun 2:00s 1:00 D Rule AN 1972 only - Feb 27 2:00s 0 S Rule AN 1973 1981 - Mar Sun>=1 2:00s 0 S Rule AN 1982 only - Apr Sun>=1 2:00s 0 S Rule AN 1983 1985 - Mar Sun>=1 2:00s 0 S Rule AN 1986 1989 - Mar Sun>=15 2:00s 0 S Rule AN 1986 only - Oct 19 2:00s 1:00 D Rule AN 1987 1999 - Oct lastSun 2:00s 1:00 D Rule AN 1990 1995 - Mar Sun>=1 2:00s 0 S Rule AN 1996 2005 - Mar lastSun 2:00s 0 S Rule AN 2000 only - Aug lastSun 2:00s 1:00 D Rule AN 2001 2007 - Oct lastSun 2:00s 1:00 D Rule AN 2006 only - Apr Sun>=1 2:00s 0 S Rule AN 2007 only - Mar lastSun 2:00s 0 S Rule AN 2008 max - Apr Sun>=1 2:00s 0 S Rule AN 2008 max - Oct Sun>=1 2:00s 1:00 D # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone Australia/Sydney 10:04:52 - LMT 1895 Feb 10:00 Aus AE%sT 1971 10:00 AN AE%sT Zone Australia/Broken_Hill 9:25:48 - LMT 1895 Feb 10:00 - AEST 1896 Aug 23 9:00 - ACST 1899 May 9:30 Aus AC%sT 1971 9:30 AN AC%sT 2000 9:30 AS AC%sT # Lord Howe Island # Rule NAME FROM TO - IN ON AT SAVE LETTER/S Rule LH 1981 1984 - Oct lastSun 2:00 1:00 - Rule LH 1982 1985 - Mar Sun>=1 2:00 0 - Rule LH 1985 only - Oct lastSun 2:00 0:30 - Rule LH 1986 1989 - Mar Sun>=15 2:00 0 - Rule LH 1986 only - Oct 19 2:00 0:30 - Rule LH 1987 1999 - Oct lastSun 2:00 0:30 - Rule LH 1990 1995 - Mar Sun>=1 2:00 0 - Rule LH 1996 2005 - Mar lastSun 2:00 0 - Rule LH 2000 only - Aug lastSun 2:00 0:30 - Rule LH 2001 2007 - Oct lastSun 2:00 0:30 - Rule LH 2006 only - Apr Sun>=1 2:00 0 - Rule LH 2007 only - Mar lastSun 2:00 0 - Rule LH 2008 max - Apr Sun>=1 2:00 0 - Rule LH 2008 max - Oct Sun>=1 2:00 0:30 - Zone Australia/Lord_Howe 10:36:20 - LMT 1895 Feb 10:00 - AEST 1981 Mar 10:30 LH %z 1985 Jul 10:30 LH %z # Australian miscellany # # Ashmore Is, Cartier # no indigenous inhabitants; only seasonal caretakers # no times are set # # Coral Sea Is # no indigenous inhabitants; only meteorologists # no times are set # # Macquarie # Permanent occupation (scientific station) 1911-1915 and since 25 March 1948; # sealing and penguin oil station operated Nov 1899 to Apr 1919. See the # Tasmania Parks & Wildlife Service history of sealing at Macquarie Island # http://www.parks.tas.gov.au/index.aspx?base=1828 # http://www.parks.tas.gov.au/index.aspx?base=1831 # Guess that it was like Australia/Hobart while inhabited before 2010. # # From Steffen Thorsen (2010-03-10): # We got these changes from the Australian Antarctic Division: # - Macquarie Island will stay on UTC+11 for winter and therefore not # switch back from daylight savings time when other parts of Australia do # on 4 April. # # From Arthur David Olson (2013-05-23): # The 1919 transition is overspecified below so pre-2013 zics # will produce a binary file with an [A]EST-type as the first 32-bit type; # this is required for correct handling of times before 1916 by # pre-2013 versions of localtime. Zone Antarctica/Macquarie 0 - -00 1899 Nov 10:00 - AEST 1916 Oct 1 2:00 10:00 1:00 AEDT 1917 Feb 10:00 Aus AE%sT 1919 Apr 1 0:00s 0 - -00 1948 Mar 25 10:00 Aus AE%sT 1967 10:00 AT AE%sT 2010 10:00 1:00 AEDT 2011 10:00 AT AE%sT # Fiji # Milne gives 11:55:44 for Suva. # From Alexander Krivenyshev (2009-11-10): # According to Fiji Broadcasting Corporation, Fiji plans to re-introduce DST # from November 29th 2009 to April 25th 2010. # # "Daylight savings to commence this month" # http://www.radiofiji.com.fj/fullstory.php?id=23719 # http://www.worldtimezone.com/dst_news/dst_news_fiji01.html # From Steffen Thorsen (2009-11-10): # The Fiji Government has posted some more details about the approved # amendments: # http://www.fiji.gov.fj/publish/page_16198.shtml # From Steffen Thorsen (2010-03-03): # The Cabinet in Fiji has decided to end DST about a month early, on # 2010-03-28 at 03:00. # The plan is to observe DST again, from 2010-10-24 to sometime in March # 2011 (last Sunday a good guess?). # # Official source: # http://www.fiji.gov.fj/index.php?option=com_content&view=article&id=1096:3310-cabinet-approves-change-in-daylight-savings-dates&catid=49:cabinet-releases&Itemid=166 # # A bit more background info here: # https://www.timeanddate.com/news/time/fiji-dst-ends-march-2010.html # From Alexander Krivenyshev (2010-10-24): # According to Radio Fiji and Fiji Times online, Fiji will end DST 3 # weeks earlier than expected - on March 6, 2011, not March 27, 2011... # Here is confirmation from Government of the Republic of the Fiji Islands, # Ministry of Information (fiji.gov.fj) web site: # http://www.fiji.gov.fj/index.php?option=com_content&view=article&id=2608:daylight-savings&catid=71:press-releases&Itemid=155 # http://www.worldtimezone.com/dst_news/dst_news_fiji04.html # From Steffen Thorsen (2011-10-03): # Now the dates have been confirmed, and at least our start date # assumption was correct (end date was one week wrong). # # http://www.fiji.gov.fj/index.php?option=com_content&view=article&id=4966:daylight-saving-starts-in-fiji&catid=71:press-releases&Itemid=155 # which says # Members of the public are reminded to change their time to one hour in # advance at 2am to 3am on October 23, 2011 and one hour back at 3am to # 2am on February 26 next year. # From Ken Rylander (2011-10-24) # Another change to the Fiji DST end date. In the TZ database the end date for # Fiji DST 2012, is currently Feb 26. This has been changed to Jan 22. # # http://www.fiji.gov.fj/index.php?option=com_content&view=article&id=5017:amendments-to-daylight-savings&catid=71:press-releases&Itemid=155 # states: # # The end of daylight saving scheduled initially for the 26th of February 2012 # has been brought forward to the 22nd of January 2012. # The commencement of daylight saving will remain unchanged and start # on the 23rd of October, 2011. # From the Fiji Government Online Portal (2012-08-21) via Steffen Thorsen: # The Minister for Labour, Industrial Relations and Employment Mr Jone Usamate # today confirmed that Fiji will start daylight savings at 2 am on Sunday 21st # October 2012 and end at 3 am on Sunday 20th January 2013. # http://www.fiji.gov.fj/index.php?option=com_content&view=article&id=6702&catid=71&Itemid=155 # From the Fijian Government Media Center (2013-08-30) via David Wheeler: # Fiji will start daylight savings on Sunday 27th October, 2013 ... # move clocks forward by one hour from 2am # http://www.fiji.gov.fj/Media-Center/Press-Releases/DAYLIGHT-SAVING-STARTS-ON-SUNDAY,-27th-OCTOBER-201.aspx # From Steffen Thorsen (2013-01-10): # Fiji will end DST on 2014-01-19 02:00: # http://www.fiji.gov.fj/Media-Center/Press-Releases/DAYLIGHT-SAVINGS-TO-END-THIS-MONTH-(1).aspx # From Ken Rylander (2014-10-20): # DST will start Nov. 2 this year. # http://www.fiji.gov.fj/Media-Center/Press-Releases/DAYLIGHT-SAVING-STARTS-ON-SUNDAY,-NOVEMBER-2ND.aspx # From a government order dated 2015-08-26 and published as Legal Notice No. 77 # in the Government of Fiji Gazette Supplement No. 24 (2015-08-28), # via Ken Rylander (2015-09-02): # the daylight saving period is 1 hour in advance of the standard time # commencing at 2.00 am on Sunday 1st November, 2015 and ending at # 3.00 am on Sunday 17th January, 2016. # From Raymond Kumar (2016-10-04): # http://www.fiji.gov.fj/Media-Center/Press-Releases/DAYLIGHT-SAVING-STARTS-ON-6th-NOVEMBER,-2016.aspx # "Fiji's daylight savings will begin on Sunday, 6 November 2016, when # clocks go forward an hour at 2am to 3am.... Daylight Saving will # end at 3.00am on Sunday 15th January 2017." # From Paul Eggert (2017-08-21): # Dominic Fok writes (2017-08-20) that DST ends 2018-01-14, citing # Extraordinary Government of Fiji Gazette Supplement No. 21 (2017-08-27), # [Legal Notice No. 41] of an order of the previous day by J Usamate. # From Raymond Kumar (2018-07-13): # http://www.fijitimes.com/government-approves-2018-daylight-saving/ # ... The daylight saving period will end at 3am on Sunday January 13, 2019. # From Paul Eggert (2019-08-06): # Today Raymond Kumar reported the Government of Fiji Gazette Supplement No. 27 # (2019-08-02) said that Fiji observes DST "commencing at 2.00 am on # Sunday, 10 November 2019 and ending at 3.00 am on Sunday, 12 January 2020." # For now, guess DST from 02:00 the second Sunday in November to 03:00 # the first Sunday on or after January 12. January transitions reportedly # depend on when school terms start. Although the guess is ad hoc, it matches # transitions planned this year and seems more likely to match future practice # than guessing no DST. # From Michael Deckers (2019-08-06): # https://www.laws.gov.fj/LawsAsMade/downloadfile/848 # From Raymond Kumar (2020-10-08): # [DST in Fiji] is from December 20th 2020, till 17th January 2021. # From Alan Mintz (2020-10-08): # https://www.laws.gov.fj/LawsAsMade/GetFile/1071 # From Tim Parenti (2020-10-08): # https://www.fijivillage.com/news/Daylight-saving-from-Dec-20th-this-year-to-Jan-17th-2021-8rf4x5/ # "Minister for Employment, Parveen Bala says they had never thought of # stopping daylight saving. He says it was just to decide on when it should # start and end. Bala says it is a short period..." # # From Tim Parenti (2021-10-11), per Jashneel Kumar (2021-10-11) and P Chan # (2021-10-12): # https://www.fiji.gov.fj/Media-Centre/Speeches/English/PM-BAINIMARAMA-S-COVID-19-ANNOUNCEMENT-10-10-21 # https://www.fbcnews.com.fj/news/covid-19/curfew-moved-back-to-11pm/ # In a 2021-10-10 speech concerning updated Covid-19 mitigation measures in # Fiji, prime minister Josaia Voreqe "Frank" Bainimarama announced the # suspension of DST for the 2021/2022 season: "Given that we are in the process # of readjusting in the midst of so many changes, we will also put Daylight # Savings Time on hold for this year. It will also make the reopening of # scheduled commercial air service much smoother if we don't have to be # concerned shifting arrival and departure times, which may look like a simple # thing but requires some significant logistical adjustments domestically and # internationally." # From Shalvin Narayan (2022-10-27): # Please note that there will not be any daylight savings time change # in Fiji for 2022-2023.... # https://www.facebook.com/FijianGovernment/posts/pfbid0mmWVTYmTibn66ybpFda75pDcf34SSpoSaskJW5gXwaKo5Sgc7273Q4fXWc6kQV6Hl # From Almaz Mingaleev (2023-10-06): # Cabinet approved the suspension of Daylight Saving and appropriate # legislative changes will be considered including the repeal of the # Daylight Saving Act 1998 # https://www.fiji.gov.fj/Media-Centre/Speeches/English/CABINET-DECISIONS-3-OCTOBER-2023 # # From Paul Eggert (2023-10-06): # For now, assume DST is suspended indefinitely. # Rule NAME FROM TO - IN ON AT SAVE LETTER/S Rule Fiji 1998 1999 - Nov Sun>=1 2:00 1:00 - Rule Fiji 1999 2000 - Feb lastSun 3:00 0 - Rule Fiji 2009 only - Nov 29 2:00 1:00 - Rule Fiji 2010 only - Mar lastSun 3:00 0 - Rule Fiji 2010 2013 - Oct Sun>=21 2:00 1:00 - Rule Fiji 2011 only - Mar Sun>=1 3:00 0 - Rule Fiji 2012 2013 - Jan Sun>=18 3:00 0 - Rule Fiji 2014 only - Jan Sun>=18 2:00 0 - Rule Fiji 2014 2018 - Nov Sun>=1 2:00 1:00 - Rule Fiji 2015 2021 - Jan Sun>=12 3:00 0 - Rule Fiji 2019 only - Nov Sun>=8 2:00 1:00 - Rule Fiji 2020 only - Dec 20 2:00 1:00 - # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone Pacific/Fiji 11:55:44 - LMT 1915 Oct 26 # Suva 12:00 Fiji %z # French Polynesia # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone Pacific/Gambier -8:59:48 - LMT 1912 Oct 1 # Rikitea -9:00 - %z Zone Pacific/Marquesas -9:18:00 - LMT 1912 Oct 1 -9:30 - %z Zone Pacific/Tahiti -9:58:16 - LMT 1912 Oct 1 # Papeete -10:00 - %z # Clipperton (near North America) is administered from French Polynesia; # it is uninhabited. # Guam # N Mariana Is # Rule NAME FROM TO - IN ON AT SAVE LETTER/S # http://guamlegislature.com/Public_Laws_5th/PL05-025.pdf # http://documents.guam.gov/wp-content/uploads/E.O.-59-7-Guam-Daylight-Savings-Time-May-6-1959.pdf Rule Guam 1959 only - Jun 27 2:00 1:00 D # http://documents.guam.gov/wp-content/uploads/E.O.-61-5-Revocation-of-Daylight-Saving-Time-and-Restoratio.pdf Rule Guam 1961 only - Jan 29 2:00 0 S # http://documents.guam.gov/wp-content/uploads/E.O.-67-13-Guam-Daylight-Savings-Time.pdf Rule Guam 1967 only - Sep 1 2:00 1:00 D # http://documents.guam.gov/wp-content/uploads/E.O.-69-2-Repeal-of-Guam-Daylight-Saving-Time.pdf Rule Guam 1969 only - Jan 26 0:01 0 S # http://documents.guam.gov/wp-content/uploads/E.O.-69-10-Guam-Daylight-Saving-Time.pdf Rule Guam 1969 only - Jun 22 2:00 1:00 D Rule Guam 1969 only - Aug 31 2:00 0 S # http://documents.guam.gov/wp-content/uploads/E.O.-70-10-Guam-Daylight-Saving-Time.pdf # http://documents.guam.gov/wp-content/uploads/E.O.-70-30-End-of-Guam-Daylight-Saving-Time.pdf # http://documents.guam.gov/wp-content/uploads/E.O.-71-5-Guam-Daylight-Savings-Time.pdf Rule Guam 1970 1971 - Apr lastSun 2:00 1:00 D Rule Guam 1970 1971 - Sep Sun>=1 2:00 0 S # http://documents.guam.gov/wp-content/uploads/E.O.-73-28.-Guam-Day-light-Saving-Time.pdf Rule Guam 1973 only - Dec 16 2:00 1:00 D # http://documents.guam.gov/wp-content/uploads/E.O.-74-7-Guam-Daylight-Savings-Time-Rescinded.pdf Rule Guam 1974 only - Feb 24 2:00 0 S # http://documents.guam.gov/wp-content/uploads/E.O.-76-13-Daylight-Savings-Time.pdf Rule Guam 1976 only - May 26 2:00 1:00 D # http://documents.guam.gov/wp-content/uploads/E.O.-76-25-Revocation-of-E.O.-76-13.pdf Rule Guam 1976 only - Aug 22 2:01 0 S # http://documents.guam.gov/wp-content/uploads/E.O.-77-4-Daylight-Savings-Time.pdf Rule Guam 1977 only - Apr 24 2:00 1:00 D # http://documents.guam.gov/wp-content/uploads/E.O.-77-18-Guam-Standard-Time.pdf Rule Guam 1977 only - Aug 28 2:00 0 S # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone Pacific/Guam -14:21:00 - LMT 1844 Dec 31 9:39:00 - LMT 1901 # Agana 10:00 - GST 1941 Dec 10 # Guam 9:00 - %z 1944 Jul 31 10:00 Guam G%sT 2000 Dec 23 10:00 - ChST # Chamorro Standard Time # Kiribati (Gilbert Is) # Marshall Is # Tuvalu # Wake # Wallis & Futuna # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone Pacific/Tarawa 11:32:04 - LMT 1901 # Bairiki 12:00 - %z # Kiribati (except Gilbert Is) # See Pacific/Tarawa for the Gilbert Is. # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone Pacific/Kanton 0 - -00 1937 Aug 31 -12:00 - %z 1979 Oct -11:00 - %z 1994 Dec 31 13:00 - %z Zone Pacific/Kiritimati -10:29:20 - LMT 1901 -10:40 - %z 1979 Oct -10:00 - %z 1994 Dec 31 14:00 - %z # Marshall Is # See Pacific/Tarawa for most locations. # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone Pacific/Kwajalein 11:09:20 - LMT 1901 11:00 - %z 1937 10:00 - %z 1941 Apr 1 9:00 - %z 1944 Feb 6 11:00 - %z 1969 Oct -12:00 - %z 1993 Aug 20 24:00 12:00 - %z # Micronesia # For Chuuk and Yap see Pacific/Port_Moresby. # For Pohnpei see Pacific/Guadalcanal. # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone Pacific/Kosrae -13:08:04 - LMT 1844 Dec 31 10:51:56 - LMT 1901 11:00 - %z 1914 Oct 9:00 - %z 1919 Feb 1 11:00 - %z 1937 10:00 - %z 1941 Apr 1 9:00 - %z 1945 Aug 11:00 - %z 1969 Oct 12:00 - %z 1999 11:00 - %z # Nauru # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone Pacific/Nauru 11:07:40 - LMT 1921 Jan 15 # Uaobe 11:30 - %z 1942 Aug 29 9:00 - %z 1945 Sep 8 11:30 - %z 1979 Feb 10 2:00 12:00 - %z # New Caledonia # Rule NAME FROM TO - IN ON AT SAVE LETTER/S Rule NC 1977 1978 - Dec Sun>=1 0:00 1:00 - Rule NC 1978 1979 - Feb 27 0:00 0 - Rule NC 1996 only - Dec 1 2:00s 1:00 - # Shanks & Pottenger say the following was at 2:00; go with IATA. Rule NC 1997 only - Mar 2 2:00s 0 - # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone Pacific/Noumea 11:05:48 - LMT 1912 Jan 13 # Nouméa 11:00 NC %z ############################################################################### # New Zealand # McMurdo Station and Scott Base in Antarctica use Auckland time. # Rule NAME FROM TO - IN ON AT SAVE LETTER/S Rule NZ 1927 only - Nov 6 2:00 1:00 S Rule NZ 1928 only - Mar 4 2:00 0 M Rule NZ 1928 1933 - Oct Sun>=8 2:00 0:30 S Rule NZ 1929 1933 - Mar Sun>=15 2:00 0 M Rule NZ 1934 1940 - Apr lastSun 2:00 0 M Rule NZ 1934 1940 - Sep lastSun 2:00 0:30 S Rule NZ 1946 only - Jan 1 0:00 0 S # Since 1957 Chatham has been 45 minutes ahead of NZ, but until 2018a # there was no documented single notation for the date and time of this # transition. Duplicate the Rule lines for now, to give the 2018a change # time to percolate out. Rule NZ 1974 only - Nov Sun>=1 2:00s 1:00 D Rule Chatham 1974 only - Nov Sun>=1 2:45s 1:00 - Rule NZ 1975 only - Feb lastSun 2:00s 0 S Rule Chatham 1975 only - Feb lastSun 2:45s 0 - Rule NZ 1975 1988 - Oct lastSun 2:00s 1:00 D Rule Chatham 1975 1988 - Oct lastSun 2:45s 1:00 - Rule NZ 1976 1989 - Mar Sun>=1 2:00s 0 S Rule Chatham 1976 1989 - Mar Sun>=1 2:45s 0 - Rule NZ 1989 only - Oct Sun>=8 2:00s 1:00 D Rule Chatham 1989 only - Oct Sun>=8 2:45s 1:00 - Rule NZ 1990 2006 - Oct Sun>=1 2:00s 1:00 D Rule Chatham 1990 2006 - Oct Sun>=1 2:45s 1:00 - Rule NZ 1990 2007 - Mar Sun>=15 2:00s 0 S Rule Chatham 1990 2007 - Mar Sun>=15 2:45s 0 - Rule NZ 2007 max - Sep lastSun 2:00s 1:00 D Rule Chatham 2007 max - Sep lastSun 2:45s 1:00 - Rule NZ 2008 max - Apr Sun>=1 2:00s 0 S Rule Chatham 2008 max - Apr Sun>=1 2:45s 0 - # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone Pacific/Auckland 11:39:04 - LMT 1868 Nov 2 11:30 NZ NZ%sT 1946 Jan 1 12:00 NZ NZ%sT Zone Pacific/Chatham 12:13:48 - LMT 1868 Nov 2 12:15 - %z 1946 Jan 1 12:45 Chatham %z # Auckland Is # uninhabited; Māori and Moriori, colonial settlers, pastoralists, sealers, # and scientific personnel have wintered # Campbell I # minor whaling stations operated 1909/1914 # scientific station operated 1941/1995; # previously whalers, sealers, pastoralists, and scientific personnel wintered # was probably like Pacific/Auckland # Cook Is # # From Alexander Krivenyshev (2021-03-24): # In 1899 the Cook Islands celebrated Christmas twice to correct the calendar. # According to the old books, missionaries were unaware of # the International Date line, when they came from Sydney. # Thus the Cook Islands were one day ahead.... # http://nzetc.victoria.ac.nz/tm/scholarly/tei-KloDisc-t1-body-d18.html # ... Appendix to the Journals of the House of Representatives, 1900 # https://atojs.natlib.govt.nz/cgi-bin/atojs?a=d&d=AJHR1900-I.2.1.2.3 # (page 20) # # From Michael Deckers (2021-03-24): # ... in the Cook Island Act of 1915-10-11, online at # http://www.paclii.org/ck/legis/ck-nz_act/cia1915132/ # "651. The hour of the day shall in each of the islands included in the # Cook Islands be determined in accordance with the meridian of that island." # so that local (mean?) time was still used in Rarotonga (and Niue) in 1915. # This was changed in the Cook Island Amendment Act of 1952-10-16 ... # http://www.paclii.org/ck/legis/ck-nz_act/ciaa1952212/ # "651 (1) The hour of the day in each of the islands included in the Cook # Islands, other than Niue, shall be determined as if each island were # situated on the meridian one hundred and fifty-seven degrees thirty minutes # West of Greenwich. (2) The hour of the day in the Island of Niue shall be # determined as if that island were situated on the meridian one hundred and # seventy degrees West of Greenwich." # This act does not state when it takes effect, so one has to assume it # applies since 1952-10-16. But there is the possibility that the act just # legalized prior existing practice, as we had seen with the Guernsey law of # 1913-06-18 for the switch in 1909-04-19. # # From Paul Eggert (2021-03-24): # Transitions after 1952 are from Shanks & Pottenger. # # Rule NAME FROM TO - IN ON AT SAVE LETTER/S Rule Cook 1978 only - Nov 12 0:00 0:30 - Rule Cook 1979 1991 - Mar Sun>=1 0:00 0 - Rule Cook 1979 1990 - Oct lastSun 0:00 0:30 - # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone Pacific/Rarotonga 13:20:56 - LMT 1899 Dec 26 # Avarua -10:39:04 - LMT 1952 Oct 16 -10:30 - %z 1978 Nov 12 -10:00 Cook %z ############################################################################### # Niue # See Pacific/Rarotonga comments for 1952 transition. # # From Tim Parenti (2021-09-13): # Consecutive contemporaneous editions of The Air Almanac listed -11:20 for # Niue as of Apr 1964 but -11 as of Aug 1964: # Apr 1964: https://books.google.com/books?id=_1So677Y5vUC&pg=SL1-PA23 # Aug 1964: https://books.google.com/books?id=MbJloqd-zyUC&pg=SL1-PA23 # Without greater specificity, guess 1964-07-01 for this transition. # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone Pacific/Niue -11:19:40 - LMT 1952 Oct 16 # Alofi -11:20 - %z 1964 Jul -11:00 - %z # Norfolk # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone Pacific/Norfolk 11:11:52 - LMT 1901 # Kingston 11:12 - %z 1951 11:30 - %z 1974 Oct 27 02:00s 11:30 1:00 %z 1975 Mar 2 02:00s 11:30 - %z 2015 Oct 4 02:00s 11:00 - %z 2019 Jul 11:00 AN %z # Palau (Belau) # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone Pacific/Palau -15:02:04 - LMT 1844 Dec 31 # Koror 8:57:56 - LMT 1901 9:00 - %z # Papua New Guinea # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone Pacific/Port_Moresby 9:48:40 - LMT 1880 9:48:32 - PMMT 1895 # Port Moresby Mean Time 10:00 - %z # # From Paul Eggert (2014-10-13): # Base the Bougainville entry on the Arawa-Kieta region, which appears to have # the most people even though it was devastated in the Bougainville Civil War. # # Although Shanks gives 1942-03-15 / 1943-11-01 for UT +09, these dates # are apparently rough guesswork from the starts of military campaigns. # The World War II entries below are instead based on Arawa-Kieta. # The Japanese occupied Kieta in July 1942, # according to the Pacific War Online Encyclopedia # https://pwencycl.kgbudge.com/B/o/Bougainville.htm # and seem to have controlled it until their 1945-08-21 surrender. # # The Autonomous Region of Bougainville switched from UT +10 to +11 # on 2014-12-28 at 02:00. They call +11 "Bougainville Standard Time". # See: # http://www.bougainville24.com/bougainville-issues/bougainville-gets-own-timezone/ # Zone Pacific/Bougainville 10:22:16 - LMT 1880 9:48:32 - PMMT 1895 10:00 - %z 1942 Jul 9:00 - %z 1945 Aug 21 10:00 - %z 2014 Dec 28 2:00 11:00 - %z # Pitcairn # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone Pacific/Pitcairn -8:40:20 - LMT 1901 # Adamstown -8:30 - %z 1998 Apr 27 0:00 -8:00 - %z # American Samoa # Midway Zone Pacific/Pago_Pago 12:37:12 - LMT 1892 Jul 5 -11:22:48 - LMT 1911 -11:00 - SST # S=Samoa # Samoa (formerly and also known as Western Samoa) # From Steffen Thorsen (2009-10-16): # We have been in contact with the government of Samoa again, and received # the following info: # # "Cabinet has now approved Daylight Saving to be effected next year # commencing from the last Sunday of September 2010 and conclude first # Sunday of April 2011." # # Background info: # https://www.timeanddate.com/news/time/samoa-dst-plan-2009.html # # Samoa's Daylight Saving Time Act 2009 is available here, but does not # contain any dates: # http://www.parliament.gov.ws/documents/acts/Daylight%20Saving%20Act%20%202009%20(English)%20-%20Final%207-7-091.pdf # From Laupue Raymond Hughes (2010-10-07): # Please see # http://www.mcil.gov.ws # the Ministry of Commerce, Industry and Labour (sideframe) "Last Sunday # September 2010 (26/09/10) - adjust clocks forward from 12:00 midnight # to 01:00am and First Sunday April 2011 (03/04/11) - adjust clocks # backwards from 1:00am to 12:00am" # From Laupue Raymond Hughes (2011-03-07): # [http://www.mcil.gov.ws/ftcd/daylight_saving_2011.pdf] # # ... when the standard time strikes the hour of four o'clock (4.00am # or 0400 Hours) on the 2nd April 2011, then all instruments used to # measure standard time are to be adjusted/changed to three o'clock # (3:00am or 0300Hrs). # From David Zülke (2011-05-09): # Subject: Samoa to move timezone from east to west of international date line # # http://www.morningstar.co.uk/uk/markets/newsfeeditem.aspx?id=138501958347963 # From Paul Eggert (2014-06-27): # The International Date Line Act 2011 # http://www.parliament.gov.ws/images/ACTS/International_Date_Line_Act__2011_-_Eng.pdf # changed Samoa from UT -11 to +13, effective "12 o'clock midnight, on # Thursday 29th December 2011". The International Date Line was adjusted # accordingly. # From Laupue Raymond Hughes (2011-09-02): # http://www.mcil.gov.ws/mcil_publications.html # # here is the official website publication for Samoa DST and dateline change # # DST # Year End Time Start Time # 2011 - - - - - - 24 September 3:00am to 4:00am # 2012 01 April 4:00am to 3:00am - - - - - - # # Dateline Change skip Friday 30th Dec 2011 # Thursday 29th December 2011 23:59:59 Hours # Saturday 31st December 2011 00:00:00 Hours # # From Nicholas Pereira (2012-09-10): # Daylight Saving Time commences on Sunday 30th September 2012 and # ends on Sunday 7th of April 2013.... # http://www.mcil.gov.ws/mcil_publications.html # # From Paul Eggert (2014-07-08): # That web page currently lists transitions for 2012/3 and 2013/4. # Assume the pattern instituted in 2012 will continue indefinitely. # # From Geoffrey D. Bennett (2021-09-20): # https://www.mcil.gov.ws/storage/2021/09/MCIL-Scan_20210920_120553.pdf # DST has been cancelled for this year. # Rule NAME FROM TO - IN ON AT SAVE LETTER/S Rule WS 2010 only - Sep lastSun 0:00 1 - Rule WS 2011 only - Apr Sat>=1 4:00 0 - Rule WS 2011 only - Sep lastSat 3:00 1 - Rule WS 2012 2021 - Apr Sun>=1 4:00 0 - Rule WS 2012 2020 - Sep lastSun 3:00 1 - # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone Pacific/Apia 12:33:04 - LMT 1892 Jul 5 -11:26:56 - LMT 1911 -11:30 - %z 1950 -11:00 WS %z 2011 Dec 29 24:00 13:00 WS %z # Solomon Is # excludes Bougainville, for which see Papua New Guinea # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone Pacific/Guadalcanal 10:39:48 - LMT 1912 Oct 1 # Honiara 11:00 - %z # Tokelau # # From Gwillim Law (2011-12-29) # A correspondent informed me that Tokelau, like Samoa, will be skipping # December 31 this year ... # # From Steffen Thorsen (2012-07-25) # ... we double checked by calling hotels and offices based in Tokelau asking # about the time there, and they all told a time that agrees with UTC+13.... # Shanks says UT-10 from 1901 [but] ... there is a good chance the change # actually was to UT-11 back then. # # From Paul Eggert (2012-07-25) # A Google Books snippet of Appendix to the Journals of the House of # Representatives of New Zealand, Session 1948, # , page 65, says Tokelau # was "11 hours slow on G.M.T." Go with Thorsen and assume Shanks & Pottenger # are off by an hour starting in 1901. # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone Pacific/Fakaofo -11:24:56 - LMT 1901 -11:00 - %z 2011 Dec 30 13:00 - %z # Tonga # Rule NAME FROM TO - IN ON AT SAVE LETTER/S Rule Tonga 1999 only - Oct 7 2:00s 1:00 - Rule Tonga 2000 only - Mar 19 2:00s 0 - Rule Tonga 2000 2001 - Nov Sun>=1 2:00 1:00 - Rule Tonga 2001 2002 - Jan lastSun 2:00 0 - Rule Tonga 2016 only - Nov Sun>=1 2:00 1:00 - Rule Tonga 2017 only - Jan Sun>=15 3:00 0 - # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone Pacific/Tongatapu 12:19:12 - LMT 1945 Sep 10 12:20 - %z 1961 13:00 - %z 1999 13:00 Tonga %z # US minor outlying islands # Howland, Baker # Howland was mined for guano by American companies 1857-1878 and British # 1886-1891; Baker was similar but exact dates are not known. # Inhabited by civilians 1935-1942; U.S. military bases 1943-1944; # uninhabited thereafter. # Howland observed Hawaii Standard Time (UT -10:30) in 1937; # see page 206 of Elgen M. Long and Marie K. Long, # Amelia Earhart: the Mystery Solved, Simon & Schuster (2000). # So most likely Howland and Baker observed Hawaii Time from 1935 # until they were abandoned after the war. # Jarvis # Mined for guano by American companies 1857-1879 and British 1883?-1891?. # Inhabited by civilians 1935-1942; IGY scientific base 1957-1958; # uninhabited thereafter. # no information; was probably like Pacific/Kiritimati # Johnston # # From Paul Eggert (2017-02-10): # Sometimes Johnston kept Hawaii time, and sometimes it was an hour behind. # Details are uncertain. We have no data for Johnston after 1970, so # treat it like Hawaii for now. Since Johnston is now uninhabited, # its link to Pacific/Honolulu is in the 'backward' file. # # In his memoirs of June 6th to October 4, 1945 # (2005), Herbert C. Bach writes, # "We started our letdown to Kwajalein Atoll and landed there at 5:00 AM # Johnston time, 1:30 AM Kwajalein time." This was in June 1945, and # confirms that Johnston kept the same time as Honolulu in summer 1945. # # From Lyle McElhaney (2014-03-11): # [W]hen JI was being used for that [atomic bomb] testing, the time being used # was not Hawaiian time but rather the same time being used on the ships, # which had a GMT offset of -11 hours. This apparently applied to at least the # time from Operation Newsreel (Hardtack I/Teak shot, 1958-08-01) to the last # Operation Fishbowl shot (Tightrope, 1962-11-04).... [See] Herman Hoerlin, # "The United States High-Altitude Test Experience: A Review Emphasizing the # Impact on the Environment", Los Alamos LA-6405, Oct 1976. # https://www.fas.org/sgp/othergov/doe/lanl/docs1/00322994.pdf # See the table on page 4 where he lists GMT and local times for the tests; a # footnote for the JI tests reads that local time is "JI time = Hawaii Time # Minus One Hour". # Kingman # uninhabited # Palmyra # uninhabited since World War II; was probably like Pacific/Kiritimati # Vanuatu # From P Chan (2020-11-27): # Joint Daylight Saving Regulation No 59 of 1973 # New Hebrides Condominium Gazette No 336. December 1973 # http://www.paclii.org/vu/other/VUNHGovGaz//1973/11.pdf#page=15 # # Joint Daylight Saving (Repeal) Regulation No 10 of 1974 # New Hebrides Condominium Gazette No 336. March 1974 # http://www.paclii.org/vu/other/VUNHGovGaz//1974/3.pdf#page=11 # # Summer Time Act No. 35 of 1982 [commenced 1983-09-01] # http://www.paclii.org/vu/other/VUGovGaz/1982/32.pdf#page=48 # # Summer Time Act (Cap 157) # Laws of the Republic of Vanuatu Revised Edition 1988 # http://www.paclii.org/cgi-bin/sinodisp/vu/legis/consol_act1988/sta147/sta147.html # # Summer Time (Amendment) Act No. 6 of 1991 [commenced 1991-11-11] # http://www.paclii.org/vu/legis/num_act/sta1991227/ # # Summer Time (Repeal) Act No. 4 of 1993 [commenced 1993-05-03] # http://www.paclii.org/vu/other/VUGovGaz/1993/15.pdf#page=59 # Rule NAME FROM TO - IN ON AT SAVE LETTER/S Rule Vanuatu 1973 only - Dec 22 12:00u 1:00 - Rule Vanuatu 1974 only - Mar 30 12:00u 0 - Rule Vanuatu 1983 1991 - Sep Sat>=22 24:00 1:00 - Rule Vanuatu 1984 1991 - Mar Sat>=22 24:00 0 - Rule Vanuatu 1992 1993 - Jan Sat>=22 24:00 0 - Rule Vanuatu 1992 only - Oct Sat>=22 24:00 1:00 - # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone Pacific/Efate 11:13:16 - LMT 1912 Jan 13 # Vila 11:00 Vanuatu %z ############################################################################### # NOTES # This file is by no means authoritative; if you think you know better, # go ahead and edit the file, and please send any changes to # the public mailing list tz@iana.org for general use in the future. # For more, please see the file CONTRIBUTING in the tz distribution. # From Paul Eggert (2018-11-18): # # Unless otherwise specified, the source for data through 1990 is: # Thomas G. Shanks and Rique Pottenger, The International Atlas (6th edition), # San Diego: ACS Publications, Inc. (2003). # Unfortunately this book contains many errors and cites no sources. # # Many years ago Gwillim Law wrote that a good source # for time zone data was the International Air Transport # Association's Standard Schedules Information Manual (IATA SSIM), # published semiannually. Law sent in several helpful summaries # of the IATA's data after 1990. Except where otherwise noted, # IATA SSIM is the source for entries after 1990. # # Another source occasionally used is Edward W. Whitman, World Time Differences, # Whitman Publishing Co, 2 Niagara Av, Ealing, London (undated), which # I found in the UCLA library. # # For data circa 1899, a common source is: # Milne J. Civil time. Geogr J. 1899 Feb;13(2):173-94. # https://www.jstor.org/stable/1774359 # # For the 1911/1912 establishment of standard time in French possessions, see: # Société Française de Physique, Recueil de constantes physiques (1913), # page 752, 18b. # # A reliable and entertaining source about time zones is # Derek Howse, Greenwich time and longitude, Philip Wilson Publishers (1997). # # I invented the abbreviation marked "*". # The following abbreviations are from other sources. # Corrections are welcome! # std dst # LMT Local Mean Time # 8:00 AWST AWDT Western Australia # 9:30 ACST ACDT Central Australia # 10:00 AEST AEDT Eastern Australia # 10:00 GST GDT* Guam through 2000 # 10:00 ChST Chamorro # 11:30 NZMT NZST New Zealand through 1945 # 12:00 NZST NZDT New Zealand 1946-present # -11:00 SST Samoa # -10:00 HST Hawaii # # See the 'northamerica' file for Hawaii. # See the 'southamerica' file for Easter I and the Galápagos Is. ############################################################################### # Australia # From Paul Eggert (2014-06-30): # Daylight saving time has long been controversial in Australia, pitting # region against region, rural against urban, and local against global. # For example, in her review of Graeme Davison's _The Unforgiving # Minute: how Australians learned to tell the time_ (1993), Perth native # Phillipa J Martyr wrote, "The section entitled 'Saving Daylight' was # very informative, but was (as can, sadly, only be expected from a # Melbourne-based study) replete with the usual chuckleheaded # Queenslanders and straw-chewing yokels from the West prattling fables # about fading curtains and crazed farm animals." # Electronic Journal of Australian and New Zealand History (1997-03-03) # http://www.jcu.edu.au/aff/history/reviews/davison.htm # From P Chan (2020-11-20): # Daylight Saving Act 1916 (No. 40 of 1916) [1916-12-21, commenced 1917-01-01] # http://classic.austlii.edu.au/au/legis/cth/num_act/dsa1916401916192/ # # Daylight Saving Repeal Act 1917 (No. 35 of 1917) [1917-09-25] # http://classic.austlii.edu.au/au/legis/cth/num_act/dsra1917351917243/ # # Statutory Rules 1941, No. 323 [1941-12-24] # https://www.legislation.gov.au/Details/C1941L00323 # # Statutory Rules 1942, No. 392 [1942-09-10] # https://www.legislation.gov.au/Details/C1942L00392 # # Statutory Rules 1943, No. 241 [1943-09-29] # https://www.legislation.gov.au/Details/C1943L00241 # # All transition times should be 02:00 standard time. # From Paul Eggert (2005-12-08): # Implementation Dates of Daylight Saving Time within Australia # http://www.bom.gov.au/climate/averages/tables/dst_times.shtml # summarizes daylight saving issues in Australia. # From Arthur David Olson (2005-12-12): # Lawlink NSW:Daylight Saving in New South Wales # http://www.lawlink.nsw.gov.au/lawlink/Corporate/ll_agdinfo.nsf/pages/community_relations_daylight_saving # covers New South Wales in particular. # From John Mackin (1991-03-06): # We in Australia have _never_ referred to DST as 'daylight' time. # It is called 'summer' time. Now by a happy coincidence, 'summer' # and 'standard' happen to start with the same letter; hence, the # abbreviation does _not_ change... # The legislation does not actually define abbreviations, at least # in this State, but the abbreviation is just commonly taken to be the # initials of the phrase, and the legislation here uniformly uses # the phrase 'summer time' and does not use the phrase 'daylight # time'. # Announcers on the Commonwealth radio network, the ABC (for Australian # Broadcasting Commission), use the phrases 'Eastern Standard Time' # or 'Eastern Summer Time'. (Note, though, that as I say in the # current australasia file, there is really no such thing.) Announcers # on its overseas service, Radio Australia, use the same phrases # prefixed by the word 'Australian' when referring to local times; # time announcements on that service, naturally enough, are made in UTC. # From Paul Eggert (2014-06-30): # # Inspired by Mackin's remarks quoted above, earlier versions of this # file used "EST" for both Eastern Standard Time and Eastern Summer # Time in Australia, and similarly for "CST", "CWST", and "WST". # However, these abbreviations were confusing and were not common # practice among Australians, and there were justifiable complaints # about them, so I attempted to survey current Australian usage. # For the tz database, the full English phrase is not that important; # what matters is the abbreviation. It's difficult to survey the web # directly for abbreviation usage, as there are so many false hits for # strings like "EST" and "EDT", so I looked for pages that defined an # abbreviation for eastern or central DST in Australia, and got the # following numbers of unique hits for the listed Google queries: # # 10 "Eastern Daylight Time AEST" site:au [some are false hits] # 10 "Eastern Summer Time AEST" site:au # 10 "Summer Time AEDT" site:au # 13 "EDST Eastern Daylight Saving Time" site:au # 18 "Summer Time ESST" site:au # 28 "Eastern Daylight Saving Time EDST" site:au # 39 "EDT Eastern Daylight Time" site:au [some are false hits] # 53 "Eastern Daylight Time EDT" site:au [some are false hits] # 54 "AEDT Australian Eastern Daylight Time" site:au # 182 "Eastern Daylight Time AEDT" site:au # # 17 "Central Daylight Time CDT" site:au [some are false hits] # 46 "Central Daylight Time ACDT" site:au # # I tried several other variants (e.g., "Eastern Summer Time EST") but # they all returned fewer than 10 unique hits. I also looked for pages # mentioning both "western standard time" and an abbreviation, since # there is no WST in the US to generate false hits, and found: # # 156 "western standard time" AWST site:au # 226 "western standard time" WST site:au # # I then surveyed the top ten newspapers in Australia by circulation as # listed in Wikipedia, using Google queries like "AEDT site:heraldsun.com.au" # and obtaining estimated counts from the initial page of search results. # All ten papers greatly preferred "AEDT" to "EDT". The papers # surveyed were the Herald Sun, The Daily Telegraph, The Courier-Mail, # The Sydney Morning Herald, The West Australian, The Age, The Advertiser, # The Australian, The Financial Review, and The Herald (Newcastle). # # I also searched for historical usage, to see whether abbreviations # like "AEDT" are new. A Trove search # found only one newspaper (The Canberra Times) with a house style # dating back to the 1970s, I expect because other newspapers weren't # fully indexed. The Canberra Times strongly preferred abbreviations # like "AEDT". The first occurrence of "AEDT" was a World Weather # column (1971-11-17, page 24), and of "ACDT" was a Scoreboard column # (1993-01-24, p 16). The style was the typical usage but was not # strictly enforced; for example, "Welcome to the twilight zones ..." # (1994-10-29, p 1) uses the abbreviations AEST/AEDT, CST/CDT, and # WST, and goes on to say, "The confusion and frustration some feel # about the lack of uniformity among Australia's six states and two # territories has prompted one group to form its very own political # party -- the Sydney-based Daylight Saving Extension Party." # # I also surveyed federal government sources. They did not agree: # # The Australian Government (2014-03-26) # http://australia.gov.au/about-australia/our-country/time # (This document was produced by the Department of Finance.) # AEST ACST AWST AEDT ACDT # # Bureau of Meteorology (2012-11-08) # http://www.bom.gov.au/climate/averages/tables/daysavtm.shtml # EST CST WST EDT CDT # # Civil Aviation Safety Authority (undated) # http://services.casa.gov.au/outnback/inc/pages/episode3/episode-3_time_zones.shtml # EST CST WST (no abbreviations given for DST) # # Geoscience Australia (2011-11-24) # http://www.ga.gov.au/geodesy/astro/sunrise.jsp # AEST ACST AWST AEDT ACDT # # Parliamentary Library (2008-11-10) # https://www.aph.gov.au/binaries/library/pubs/rp/2008-09/09rp14.pdf # EST CST WST preferred for standard time; AEST AEDT ACST ACDT also used # # The Transport Safety Bureau has an extensive series of accident reports, # and investigators seem to use whatever abbreviation they like. # Googling site:atsb.gov.au found the following number of unique hits: # 311 "ESuT", 195 "EDT", 26 "AEDT", 83 "CSuT", 46 "CDT". # "_SuT" tended to appear in older reports, and "A_DT" tended to # appear in reports of events with international implications. # # From the above it appears that there is a working consensus in # Australia to use trailing "DT" for daylight saving time; although # some sources use trailing "SST" or "ST" or "SuT" they are by far in # the minority. The case for leading "A" is weaker, but since it # seems to be preferred in the overall web and is preferred in all # the leading newspaper websites and in many government departments, # it has a stronger case than omitting the leading "A". The current # version of the database therefore uses abbreviations like "AEST" and # "AEDT" for Australian time zones. # From Paul Eggert (1995-12-19): # Shanks & Pottenger report 2:00 for all autumn changes in Australia and NZ. # Mark Prior writes that his newspaper # reports that NSW's fall 1995 change will occur at 2:00, # but Robert Elz says it's been 3:00 in Victoria since 1970 # and perhaps the newspaper's '2:00' is referring to standard time. # For now we'll continue to assume 2:00s for changes since 1960. # From Eric Ulevik (1998-01-05): # # Here are some URLs to Australian time legislation. These URLs are stable, # and should probably be included in the data file. There are probably more # relevant entries in this database. # # NSW (including LHI and Broken Hill): # Standard Time Act 1987 (updated 1995-04-04) # https://www.austlii.edu.au/au/legis/nsw/consol_act/sta1987137/index.html # ACT # Standard Time and Summer Time Act 1972 # https://www.austlii.edu.au/au/legis/act/consol_act/stasta1972279/index.html # SA # Standard Time Act, 1898 # https://www.austlii.edu.au/au/legis/sa/consol_act/sta1898137/index.html # From David Grosz (2005-06-13): # It was announced last week that Daylight Saving would be extended by # one week next year to allow for the 2006 Commonwealth Games. # Daylight Saving is now to end for next year only on the first Sunday # in April instead of the last Sunday in March. # # From Gwillim Law (2005-06-14): # I did some Googling and found that all of those states (and territory) plan # to extend DST together in 2006. # ACT: http://www.cmd.act.gov.au/mediareleases/fileread.cfm?file=86.txt # New South Wales: http://www.thecouriermail.news.com.au/common/story_page/0,5936,15538869%255E1702,00.html # South Australia: http://www.news.com.au/story/0,10117,15555031-1246,00.html # Tasmania: http://www.media.tas.gov.au/release.php?id=14772 # Victoria: I wasn't able to find anything separate, but the other articles # allude to it. # But not Queensland # http://www.news.com.au/story/0,10117,15564030-1248,00.html # Northern Territory # From George Shepherd via Simon Woodhead via Robert Elz (1991-03-06): # # The NORTHERN TERRITORY.. [ Courtesy N.T. Dept of the Chief Minister ] # # [ Nov 1990 ] # # N.T. have never utilised any DST due to sub-tropical/tropical location. # ... # Zone Australia/North 9:30 - CST # From Bradley White (1991-03-04): # A recent excerpt from an Australian newspaper... # the Northern Territory do[es] not have daylight saving. # Western Australia # From George Shepherd via Simon Woodhead via Robert Elz (1991-03-06): # # The state of WESTERN AUSTRALIA.. [ Courtesy W.A. dept Premier+Cabinet ] # # [ Nov 1990 ] # # W.A. suffers from a great deal of public and political opposition to # # DST in principle. A bill is brought before parliament in most years, but # # usually defeated either in the upper house, or in party caucus # # before reaching parliament. # ... # Zone Australia/West 8:00 AW %sST # ... # Rule AW 1974 only - Oct lastSun 2:00 1:00 D # Rule AW 1975 only - Mar Sun>=1 3:00 0 W # Rule AW 1983 only - Oct lastSun 2:00 1:00 D # Rule AW 1984 only - Mar Sun>=1 3:00 0 W # From Bradley White (1991-03-04): # A recent excerpt from an Australian newspaper... # Western Australia...do[es] not have daylight saving. # From John D. Newman via Bradley White (1991-11-02): # Western Australia is still on "winter time". Some DH in Sydney # rang me at home a few days ago at 6.00am. (He had just arrived at # work at 9.00am.) # W.A. is switching to Summer Time on Nov 17th just to confuse # everybody again. # From Arthur David Olson (1992-03-08): # The 1992 ending date used in the rules is a best guess; # it matches what was used in the past. # From Christopher Hunt (2006-11-21), after an advance warning # from Jesper Nørgaard Welen (2006-11-01): # WA are trialing DST for three years. # http://www.parliament.wa.gov.au/parliament/bills.nsf/9A1B183144403DA54825721200088DF1/$File/Bill175-1B.pdf # From Paul Eggert (2018-04-01): # The Guardian Express of Perth, Australia reported today that the # government decided to advance the clocks permanently on January 1, # 2019, from UT +08 to UT +09. The article noted that an exemption # would be made for people aged 61 and over, who "can apply in writing # to have the extra hour of sunshine removed from their area." See: # Daylight saving coming to WA in 2019. Guardian Express. 2018-04-01. # https://www.communitynews.com.au/guardian-express/news/exclusive-daylight-savings-coming-wa-summer-2018/ # [The article ends with "Today's date is April 1."] # The Australian Bureau of Meteorology FAQ # http://www.bom.gov.au/faq/faqgen.htm # (1999-09-27) writes that Giles Meteorological Station uses # South Australian time even though it's located in Western Australia. # From Rives McDow (2002-04-09): # The most interesting region I have found consists of three towns on the # southern coast.... South Australia observes daylight saving time; Western # Australia does not. The two states are one and a half hours apart. The # residents decided to forget about this nonsense of changing the clock so # much and set the local time 20 hours and 45 minutes from the # international date line, or right in the middle of the time of South # Australia and Western Australia.... # # From Paul Eggert (2002-04-09): # This is confirmed by the section entitled # "What's the deal with time zones???" in # http://www.earthsci.unimelb.edu.au/~awatkins/null.html # # From Alex Livingston (2006-12-07): # ... it was just on four years ago that I drove along the Eyre Highway, # which passes through eastern Western Australia close to the southern # coast of the continent. # # I paid particular attention to the time kept there. There can be no # dispute that UTC+08:45 was considered "the time" from the border # village just inside the border with South Australia to as far west # as just east of Caiguna. There can also be no dispute that Eucla is # the largest population centre in this zone.... # # Now that Western Australia is observing daylight saving, the # question arose whether this part of the state would follow suit. I # just called the border village and confirmed that indeed they have, # meaning that they are now observing UTC+09:45. # # (2006-12-09): # I personally doubt that either experimentation with daylight saving # in WA or its introduction in SA had anything to do with the genesis # of this time zone. My hunch is that it's been around since well # before 1975. I remember seeing it noted on road maps decades ago. # # From Gilmore Davidson (2019-04-08): # https://www.abc.net.au/news/2019-04-08/this-remote-stretch-of-desert-has-its-own-custom-time-zone/10981000 # ... include[s] a rough description of the geographical boundaries... # "The time zone exists for about 340 kilometres and takes in the tiny # roadhouse communities of Cocklebiddy, Madura, Eucla and Border Village." # ... and an indication that the zone has definitely been in existence # since before the 1970 cut-off of the database ... # From Paul Eggert (2019-05-17): # That ABC Esperance story by Christien de Garis also says: # Although the Central Western Time Zone is not officially recognised (your # phones won't automatically change), there is a sign instructing you which # way to wind your clocks 45 minutes and scrawled underneath one of them in # Texta is the word: 'Why'? # "Good question," Mr Pike said. # "I don't even know that, and it's been going for over 50 years." # From Paul Eggert (2006-12-15): # For lack of better info, assume the tradition dates back to the # introduction of standard time in 1895. # From Stuart Bishop (2024-11-12): # An article discussing the in-use but technically unofficial timezones # in the Western Australian portion of the Nullarbor Plain. # https://www.abc.net.au/news/2024-11-22/outback-wa-properties-strange-time-zones/104542494 # From Paul Eggert (2024-11-12): # As the article says, the Eyre Bird Observatory and nearby sheep stations # can use Tokyo time. Other possibilities include Asia/Chita, Asia/Seoul, # and Asia/Jayapura. # Queensland # From Paul Eggert (2018-02-26): # I lack access to the following source for Queensland DST: # Pearce C. History of daylight saving time in Queensland. # Queensland Hist J. 2017 Aug;23(6):389-403 # https://search.informit.com.au/documentSummary;dn=994682348436426;res=IELHSS # From George Shepherd via Simon Woodhead via Robert Elz (1991-03-06): # # The state of QUEENSLAND.. [ Courtesy Qld. Dept Premier Econ&Trade Devel ] # # [ Dec 1990 ] # ... # Zone Australia/Queensland 10:00 AQ %sST # ... # Rule AQ 1971 only - Oct lastSun 2:00 1:00 D # Rule AQ 1972 only - Feb lastSun 3:00 0 E # Rule AQ 1989 max - Oct lastSun 2:00 1:00 D # Rule AQ 1990 max - Mar Sun>=1 3:00 0 E # From Bradley White (1989-12-24): # "Australia/Queensland" now observes daylight time (i.e. from # October 1989). # From Bradley White (1991-03-04): # A recent excerpt from an Australian newspaper... # ...Queensland...[has] agreed to end daylight saving # at 3am tomorrow (March 3)... # From John Mackin (1991-03-06): # I can certainly confirm for my part that Daylight Saving in NSW did in fact # end on Sunday, 3 March. I don't know at what hour, though. (It surprised # me.) # From Bradley White (1992-03-08): # ...there was recently a referendum in Queensland which resulted # in the experimental daylight saving system being abandoned. So, ... # ... # Rule QLD 1989 1991 - Oct lastSun 2:00 1:00 D # Rule QLD 1990 1992 - Mar Sun>=1 3:00 0 S # ... # From Arthur David Olson (1992-03-08): # The chosen rules the union of the 1971/1972 change and the 1989-1992 changes. # southeast Australia # # From Paul Eggert (2007-07-23): # Starting autumn 2008 Victoria, NSW, South Australia, Tasmania and the ACT # end DST the first Sunday in April and start DST the first Sunday in October. # http://www.theage.com.au/news/national/daylight-savings-to-span-six-months/2007/06/27/1182623966703.html # South Australia # From Bradley White (1991-03-04): # A recent excerpt from an Australian newspaper... # ...South Australia...[has] agreed to end daylight saving # at 3am tomorrow (March 3)... # From George Shepherd via Simon Woodhead via Robert Elz (1991-03-06): # # The state of SOUTH AUSTRALIA....[ Courtesy of S.A. Dept of Labour ] # # [ Nov 1990 ] # ... # Zone Australia/South 9:30 AS %sST # ... # Rule AS 1971 max - Oct lastSun 2:00 1:00 D # Rule AS 1972 1985 - Mar Sun>=1 3:00 0 C # Rule AS 1986 1990 - Mar Sun>=15 3:00 0 C # Rule AS 1991 max - Mar Sun>=1 3:00 0 C # From Bradley White (1992-03-11): # Recent correspondence with a friend in Adelaide # contained the following exchange: "Due to the Adelaide Festival, # South Australia delays setting back our clocks for a few weeks." # From Robert Elz (1992-03-13): # I heard that apparently (or at least, it appears that) # South Aus will have an extra 3 weeks daylight saving every even # numbered year (from 1990). That's when the Adelaide Festival # is on... # From Robert Elz (1992-03-16, 00:57:07 +1000): # DST didn't end in Adelaide today (yesterday).... # But whether it's "4th Sunday" or "2nd last Sunday" I have no idea whatever... # (it's just as likely to be "the Sunday we pick for this year"...). # From Bradley White (1994-04-11): # If Sun, 15 March, 1992 was at +1030 as kre asserts, but yet Sun, 20 March, # 1994 was at +0930 as John Connolly's customer seems to assert, then I can # only conclude that the actual rule is more complicated.... # From John Warburton (1994-10-07): # The new Daylight Savings dates for South Australia ... # was gazetted in the Government Hansard on Sep 26 1994.... # start on last Sunday in October and end in last sunday in March. # From Paul Eggert (2007-07-23): # See "southeast Australia" above for 2008 and later. # Tasmania # From P Chan (2020-11-20): # Tasmania observed DST in 1916-1919. # # Daylight Saving Act, 1916 (7 Geo V, No 2) [1916-09-22] # http://classic.austlii.edu.au/au/legis/tas/num_act/tdsa19167gvn2267/ # # Daylight Saving Amendment Act, 1917 (8 Geo V, No 5) [1917-10-01] # http://classic.austlii.edu.au/au/legis/tas/num_act/tdsaa19178gvn5347/ # # Daylight Saving Act Repeal Act, 1919 (10 Geo V, No 9) [1919-10-24] # http://classic.austlii.edu.au/au/legis/tas/num_act/tdsara191910gvn9339/ # # King Island is mentioned in the 1967 Act but not the 1968 Act. # Therefore it possibly observed DST from 1968/69. # # Daylight Saving Act 1967 (No. 33 of 1967) [1967-09-22] # http://classic.austlii.edu.au/au/legis/tas/num_act/dsa196733o1967211/ # # Daylight Saving Act 1968 (No. 42 of 1968) [1968-10-15] # http://classic.austlii.edu.au/au/legis/tas/num_act/dsa196842o1968211/ # The rules for 1967 through 1991 were reported by George Shepherd # via Simon Woodhead via Robert Elz (1991-03-06): # # The state of TASMANIA.. [Courtesy Tasmanian Dept of Premier + Cabinet ] # # [ Nov 1990 ] # From Bill Hart via Guy Harris (1991-10-10): # Oh yes, the new daylight savings rules are uniquely tasmanian, we have # 6 weeks a year now when we are out of sync with the rest of Australia # (but nothing new about that). # From Alex Livingston (1999-10-04): # I heard on the ABC (Australian Broadcasting Corporation) radio news on the # (long) weekend that Tasmania, which usually goes its own way in this regard, # has decided to join with most of NSW, the ACT, and most of Victoria # (Australia) and start daylight saving on the last Sunday in August in 2000 # instead of the first Sunday in October. # Sim Alam (2000-07-03) reported a legal citation for the 2000/2001 rules: # http://www.thelaw.tas.gov.au/fragview/42++1968+GS3A@EN+2000070300 # From Paul Eggert (2007-07-23): # See "southeast Australia" above for 2008 and later. # Victoria # The rules for 1971 through 1991 were reported by George Shepherd # via Simon Woodhead via Robert Elz (1991-03-06): # # The state of VICTORIA.. [ Courtesy of Vic. Dept of Premier + Cabinet ] # # [ Nov 1990 ] # From Scott Harrington (2001-08-29): # On KQED's "City Arts and Lectures" program last night I heard an # interesting story about daylight savings time. Dr. John Heilbron was # discussing his book "The Sun in the Church: Cathedrals as Solar # Observatories"[1], and in particular the Shrine of Remembrance[2] located # in Melbourne, Australia. # # Apparently the shrine's main purpose is a beam of sunlight which # illuminates a special spot on the floor at the 11th hour of the 11th day # of the 11th month (Remembrance Day) every year in memory of Australia's # fallen WWI soldiers. And if you go there on Nov. 11, at 11am local time, # you will indeed see the sunbeam illuminate the special spot at the # expected time. # # However, that is only because of some special mirror contraption that had # to be employed, since due to daylight savings time, the true solar time of # the remembrance moment occurs one hour later (or earlier?). Perhaps # someone with more information on this jury-rig can tell us more. # # [1] http://www.hup.harvard.edu/catalog/HEISUN.html # [2] http://www.shrine.org.au # From Paul Eggert (2007-07-23): # See "southeast Australia" above for 2008 and later. # New South Wales # From Arthur David Olson: # New South Wales and subjurisdictions have their own ideas of a fun time. # Based on law library research by John Mackin, # who notes: # In Australia, time is not legislated federally, but rather by the # individual states. Thus, while such terms as "Eastern Standard Time" # [I mean, of course, Australian EST, not any other kind] are in common # use, _they have NO REAL MEANING_, as they are not defined in the # legislation. This is very important to understand. # I have researched New South Wales time only... # From Eric Ulevik (1999-05-26): # DST will start in NSW on the last Sunday of August, rather than the usual # October in 2000. See: Matthew Moore, # Two months more daylight saving, Sydney Morning Herald (1999-05-26). # http://www.smh.com.au/news/9905/26/pageone/pageone4.html # From Paul Eggert (1999-09-27): # See the following official NSW source: # Daylight Saving in New South Wales. # http://dir.gis.nsw.gov.au/cgi-bin/genobject/document/other/daylightsaving/tigGmZ # # Narrabri Shire (NSW) council has announced it will ignore the extension of # daylight saving next year. See: # Narrabri Council to ignore daylight saving # http://abc.net.au/news/regionals/neweng/monthly/regeng-22jul1999-1.htm # (1999-07-22). For now, we'll wait to see if this really happens. # # Victoria will follow NSW. See: # Vic to extend daylight saving (1999-07-28) # http://abc.net.au/local/news/olympics/1999/07/item19990728112314_1.htm # # However, South Australia rejected the DST request. See: # South Australia rejects Olympics daylight savings request (1999-07-19) # http://abc.net.au/news/olympics/1999/07/item19990719151754_1.htm # # Queensland also will not observe DST for the Olympics. See: # Qld says no to daylight savings for Olympics # http://abc.net.au/news/olympics/1999/06/item19990601114608_1.htm # (1999-06-01), which quotes Queensland Premier Peter Beattie as saying # "Look you've got to remember in my family when this came up last time # I voted for it, my wife voted against it and she said to me it's all very # well for you, you don't have to worry about getting the children out of # bed, getting them to school, getting them to sleep at night. # I've been through all this argument domestically...my wife rules." # # Broken Hill will stick with South Australian time in 2000. See: # Broken Hill to be behind the times (1999-07-21) # http://abc.net.au/news/regionals/brokenh/monthly/regbrok-21jul1999-6.htm # IATA SSIM (1998-09) says that the spring 2000 change for Australian # Capital Territory, New South Wales except Lord Howe Island and Broken # Hill, and Victoria will be August 27, presumably due to the Sydney Olympics. # From Eric Ulevik, referring to Sydney's Sun Herald (2000-08-13), page 29: # The Queensland Premier Peter Beattie is encouraging northern NSW # towns to use Queensland time. # From Paul Eggert (2007-07-23): # See "southeast Australia" above for 2008 and later. # Yancowinna # From John Mackin (1989-01-04): # 'Broken Hill' means the County of Yancowinna. # From George Shepherd via Simon Woodhead via Robert Elz (1991-03-06): # # YANCOWINNA.. [ Confirmation courtesy of Broken Hill Postmaster ] # # [ Dec 1990 ] # ... # # Yancowinna uses Central Standard Time, despite [its] location on the # # New South Wales side of the S.A. border. Most business and social dealings # # are with CST zones, therefore CST is legislated by local government # # although the switch to Summer Time occurs in line with N.S.W. There have # # been years when this did not apply, but the historical data is not # # presently available. # Zone Australia/Yancowinna 9:30 AY %sST # ... # Rule AY 1971 1985 - Oct lastSun 2:00 1:00 D # Rule AY 1972 only - Feb lastSun 3:00 0 C # [followed by other Rules] # Lord Howe Island # From George Shepherd via Simon Woodhead via Robert Elz (1991-03-06): # LHI... [ Courtesy of Pauline Van Winsen ] # [ Dec 1990 ] # Lord Howe Island is located off the New South Wales coast, and is half an # hour ahead of NSW time. # From James Lonergan, Secretary, Lord Howe Island Board (2000-01-27): # Lord Howe Island summer time in 2000/2001 will commence on the same # date as the rest of NSW (i.e. 2000-08-27). For your information the # Lord Howe Island Board (controlling authority for the Island) is # seeking the community's views on various options for summer time # arrangements on the Island, e.g. advance clocks by 1 full hour # instead of only 30 minutes. [Dependent] on the wishes of residents # the Board may approach the NSW government to change the existing # arrangements. The starting date for summer time on the Island will # however always coincide with the rest of NSW. # From James Lonergan, Secretary, Lord Howe Island Board (2000-10-25): # Lord Howe Island advances clocks by 30 minutes during DST in NSW and retards # clocks by 30 minutes when DST finishes. Since DST was most recently # introduced in NSW, the "changeover" time on the Island has been 02:00 as # shown on clocks on LHI. I guess this means that for 30 minutes at the start # of DST, LHI is actually 1 hour ahead of the rest of NSW. # From Paul Eggert (2006-03-22): # For Lord Howe dates we use Shanks & Pottenger through 1989, and # Lonergan thereafter. For times we use Lonergan. # From Paul Eggert (2007-07-23): # See "southeast Australia" above for 2008 and later. # From Steffen Thorsen (2009-04-28): # According to the official press release, South Australia's extended daylight # saving period will continue with the same rules as used during the 2008-2009 # summer (southern hemisphere). # # From # http://www.safework.sa.gov.au/uploaded_files/DaylightDatesSet.pdf # The extended daylight saving period that South Australia has been trialling # for over the last year is now set to be ongoing. # Daylight saving will continue to start on the first Sunday in October each # year and finish on the first Sunday in April the following year. # Industrial Relations Minister, Paul Caica, says this provides South Australia # with a consistent half hour time difference with NSW, Victoria, Tasmania and # the ACT for all 52 weeks of the year... # # We have a wrap-up here: # https://www.timeanddate.com/news/time/south-australia-extends-dst.html ############################################################################### # New Zealand # From Mark Davies (1990-10-03): # the 1989/90 year was a trial of an extended "daylight saving" period. # This trial was deemed successful and the extended period adopted for # subsequent years (with the addition of a further week at the start). # source - phone call to Ministry of Internal Affairs Head Office. # From George Shepherd via Simon Woodhead via Robert Elz (1991-03-06): # # The Country of New Zealand (Australia's east island -) Gee they hate that! # # or is Australia the west island of N.Z. # # [ courtesy of Geoff Tribble.. Auckland N.Z. ] # # [ Nov 1990 ] # ... # Rule NZ 1974 1988 - Oct lastSun 2:00 1:00 D # Rule NZ 1989 max - Oct Sun>=1 2:00 1:00 D # Rule NZ 1975 1989 - Mar Sun>=1 3:00 0 S # Rule NZ 1990 max - Mar lastSun 3:00 0 S # ... # Zone NZ 12:00 NZ NZ%sT # New Zealand # Zone NZ-CHAT 12:45 - NZ-CHAT # Chatham Island # From Arthur David Olson (1992-03-08): # The chosen rules use the Davies October 8 values for the start of DST in 1989 # rather than the October 1 value. # From Paul Eggert (1995-12-19); # Shank & Pottenger report 2:00 for all autumn changes in Australia and NZ. # Robert Uzgalis writes that the New Zealand Daylight # Savings Time Order in Council dated 1990-06-18 specifies 2:00 standard # time on both the first Sunday in October and the third Sunday in March. # As with Australia, we'll assume the tradition is 2:00s, not 2:00. # # From Paul Eggert (2006-03-22): # The Department of Internal Affairs (DIA) maintains a brief history, # as does Carol Squires; see tz-link.html for the full references. # Use these sources in preference to Shanks & Pottenger. # # For Chatham, IATA SSIM (1991/1999) gives the NZ rules but with # transitions at 2:45 local standard time; this confirms that Chatham # is always exactly 45 minutes ahead of Auckland. # From Colin Sharples (2007-04-30): # DST will now start on the last Sunday in September, and end on the # first Sunday in April. The changes take effect this year, meaning # that DST will begin on 2007-09-30 2008-04-06. # http://www.dia.govt.nz/diawebsite.nsf/wpg_URL/Services-Daylight-Saving-Daylight-saving-to-be-extended # From Paul Eggert (2014-07-14): # Chatham Island time was formally standardized on 1957-01-01 by # New Zealand's Standard Time Amendment Act 1956 (1956-10-26). # https://www.austlii.edu.au/nz/legis/hist_act/staa19561956n100244.pdf # According to Google Books snippet view, a speaker in the New Zealand # parliamentary debates in 1956 said "Clause 78 makes provision for standard # time in the Chatham Islands. The time there is 45 minutes in advance of New # Zealand time. I understand that is the time they keep locally, anyhow." # For now, assume this practice goes back to the introduction of standard time # in New Zealand, as this would make Chatham Islands time almost exactly match # LMT back when New Zealand was at UT +11:30; also, assume Chatham Islands did # not observe New Zealand's prewar DST. ############################################################################### # Bonin (Ogasawara) Islands and Marcus Island (Minami-Tori-shima) # From Wakaba (2019-01-28) via Phake Nick: # National Diet Library of Japan has several reports by Japanese Government # officers that describe the time used in islands when they visited there. # According to them (and other sources such as newspapers), standard time UTC # + 10 (JST + 1) and DST UTC + 11 (JST + 2) was used until its return to Japan # at 1968-06-26 00:00 JST. The exact periods of DST are still unknown. # I guessed Guam, Mariana, and Bonin and Marcus districts might have # synchronized their DST periods, but reports imply they had their own # decisions, i.e. there were three or more different time zones.... # # https://wiki.suikawiki.org/n/小笠原諸島の標準時 # From Phake Nick (2019-02-12): # Because their last time change to return to Japanese time when they returned # to Japanese rule was right before 1970, ... per the current tz database # rule, the information doesn't warrant creation of a new timezone for Bonin # Islands itself and is thus as an anecdotal note for interest purpose only. # ... [The link mentioned above] described some special timekeeping phenomenon # regarding Marcus island, another remote island currently owned by Japanese # in the same administrative unit as Bonin Islands. Many reports claim that # the American coastal guard on the American quarter of the island use its own # coastal guard time, and most sources describe the time as UTC+11, being two # hours faster than JST used by some Japanese personnel on the island. Some # sites describe it as same as Wake Island/Guam time although it would be # incorrect to be same as Guam. And then in a few Japanese governmental # report from 1980s (from National Institute of Information and Communications # Technology) regarding the construction of VLBI facility on the Marcus # Island, it claimed that there are three time standards being used on the # island at the time which include not just JST (UTC+9) or [US]CG time # (UTC+11) but also a JMSDF time (UTC+10) (Japan Maritime Self-Defense # Force). Unfortunately there are no other sources that mentioned such time # and there are also no information on things like how the time was used. # Fiji # Howse writes (p 153) that in 1879 the British governor of Fiji # enacted an ordinance standardizing the islands on Antipodean Time # instead of the American system (which was one day behind). # From Rives McDow (1998-10-08): # Fiji will introduce DST effective 0200 local time, 1998-11-01 # until 0300 local time 1999-02-28. Each year the DST period will # be from the first Sunday in November until the last Sunday in February. # From Paul Eggert (2000-01-08): # IATA SSIM (1999-09) says DST ends 0100 local time. Go with McDow. # From the BBC World Service in # http://news.bbc.co.uk/2/hi/asia-pacific/205226.stm (1998-10-31 16:03 UTC): # The Fijian government says the main reasons for the time change is to # improve productivity and reduce road accidents.... [T]he move is also # intended to boost Fiji's ability to attract tourists to witness the dawning # of the new millennium. # http://www.fiji.gov.fj/press/2000_09/2000_09_13-05.shtml (2000-09-13) # reports that Fiji has discontinued DST. # Kiribati # From Paul Eggert (1996-01-22): # Today's _Wall Street Journal_ (page 1) reports that Kiribati # "declared it the same day [throughout] the country as of Jan. 1, 1995" # as part of the competition to be first into the 21st century. # From Kerry Shetline (2018-02-03): # December 31 was the day that was skipped, so that the transition # would be from Friday December 30, 1994 to Sunday January 1, 1995. # From Paul Eggert (2018-02-04): # One source for this is page 202 of: Bartky IR. One Time Fits All: # The Campaigns for Global Uniformity (2007). # Kanton # From Paul Eggert (2021-05-27): # Kiribati's +13 timezone is represented by Kanton, its only populated # island. (It was formerly spelled "Canton", but Gilbertese lacks "C".) # Kanton was settled on 1937-08-31 by two British radio operators # ; # Americans came the next year and built an airfield, partly to # establish airline service and perhaps partly anticipating the # next war. Aside from the war, the airfield was used by commercial # airlines until long-range jets became standard; although currently # for emergency use only, China says it is considering rebuilding the # airfield for high-end niche tourism. Kanton has about two dozen # people, caretakers who rotate in from the rest of Kiribati in 2-5 # year shifts, and who use some of the leftover structures # . # Kwajalein # From an AP article (1993-08-22): # "The nearly 3,000 Americans living on this remote Pacific atoll have a good # excuse for not remembering Saturday night: there wasn't one. Residents were # going to bed Friday night and waking up Sunday morning because at midnight # -- 8 A.M. Eastern daylight time on Saturday -- Kwajalein was jumping from # one side of the international date line to the other." # "In Marshall Islands, Friday is followed by Sunday", NY Times. 1993-08-22. # https://www.nytimes.com/1993/08/22/world/in-marshall-islands-friday-is-followed-by-sunday.html # From Paul Eggert (2022-03-31): # Phake Nick (2018-10-27) noted 's # citation of a 1993 AP article published in the New York Times saying # Kwajalein synchronized its day with the US mainland about 40 years earlier. # However the AP article is vague and possibly wrong about this. The article # says the earlier switch was "about 40 years ago when the United States # Army established a missile test range here". However, the Kwajalein Test # Center was established on 1960-10-01 and was run by the US Navy. It was # transferred to the US Army on 1964-07-01. See "Seize the High Ground" # . # Given that Shanks was right on the money about the 1993 change, I'm inclined # to take Shanks's word for the 1969 change unless we find better evidence. # N Mariana Is, Guam # From Phake Nick (2018-10-27): # Guam Island was briefly annexed by Japan during ... year 1941-1944 ... # however there are no detailed information about what time it use during that # period. It would probably be reasonable to assume Guam use GMT+9 during # that period of time like the surrounding area. # From Paul Eggert (2023-01-23): # Howse writes (p 153) "The Spaniards, on the other hand, reached the # Philippines and the Ladrones from America," and implies that the Ladrones # (now called the Marianas) kept American date for quite some time. # For now, we assume the Ladrones switched at the same time as the Philippines; # see Asia/Manila. # # Use 1941-12-10 and 1944-07-31 for Guam WWII transitions, as the rough start # and end of Japanese control of Agana. We don't know whether the Northern # Marianas followed Guam's DST rules from 1959 through 1977; for now, assume # they did as that avoids the need for a separate zone due to our 1970 cutoff. # # US Public Law 106-564 (2000-12-23) made UT +10 the official standard time, # under the name "Chamorro standard time". There is no official abbreviation, # but Congressman Robert A. Underwood, author of the bill that became law, # wrote in a press release (2000-12-27) that he will seek the use of "ChST". # See also the commentary for Micronesia. # Marshall Is # See the commentary for Micronesia. # Micronesia (and nearby) # From Paul Eggert (2018-11-18): # Like the Ladrones (see Guam commentary), assume the Spanish East Indies # kept American time until the Philippines switched at the end of 1844. # From Paul Eggert (1999-10-29): # The Federated States of Micronesia Visitors Board writes in # The Federated States of Micronesia - Visitor Information (1999-01-26) # http://www.fsmgov.org/info/clocks.html # that Truk and Yap are UT +10, and Ponape and Kosrae are +11. # We don't know when Kosrae switched from +12; assume January 1 for now. # From Phake Nick (2018-10-27): # # From a Japanese wiki site https://wiki.suikawiki.org/n/南洋群島の標準時 # ... # For "Southern Islands" (modern region of Mariana + Palau + Federation of # Micronesia + Marshall Islands): # # A 1906 Japanese magazine shown the Caroline Islands and Mariana Islands # who was occupied by Germany at the time as GMT+10, together with the like # of German New Guinea. However there is a marking saying it have not been # implemented (yet). No further information after that were found. # # Japan invaded those islands in 1914, and records shows that they were # instructed to use JST at the time. # # 1915 January telecommunication record on the Jaluit Atoll shows they use # the meridian of 170E as standard time (GMT+11:20), which is similar to the # longitude of the atoll. # 1915 February record say the 170E standard time is to be used until # February 9 noon, and after February 9 noon they are to use JST. # However these are time used within the Japanese Military at the time and # probably does not reflect the time used by local resident at the time (that # is if they keep their own time back then) # # In January 1919 the occupying force issued a command that split the area # into three different timezone with meridian of 135E, 150E, 165E (JST+0, +1, # +2), and the command was to become effective from February 1 of the same # year. Despite the target of the command is still only for the occupying # force itself, further publication have described the time as the standard # time for the occupied area and thus it can probably be seen as such. # * Area that use meridian of 135E: Palau and Yap civil administration area # (Southern Islands Western Standard Time) # * Area that use meridian of 150E: Truk (Chuuk) and Saipan civil # administration area (Southern Islands Central Standard Time) # * Area that use meridian of 165E: Ponape (Pohnpei) and Jaluit civil # administration area (Southern Islands Eastern Standard Time). # * In the next few years Japanese occupation of those islands have been # formalized via League of Nation Mandate (South Pacific Mandate) and formal # governance structure have been established, these district [become # subprefectures] and timezone classification have been inherited as standard # time of the area. # * Saipan subprefecture include Mariana islands (exclude Guam which was # occupied by America at the time), Palau and Yap subprefecture rule the # Western Caroline Islands with 137E longitude as border, Truk and Ponape # subprefecture rule the Eastern Caroline Islands with 154E as border, Ponape # subprefecture also rule part of Marshall Islands to the west of 164E # starting from (1918?) and Jaluit subprefecture rule the rest of the # Marshall Islands. # # And then in year 1937, an announcement was made to change the time in the # area into 2 timezones: # * Area that use meridian of 135E: area administered by Palau, Yap and # Saipan subprefecture (Southern Islands Western Standard Time) # * Area that use meridian of 150E: area administered by Truk (Chuuk), # Ponape (Pohnpei) and Jaluit subprefecture (Southern Islands Eastern # Standard Time) # # Another announcement issued in 1941 say that on April 1 that year, # standard time of the Southern Islands would be changed to use the meridian # of 135E (GMT+9), and thus abolishing timezone different within the area. # # Then Pacific theater of WWII started and Japan slowly lose control on the # island. The webpage I linked above contain no information during this # period of time.... # # After the end of WWII, in 1946 February, a document written by the # (former?) Japanese military personnel describe there are 3 hours time # different between Caroline islands time/Wake island time and the Chungking # time, which would mean the time being used there at the time was GMT+10. # # After that, the area become Trust Territories of the Pacific Islands # under American administration from year 1947. The site listed some # American/International books/maps/publications about time used in those # area during this period of time but they doesn't seems to be reliable # information so it would be the best if someone know where can more reliable # information can be found. # # # From Paul Eggert (2018-11-18): # # For the above, use vague dates like "1914" and "1945" for transitions that # plausibly exist but for which the details are not known. The information # for Wake is too sketchy to act on. # # The 1906 GMT+10 info about German-controlled islands might not have been # done, so omit it from the data for now. # # The Jaluit info governs Kwajalein. # Midway # From Charles T O'Connor, KMTH DJ (1956), # quoted in the KTMH section of the Radio Heritage Collection # (2002-12-31): # For the past two months we've been on what is known as Daylight # Saving Time. This time has put us on air at 5am in the morning, # your time down there in New Zealand. Starting September 2, 1956 # we'll again go back to Standard Time. This'll mean that we'll go to # air at 6am your time. # # From Paul Eggert (2003-03-23): # We don't know the date of that quote, but we'll guess they # started DST on June 3. Possibly DST was observed other years # in Midway, but we have no record of it. # Nauru # From Phake Nick (2018-10-31): # Currently, the tz database say Nauru use LMT until 1921, and then # switched to GMT+11:30 for the next two decades. # However, a number of timezone map published in America/Japan back then # showed its timezone as GMT+11 per https://wiki.suikawiki.org/n/ナウルの標準時 # And it would also be nice if the 1921 transition date could be sourced. # ... # The "Nauru Standard Time Act 1978 Time Change" # http://ronlaw.gov.nr/nauru_lpms/files/gazettes/4b23a17d2030150404db7a5fa5872f52.pdf#page=3 # based on "Nauru Standard Time Act 1978 Time Change" # http://www.paclii.org/nr/legis/num_act/nsta1978207/ defined that "Nauru # Alternative Time" (GMT+12) should be in effect from 1979 Feb. # # From Paul Eggert (2018-11-19): # The 1921-01-15 introduction of standard time is in Shanks; it is also in # "Standard Time Throughout the World", US National Bureau of Standards (1935), # https://nvlpubs.nist.gov/nistpubs/Legacy/circ/nbscircular406.pdf # page 3, which does not give the UT offset. In response to a comment by # Phake Nick I set the Nauru time of occupation by Japan to # 1942-08-29/1945-09-08 by using dates from: # https://en.wikipedia.org/wiki/Japanese_occupation_of_Nauru # Norfolk # From Alexander Krivenyshev (2015-09-23): # Norfolk Island will change ... from +1130 to +1100: # https://www.comlaw.gov.au/Details/F2015L01483/Explanatory%20Statement/Text # ... at 12.30 am (by legal time in New South Wales) on 4 October 2015. # http://www.norfolkisland.gov.nf/nia/MediaRelease/Media%20Release%20Norfolk%20Island%20Standard%20Time%20Change.pdf # From Paul Eggert (2019-08-28): # Transitions before 2015 are from timeanddate.com, which consulted # the Norfolk Island Museum and the Australian Bureau of Meteorology's # Norfolk Island station, and found no record of Norfolk observing DST # other than in 1974/5. See: # https://www.timeanddate.com/time/australia/norfolk-island.html # However, disagree with timeanddate about the 1975-03-02 transition; # timeanddate has 02:00 but 02:00s corresponds to what the NSW law said # (thanks to Michael Deckers). # Norfolk started observing Australian DST in spring 2019. # From Kyle Czech (2019-08-13): # https://www.legislation.gov.au/Details/F2018L01702 # From Michael Deckers (2019-08-14): # https://www.legislation.gov.au/Details/F2019C00010 # Palau # See commentary for Micronesia. # Pitcairn # From Rives McDow (1999-11-08): # A Proclamation was signed by the Governor of Pitcairn on the 27th March 1998 # with regard to Pitcairn Standard Time. The Proclamation is as follows. # # The local time for general purposes in the Islands shall be # Co-ordinated Universal time minus 8 hours and shall be known # as Pitcairn Standard Time. # # ... I have also seen Pitcairn listed as UTC minus 9 hours in several # references, and can only assume that this was an error in interpretation # somehow in light of this proclamation. # From Rives McDow (1999-11-09): # The Proclamation regarding Pitcairn time came into effect on 27 April 1998 # ... at midnight. # From Howie Phelps (1999-11-10), who talked to a Pitcairner via shortwave: # Betty Christian told me yesterday that their local time is the same as # Pacific Standard Time. They used to be ½ hour different from us here in # Sacramento but it was changed a couple of years ago. # (Western) Samoa and American Samoa # Howse writes (p 153) that after the 1879 standardization on Antipodean # time by the British governor of Fiji, the King of Samoa decided to change # "the date in his kingdom from the Antipodean to the American system, # ordaining - by a masterpiece of diplomatic flattery - that # the Fourth of July should be celebrated twice in that year." # This happened in 1892, according to the Evening News (Sydney) of 1892-07-20. # https://webspace.science.uu.nl/~gent0113/idl/idl_alaska_samoa.htm # Although Shanks & Pottenger says they both switched to UT -11:30 # in 1911, and to -11 in 1950, many earlier sources give -11 # for American Samoa, e.g., the US National Bureau of Standards # circular "Standard Time Throughout the World", 1932. # https://nvlpubs.nist.gov/nistpubs/Legacy/circ/nbscircular399.pdf # Assume American Samoa switched to -11 in 1911, not 1950, # and that after 1950 they agreed until (western) Samoa skipped a # day in 2011. Assume also that the Samoas follow the US and New # Zealand's "ST"/"DT" style of daylight-saving abbreviations. # Tonga # From Paul Eggert (2021-03-04): # In 1943 "The standard time kept is 12 hrs. 19 min. 12 sec. fast # on Greenwich mean time." according to the Admiralty's Hydrographic # Dept., Pacific Islands Pilot, Vol. II, 7th ed., 1943, p 360. # From Michael Deckers (2021-03-03): # [Ian R Bartky: "One Time Fits All: The Campaigns for Global Uniformity". # Stanford University Press. 2007. p. 255]: # On 10 September 1945 Tonga adopted a standard time 12 hours, # 20 minutes in advance of Greenwich. # From Paul Eggert (1996-01-22): # Today's _Wall Street Journal_ (p 1) reports that "Tonga has been plotting # to sneak ahead of [New Zealanders] by introducing daylight-saving time." # Since Kiribati has moved the Date Line it's not clear what Tonga will do. # Don Mundell writes in the 1997-02-20 Tonga Chronicle # How Tonga became 'The Land where Time Begins': # http://www.tongatapu.net.to/tonga/homeland/timebegins.htm # # Until 1941 Tonga maintained a standard time 50 minutes ahead of NZST # 12 hours and 20 minutes ahead of GMT. When New Zealand adjusted its # standard time in 1940s, Tonga had the choice of subtracting from its # local time to come on the same standard time as New Zealand or of # advancing its time to maintain the differential of 13° # (approximately 50 minutes ahead of New Zealand time). # # Because His Majesty King Tāufaʻāhau Tupou IV, then Crown Prince # Tungī, preferred to ensure Tonga's title as the land where time # begins, the Legislative Assembly approved the latter change. # # But some of the older, more conservative members from the outer # islands objected. "If at midnight on Dec. 31, we move ahead 40 # minutes, as your Royal Highness wishes, what becomes of the 40 # minutes we have lost?" # # The Crown Prince, presented an unanswerable argument: "Remember that # on the World Day of Prayer, you would be the first people on Earth # to say your prayers in the morning." # # From Tim Parenti (2021-09-13), per Paul Eggert (2006-03-22) and Michael # Deckers (2021-03-03): # Mundell places the transition from +12:20 to +13 in 1941, while Shanks & # Pottenger say the transition was on 1968-10-01. # # The Air Almanac published contemporaneous tables of standard times, # which listed +12:20 as of Nov 1960 and +13 as of Mar 1961: # Nov 1960: https://books.google.com/books?id=bVgtWM6kPZUC&pg=SL1-PA19 # Mar 1961: https://books.google.com/books?id=W2nItAul4g0C&pg=SL1-PA19 # (Thanks to P Chan for pointing us toward these sources.) # This agrees with Bartky, who writes that "since 1961 [Tonga's] official time # has been thirteen hours in advance of Greenwich time" (p. 202) and further # writes in an endnote that this was because "the legislation was amended" on # 1960-10-19. (p. 255) # # Without greater specificity, presume that Bartky and the Air Almanac point to # a 1961-01-01 transition, as Tāufaʻāhau Tupou IV was still Crown Prince in # 1961 and this still jives with the gist of Mundell's telling, and go with # this over Shanks & Pottenger. # From Eric Ulevik (1999-05-03): # Tonga's director of tourism, who is also secretary of the National Millennium # Committee, has a plan to get Tonga back in front. # He has proposed a one-off move to tropical daylight saving for Tonga from # October to March, which has won approval in principle from the Tongan # Government. # From Steffen Thorsen (1999-09-09): # * Tonga will introduce DST in November # # I was given this link by John Letts: # http://news.bbc.co.uk/hi/english/world/asia-pacific/newsid_424000/424764.stm # # I have not been able to find exact dates for the transition in November # yet. By reading this article it seems like Fiji will be 14 hours ahead # of UTC as well, but as far as I know Fiji will only be 13 hours ahead # (12 + 1 hour DST). # From Arthur David Olson (1999-09-20): # According to : # "Daylight Savings Time will take effect on Oct. 2 through April 15, 2000 # and annually thereafter from the first Saturday in October through the # third Saturday of April. Under the system approved by Privy Council on # Sept. 10, clocks must be turned ahead one hour on the opening day and # set back an hour on the closing date." # Alas, no indication of the time of day. # From Rives McDow (1999-10-06): # Tonga started its Daylight Saving on Saturday morning October 2nd at 0200am. # Daylight Saving ends on April 16 at 0300am which is Sunday morning. # From Steffen Thorsen (2000-10-31): # Back in March I found a notice on the website http://www.tongaonline.com # that Tonga changed back to standard time one month early, on March 19 # instead of the original reported date April 16. Unfortunately, the article # is no longer available on the site, and I did not make a copy of the # text, and I have forgotten to report it here. # (Original URL was ) # From Rives McDow (2000-12-01): # Tonga is observing DST as of 2000-11-04 and will stop on 2001-01-27. # From Sione Moala-Mafi (2001-09-20) via Rives McDow: # At 2:00am on the first Sunday of November, the standard time in the Kingdom # shall be moved forward by one hour to 3:00am. At 2:00am on the last Sunday # of January the standard time in the Kingdom shall be moved backward by one # hour to 1:00am. # From Pulu ʻAnau (2002-11-05): # The law was for 3 years, supposedly to get renewed. It wasn't. # From Pulu ʻAnau (2016-10-27): # http://mic.gov.to/news-today/press-releases/6375-daylight-saving-set-to-run-from-6-november-2016-to-15-january-2017 # Cannot find anyone who knows the rules, has seen the duration or has seen # the cabinet decision, but it appears we are following Fiji's rule set. # # From Tim Parenti (2016-10-26): # Assume Tonga will observe DST from the first Sunday in November at 02:00 # through the third Sunday in January at 03:00, like Fiji, for now. # From David Wade (2017-10-18): # In August government was dissolved by the King. The current prime minister # continued in office in care taker mode. It is easy to see that few # decisions will be made until elections 16th November. # # From Paul Eggert (2017-10-18): # For now, guess that DST is discontinued. That's what the IATA is guessing. ############################################################################### # The International Date Line # From Gwillim Law (2000-01-03): # # The International Date Line is not defined by any international standard, # convention, or treaty. Mapmakers are free to draw it as they please. # Reputable mapmakers will simply ensure that every point of land appears on # the correct side of the IDL, according to the date legally observed there. # # When Kiribati adopted a uniform date in 1995, thereby moving the Phoenix and # Line Islands to the west side of the IDL (or, if you prefer, moving the IDL # to the east side of the Phoenix and Line Islands), I suppose that most # mapmakers redrew the IDL following the boundary of Kiribati. Even that line # has a rather arbitrary nature. The straight-line boundaries between Pacific # island nations that are shown on many maps are based on an international # convention, but are not legally binding national borders.... The date is # governed by the IDL; therefore, even on the high seas, there may be some # places as late as fourteen hours later than UTC. And, since the IDL is not # an international standard, there are some places on the high seas where the # correct date is ambiguous. # From Wikipedia (2023-01-23): # The nautical time zone system is analogous to the terrestrial time zone # system for use on high seas. Under the system time changes are required for # changes of longitude in one-hour steps. The one-hour step corresponds to a # time zone width of 15° longitude. The 15° gore that is offset from GMT or # UT1 (not UTC) by twelve hours is bisected by the nautical date line into two # 7°30' gores that differ from GMT by ±12 hours. A nautical date line is # implied but not explicitly drawn on time zone maps. It follows the 180th # meridian except where it is interrupted by territorial waters adjacent to # land, forming gaps: it is a pole-to-pole dashed line. # From Paul Eggert (2023-01-23): # The American Practical Navigator , # 2019 edition, merely says that the International Date Line # "coincides with the 180th meridian over most of its length." pr0m1th3as-datatypes-9c9a8d3/inst/tzdata/backward000066400000000000000000000274071522766574100220340ustar00rootroot00000000000000# Links and zones for backward compatibility # This file is in the public domain, so clarified as of # 2009-05-17 by Arthur David Olson. # This file provides links from old or merged timezone names to current ones. # It also provides a few zone entries for old naming conventions. # Many names changed in 1993 and in 1995, and many merged names moved here # in the period from 2013 through 2022. Several of these names are # also present in the file 'backzone', which has data important only # for pre-1970 timestamps and so is out of scope for tzdb proper. # Although this file is optional and tzdb will work if you omit it by # building with 'make BACKWARD=', in practice downstream users # typically use this file for backward compatibility. # This file is divided into sections, one for each major reason for a # backward compatibility link. Each section is sorted by link name. # A "#= TARGET1" comment labels each link inserted only because some # .zi parsers (including tzcode through 2022e) mishandle links to links. # The comment says what the target would be if these parsers were fixed # so that data could contain links to links. For example, the line # "Link Australia/Sydney Australia/ACT #= Australia/Canberra" would be # "Link Australia/Canberra Australia/ACT" were it not that data lines # refrain from linking to links like Australia/Canberra, which means # the Australia/ACT line links instead to Australia/Sydney, # Australia/Canberra's target. # Pre-1993 naming conventions # Link TARGET LINK-NAME #= TARGET1 Link Australia/Sydney Australia/ACT #= Australia/Canberra Link Australia/Lord_Howe Australia/LHI Link Australia/Sydney Australia/NSW Link Australia/Darwin Australia/North Link Australia/Brisbane Australia/Queensland Link Australia/Adelaide Australia/South Link Australia/Hobart Australia/Tasmania Link Australia/Melbourne Australia/Victoria Link Australia/Perth Australia/West Link Australia/Broken_Hill Australia/Yancowinna Link America/Rio_Branco Brazil/Acre #= America/Porto_Acre Link America/Noronha Brazil/DeNoronha Link America/Sao_Paulo Brazil/East Link America/Manaus Brazil/West Link Europe/Brussels CET Link America/Chicago CST6CDT Link America/Halifax Canada/Atlantic Link America/Winnipeg Canada/Central # This line is commented out, as the name exceeded the 14-character limit # and was an unused misnomer. #Link America/Regina Canada/East-Saskatchewan Link America/Toronto Canada/Eastern Link America/Edmonton Canada/Mountain Link America/St_Johns Canada/Newfoundland Link America/Vancouver Canada/Pacific Link America/Regina Canada/Saskatchewan Link America/Whitehorse Canada/Yukon Link America/Santiago Chile/Continental Link Pacific/Easter Chile/EasterIsland Link America/Havana Cuba Link Europe/Athens EET Link America/Panama EST Link America/New_York EST5EDT Link Africa/Cairo Egypt Link Europe/Dublin Eire # Vanguard section, for most .zi parsers. #Link GMT Etc/GMT #Link GMT Etc/GMT+0 #Link GMT Etc/GMT-0 #Link GMT Etc/GMT0 #Link GMT Etc/Greenwich # Rearguard section, for TZUpdater 2.3.2 and earlier. Link Etc/GMT Etc/GMT+0 Link Etc/GMT Etc/GMT-0 Link Etc/GMT Etc/GMT0 Link Etc/GMT Etc/Greenwich # End of rearguard section. Link Etc/UTC Etc/UCT Link Etc/UTC Etc/Universal Link Etc/UTC Etc/Zulu Link Europe/London GB Link Europe/London GB-Eire # Vanguard section, for most .zi parsers. #Link GMT GMT+0 #Link GMT GMT-0 #Link GMT GMT0 #Link GMT Greenwich # Rearguard section, for TZUpdater 2.3.2 and earlier. Link Etc/GMT GMT+0 Link Etc/GMT GMT-0 Link Etc/GMT GMT0 Link Etc/GMT Greenwich # End of rearguard section. Link Asia/Hong_Kong Hongkong Link Africa/Abidjan Iceland #= Atlantic/Reykjavik Link Asia/Tehran Iran Link Asia/Jerusalem Israel Link America/Jamaica Jamaica Link Asia/Tokyo Japan Link Pacific/Kwajalein Kwajalein Link Africa/Tripoli Libya Link Europe/Brussels MET Link America/Phoenix MST Link America/Denver MST7MDT Link America/Tijuana Mexico/BajaNorte Link America/Mazatlan Mexico/BajaSur Link America/Mexico_City Mexico/General Link Pacific/Auckland NZ Link Pacific/Chatham NZ-CHAT Link America/Denver Navajo #= America/Shiprock Link Asia/Shanghai PRC Link Europe/Warsaw Poland Link Europe/Lisbon Portugal Link Asia/Taipei ROC Link Asia/Seoul ROK Link Asia/Singapore Singapore Link Europe/Istanbul Turkey Link Etc/UTC UCT Link America/Anchorage US/Alaska Link America/Adak US/Aleutian Link America/Phoenix US/Arizona Link America/Chicago US/Central Link America/Indiana/Indianapolis US/East-Indiana Link America/New_York US/Eastern Link Pacific/Honolulu US/Hawaii Link America/Indiana/Knox US/Indiana-Starke Link America/Detroit US/Michigan Link America/Denver US/Mountain Link America/Los_Angeles US/Pacific Link Pacific/Pago_Pago US/Samoa Link Etc/UTC UTC Link Etc/UTC Universal Link Europe/Moscow W-SU Link Etc/UTC Zulu # Two-part names that were renamed mostly to three-part names in 1995 # Link TARGET LINK-NAME #= TARGET1 Link America/Argentina/Buenos_Aires America/Buenos_Aires Link America/Argentina/Catamarca America/Catamarca Link America/Argentina/Cordoba America/Cordoba Link America/Indiana/Indianapolis America/Indianapolis Link America/Argentina/Jujuy America/Jujuy Link America/Indiana/Knox America/Knox_IN Link America/Kentucky/Louisville America/Louisville Link America/Argentina/Mendoza America/Mendoza Link America/Puerto_Rico America/Virgin #= America/St_Thomas Link Pacific/Pago_Pago Pacific/Samoa # Pre-2013 practice, which typically had a Zone per zone.tab line # Link TARGET LINK-NAME Link Africa/Abidjan Africa/Accra Link Africa/Nairobi Africa/Addis_Ababa Link Africa/Nairobi Africa/Asmara Link Africa/Abidjan Africa/Bamako Link Africa/Lagos Africa/Bangui Link Africa/Abidjan Africa/Banjul Link Africa/Maputo Africa/Blantyre Link Africa/Lagos Africa/Brazzaville Link Africa/Maputo Africa/Bujumbura Link Africa/Abidjan Africa/Conakry Link Africa/Abidjan Africa/Dakar Link Africa/Nairobi Africa/Dar_es_Salaam Link Africa/Nairobi Africa/Djibouti Link Africa/Lagos Africa/Douala Link Africa/Abidjan Africa/Freetown Link Africa/Maputo Africa/Gaborone Link Africa/Maputo Africa/Harare Link Africa/Nairobi Africa/Kampala Link Africa/Maputo Africa/Kigali Link Africa/Lagos Africa/Kinshasa Link Africa/Lagos Africa/Libreville Link Africa/Abidjan Africa/Lome Link Africa/Lagos Africa/Luanda Link Africa/Maputo Africa/Lubumbashi Link Africa/Maputo Africa/Lusaka Link Africa/Lagos Africa/Malabo Link Africa/Johannesburg Africa/Maseru Link Africa/Johannesburg Africa/Mbabane Link Africa/Nairobi Africa/Mogadishu Link Africa/Lagos Africa/Niamey Link Africa/Abidjan Africa/Nouakchott Link Africa/Abidjan Africa/Ouagadougou Link Africa/Lagos Africa/Porto-Novo Link America/Puerto_Rico America/Anguilla Link America/Puerto_Rico America/Antigua Link America/Puerto_Rico America/Aruba Link America/Panama America/Atikokan Link America/Puerto_Rico America/Blanc-Sablon Link America/Panama America/Cayman Link America/Phoenix America/Creston Link America/Puerto_Rico America/Curacao Link America/Puerto_Rico America/Dominica Link America/Puerto_Rico America/Grenada Link America/Puerto_Rico America/Guadeloupe Link America/Puerto_Rico America/Kralendijk Link America/Puerto_Rico America/Lower_Princes Link America/Puerto_Rico America/Marigot Link America/Puerto_Rico America/Montserrat Link America/Toronto America/Nassau Link America/Puerto_Rico America/Port_of_Spain Link America/Puerto_Rico America/St_Barthelemy Link America/Puerto_Rico America/St_Kitts Link America/Puerto_Rico America/St_Lucia Link America/Puerto_Rico America/St_Thomas Link America/Puerto_Rico America/St_Vincent Link America/Puerto_Rico America/Tortola Link Pacific/Port_Moresby Antarctica/DumontDUrville Link Pacific/Auckland Antarctica/McMurdo Link Asia/Riyadh Antarctica/Syowa Link Europe/Berlin Arctic/Longyearbyen Link Asia/Riyadh Asia/Aden Link Asia/Qatar Asia/Bahrain Link Asia/Kuching Asia/Brunei Link Asia/Singapore Asia/Kuala_Lumpur Link Asia/Riyadh Asia/Kuwait Link Asia/Dubai Asia/Muscat Link Asia/Bangkok Asia/Phnom_Penh Link Asia/Bangkok Asia/Vientiane Link Africa/Abidjan Atlantic/Reykjavik Link Africa/Abidjan Atlantic/St_Helena Link Europe/Brussels Europe/Amsterdam Link Europe/Prague Europe/Bratislava Link Europe/Zurich Europe/Busingen Link Europe/Berlin Europe/Copenhagen Link Europe/London Europe/Guernsey Link Europe/London Europe/Isle_of_Man Link Europe/London Europe/Jersey Link Europe/Belgrade Europe/Ljubljana Link Europe/Brussels Europe/Luxembourg Link Europe/Helsinki Europe/Mariehamn Link Europe/Paris Europe/Monaco Link Europe/Berlin Europe/Oslo Link Europe/Belgrade Europe/Podgorica Link Europe/Rome Europe/San_Marino Link Europe/Belgrade Europe/Sarajevo Link Europe/Belgrade Europe/Skopje Link Europe/Berlin Europe/Stockholm Link Europe/Zurich Europe/Vaduz Link Europe/Rome Europe/Vatican Link Europe/Belgrade Europe/Zagreb Link Africa/Nairobi Indian/Antananarivo Link Asia/Bangkok Indian/Christmas Link Asia/Yangon Indian/Cocos Link Africa/Nairobi Indian/Comoro Link Indian/Maldives Indian/Kerguelen Link Asia/Dubai Indian/Mahe Link Africa/Nairobi Indian/Mayotte Link Asia/Dubai Indian/Reunion Link Pacific/Port_Moresby Pacific/Chuuk Link Pacific/Tarawa Pacific/Funafuti Link Pacific/Tarawa Pacific/Majuro Link Pacific/Pago_Pago Pacific/Midway Link Pacific/Guadalcanal Pacific/Pohnpei Link Pacific/Guam Pacific/Saipan Link Pacific/Tarawa Pacific/Wake Link Pacific/Tarawa Pacific/Wallis # Non-zone.tab locations with timestamps since 1970 that duplicate # those of an existing location # Link TARGET LINK-NAME Link Africa/Abidjan Africa/Timbuktu Link America/Argentina/Catamarca America/Argentina/ComodRivadavia Link America/Adak America/Atka Link America/Panama America/Coral_Harbour Link America/Tijuana America/Ensenada Link America/Indiana/Indianapolis America/Fort_Wayne Link America/Toronto America/Montreal Link America/Toronto America/Nipigon Link America/Iqaluit America/Pangnirtung Link America/Rio_Branco America/Porto_Acre Link America/Winnipeg America/Rainy_River Link America/Argentina/Cordoba America/Rosario Link America/Tijuana America/Santa_Isabel Link America/Denver America/Shiprock Link America/Toronto America/Thunder_Bay Link America/Edmonton America/Yellowknife Link Pacific/Auckland Antarctica/South_Pole Link Asia/Ulaanbaatar Asia/Choibalsan Link Asia/Shanghai Asia/Chongqing Link Asia/Shanghai Asia/Harbin Link Asia/Urumqi Asia/Kashgar Link Asia/Jerusalem Asia/Tel_Aviv Link Europe/Berlin Atlantic/Jan_Mayen Link Australia/Sydney Australia/Canberra Link Australia/Hobart Australia/Currie Link Europe/London Europe/Belfast Link Europe/Chisinau Europe/Tiraspol Link Europe/Kyiv Europe/Uzhgorod Link Europe/Kyiv Europe/Zaporozhye Link Pacific/Kanton Pacific/Enderbury Link Pacific/Honolulu Pacific/Johnston Link Pacific/Port_Moresby Pacific/Yap Link Europe/Lisbon WET # Alternate names for the same location # Link TARGET LINK-NAME #= TARGET1 Link Africa/Nairobi Africa/Asmera #= Africa/Asmara Link America/Nuuk America/Godthab Link Asia/Ashgabat Asia/Ashkhabad Link Asia/Kolkata Asia/Calcutta Link Asia/Shanghai Asia/Chungking #= Asia/Chongqing Link Asia/Dhaka Asia/Dacca # Istanbul is in both continents. Link Europe/Istanbul Asia/Istanbul Link Asia/Kathmandu Asia/Katmandu Link Asia/Macau Asia/Macao Link Asia/Yangon Asia/Rangoon Link Asia/Ho_Chi_Minh Asia/Saigon Link Asia/Thimphu Asia/Thimbu Link Asia/Makassar Asia/Ujung_Pandang Link Asia/Ulaanbaatar Asia/Ulan_Bator Link Atlantic/Faroe Atlantic/Faeroe Link Europe/Kyiv Europe/Kiev # Classically, Cyprus is in Asia; e.g. see Herodotus, Histories, I.72. # However, for various reasons many users expect to find it under Europe. Link Asia/Nicosia Europe/Nicosia Link Pacific/Honolulu HST Link America/Los_Angeles PST8PDT Link Pacific/Guadalcanal Pacific/Ponape #= Pacific/Pohnpei Link Pacific/Port_Moresby Pacific/Truk #= Pacific/Chuuk pr0m1th3as-datatypes-9c9a8d3/inst/tzdata/backzone000066400000000000000000002131541522766574100220460ustar00rootroot00000000000000# Zones that go back beyond the scope of the tz database # This file is in the public domain. # This file is by no means authoritative; if you think you know better, # go ahead and edit the file, and please send any changes to # the public mailing list tz@iana.org for general use in the future. # For more, please see the file CONTRIBUTING in the tz distribution. # When proposing changes to this file, please use 'git format-patch' # format, either by attaching the resulting .patch file to your email, # or by using 'git send-email'. This will help maintainers save time. # From Paul Eggert (2014-10-31): # This file contains data outside the normal scope of the tz database, # in that its zones do not differ from normal tz zones after 1970. # Links in this file point to zones in this file, superseding links in # the file 'backward'. # Although zones in this file may be of some use for analyzing # pre-1970 timestamps, they are less reliable, cover only a tiny # sliver of the pre-1970 era, and cannot feasibly be improved to cover # most of the era. Because the zones are out of normal scope for the # database, less effort is put into maintaining this file. Many of # the zones were formerly in other source files, but were removed or # replaced by links as their data entries were questionable and/or they # differed from other zones only in pre-1970 timestamps. # Unless otherwise specified, the source for data through 1990 is: # Thomas G. Shanks and Rique Pottenger, The International Atlas (6th edition), # San Diego: ACS Publications, Inc. (2003). # Unfortunately this book contains many errors and cites no sources. # This file is not intended to be compiled standalone, as it # assumes rules from other files. In the tz distribution, use # 'make PACKRATDATA=backzone zones' to compile and install this file. # From Paul Eggert (2020-04-15): # The following remarks should be incorporated into this table sometime. # Patches in 'git format-patch' format would be welcome. # # From Phake Nick (2020-04-15): # ... the historical timezone data for those China zones seems to be # incorrect. The transition to GMT+8 date given there for these zones # were 1980 which also contradict the file description that they do # not disagree with normal zone after 1970. According to sources that # have also been cited in the asia file, except Xinjiang and Tibet, # they should have adopted the Beijing Time from around 1949/1950 # depends on exactly when each of those cities were taken over by the # communist army. And they should also follow the DST setting of # Asia/Shanghai after that point of time. In addition, # http://gaz.ncl.edu.tw/detail.jsp?sysid=E1091792 the document from # Chongqing Nationalist government say in year 1945 all of China # should adopt summer time due to the war (not sure whether it # continued after WWII ends)(Probably only enforced in area under # their rule at the time?) The Asia/Harbin's 1932 and 1940 entry # should also be incorrect. As per sources recorded at # https://wiki.suikawiki.org/n/%E6%BA%80%E5%B7%9E%E5%9B%BD%E3%81%AE%E6%A8%99%E6%BA%96%E6%99%82 # , in 1932 Harbin should have adopted UTC+8:00 instead of data # currently listed in the tz database according to official # announcement from Manchuko. And they should have adopted GMT+9 in # 1937 January 1st according to official announcement at the time # being cited on the webpage. # Zones are sorted by zone name. Each zone is preceded by the # name of the country that the zone is in, along with any other # commentary and rules associated with the entry. # If the zone overrides links in the main data, it # is followed by the corresponding Link lines. # If the zone overrides main-data links only when building with # PACKRATLIST=zone.tab, it is followed by a commented-out Link line # that starts with "#PACKRATLIST zone.tab". # # As explained in the zic man page, the zone columns are: # Zone NAME STDOFF RULES FORMAT [UNTIL] # and the rule columns are: # Rule NAME FROM TO - IN ON AT SAVE LETTER/S # Ghana # From P Chan (2020-11-20): # Interpretation Amendment Ordinance, 1915 (No.24 of 1915) [1915-11-02] # Ordinances of the Gold Coast, Ashanti, Northern Territories 1915, p 69-71 # https://books.google.com/books?id=ErA-AQAAIAAJ&pg=PA70 # This Ordinance added "'Time' shall mean Greenwich Mean Time" to the # Interpretation Ordinance, 1876. # # Determination of the Time Ordinance, 1919 (No. 18 of 1919) [1919-11-24] # Ordinances of the Gold Coast, Ashanti, Northern Territories 1919, p 75-76 # https://books.google.com/books?id=MbA-AQAAIAAJ&pg=PA75 # This Ordinance removed the previous definition of time and introduced DST. # # Time Determination Ordinance (Cap. 214) # The Laws of the Gold Coast (including Togoland Under British Mandate) # Vol. II (1937), p 2328 # https://books.google.com/books?id=Z7M-AQAAIAAJ&pg=PA2328 # Revised edition of the 1919 Ordinance. # # Time Determination (Amendment) Ordinance, 1940 (No. 9 of 1940) [1940-04-06] # Annual Volume of the Laws of the Gold Coast: # Containing All Legislation Enacted During Year 1940, p 22 # https://books.google.com/books?id=1ao-AQAAIAAJ&pg=PA22 # This Ordinance changed the forward transition from September to May. # # Defence (Time Determination Ordinance Amendment) Regulations, 1942 # (Regulations No. 6 of 1942) [1942-01-31, commenced on 1942-02-08] # Annual Volume of the Laws of the Gold Coast: # Containing All Legislation Enacted During Year 1942, p 48 # https://books.google.com/books?id=Das-AQAAIAAJ&pg=PA48 # These regulations advanced the [standard] time by thirty minutes. # # Defence (Time Determination Ordinance Amendment (No.2)) Regulations, # 1942 (Regulations No. 28 of 1942) [1942-04-25] # Annual Volume of the Laws of the Gold Coast: # Containing All Legislation Enacted During Year 1942, p 87 # https://books.google.com/books?id=Das-AQAAIAAJ&pg=PA87 # These regulations abolished DST and changed the time to GMT+0:30. # # Defence (Revocation) (No.4) Regulations, 1945 (Regulations No. 45 of # 1945) [1945-10-24, commenced on 1946-01-06] # Annual Volume of the Laws of the Gold Coast: # Containing All Legislation Enacted During Year 1945, p 256 # https://books.google.com/books?id=9as-AQAAIAAJ&pg=PA256 # These regulations revoked the previous two sets of Regulations. # # Time Determination (Amendment) Ordinance, 1945 (No. 18 of 1945) [1946-01-06] # Annual Volume of the Laws of the Gold Coast: # Containing All Legislation Enacted During Year 1945, p 69 # https://books.google.com/books?id=9as-AQAAIAAJ&pg=PA69 # This Ordinance abolished DST. # # Time Determination (Amendment) Ordinance, 1950 (No. 26 of 1950) [1950-07-22] # Annual Volume of the Laws of the Gold Coast: # Containing All Legislation Enacted During Year 1950, p 35 # https://books.google.com/books?id=e60-AQAAIAAJ&pg=PA35 # This Ordinance restored DST but with thirty minutes offset. # # Time Determination Ordinance (Cap. 264) # The Laws of the Gold Coast, Vol. V (1954), p 380 # https://books.google.com/books?id=Mqc-AQAAIAAJ&pg=PA380 # Revised edition of the Time Determination Ordinance. # # Time Determination (Amendment) Ordinance, 1956 (No. 21 of 1956) [1956-08-29] # Annual Volume of the Ordinances of the Gold Coast Enacted During the # Year 1956, p 83 # https://books.google.com/books?id=VLE-AQAAIAAJ&pg=PA83 # This Ordinance abolished DST. Rule Ghana 1919 only - Nov 24 0:00 0:20 +0020 Rule Ghana 1920 1942 - Jan 1 2:00 0 GMT Rule Ghana 1920 1939 - Sep 1 2:00 0:20 +0020 Rule Ghana 1940 1941 - May 1 2:00 0:20 +0020 Rule Ghana 1950 1955 - Sep 1 2:00 0:30 +0030 Rule Ghana 1951 1956 - Jan 1 2:00 0 GMT Zone Africa/Accra -0:00:52 - LMT 1915 Nov 2 0:00 Ghana %s 1942 Feb 8 0:30 - +0030 1946 Jan 6 0:00 Ghana %s # Ethiopia # From Paul Eggert (2014-07-31): # Like the Swahili of Kenya and Tanzania, many Ethiopians keep a # 12-hour clock starting at our 06:00, so their "8 o'clock" is our # 02:00 or 14:00. Keep this in mind when you ask the time in Amharic. # # Shanks & Pottenger write that Ethiopia had six narrowly spaced time # zones between 1870 and 1890, that they merged to 38E50 (2:35:20) in # 1890, and that they switched to 3:00 on 1936-05-05. Perhaps 38E50 # was for Adis Dera. Quite likely the Shanks data entries are wrong # anyway. Zone Africa/Addis_Ababa 2:34:48 - LMT 1870 2:35:20 - ADMT 1936 May 5 # Adis Dera MT 3:00 - EAT # Eritrea Zone Africa/Asmara 2:35:32 - LMT 1870 2:35:32 - AMT 1890 # Asmara Mean Time 2:35:20 - ADMT 1936 May 5 # Adis Dera MT 3:00 - EAT Link Africa/Asmara Africa/Asmera # Mali (southern) Zone Africa/Bamako -0:32:00 - LMT 1912 0:00 - GMT 1934 Feb 26 -1:00 - -01 1960 Jun 20 0:00 - GMT #PACKRATLIST zone.tab Link Africa/Bamako Africa/Timbuktu # Central African Republic Zone Africa/Bangui 1:14:20 - LMT 1912 1:00 - WAT # The Gambia # From P Chan (2020-12-09): # Standard time of GMT-1 was adopted on 1933-04-01. On 1942-02-01, GMT was # adopted as a war time measure. This was made permanent in 1946. # # Interpretation Ordinance, 1914 (No. 12 of 1914) [1914-09-29] # Interpretation Ordinance, 1933 (No. 10 of 1933) [1933-03-31] # Notice No. 5 of 1942, Colony of the Gambia Government Gazette, Vol. LIX, # No.2, 1942-01-15, p 2 # Interpretation (Amendment) Ordinance, 1946 (No. 3 of 1946) [1946-07-15] Zone Africa/Banjul -1:06:36 - LMT 1912 -1:06:36 - BMT 1933 Apr 1 # Banjul Mean Time -1:00 - -01 1942 Feb 1 0:00 0:00 - GMT # Malawi # From P Chan (2020-12-09): # In 1911, Zomba mean time was adopted as the legal time of Nyasaland. In # 1914, Zomba mean time switched from GMT+2:21:10 to GMT+2:21. On 1925-07-01, # GMT+2 was adopted. # # Interpretation and General Clauses Ordinance, 1911 (No. 12 of 1911) # [1911-07-24] # Notice No. 124 of 1914, 1914-06-30, The Nyasaland Government Gazette, Vol. # XXI, No. 8, 1914-06-30, p 122 # Interpretation and General Clauses (Amendment) Ordinance, 1925 (No. 3 of # 1925) [1925-04-02] Zone Africa/Blantyre 2:20:00 - LMT 1911 Jul 24 2:21:10 - ZMT 1914 Jun 30 # Zomba Mean Time 2:21 - ZMT 1925 Jul 1 2:00 - CAT # Republic of the Congo Zone Africa/Brazzaville 1:01:08 - LMT 1912 1:00 - WAT # Burundi Zone Africa/Bujumbura 1:57:28 - LMT 1890 2:00 - CAT # Guinea Zone Africa/Conakry -0:54:52 - LMT 1912 0:00 - GMT 1934 Feb 26 -1:00 - -01 1960 0:00 - GMT # Senegal Zone Africa/Dakar -1:09:44 - LMT 1912 -1:00 - -01 1941 Jun 0:00 - GMT # Tanzania Zone Africa/Dar_es_Salaam 2:37:08 - LMT 1931 3:00 - EAT 1948 2:45 - +0245 1961 3:00 - EAT # Djibouti Zone Africa/Djibouti 2:52:36 - LMT 1911 Jul 3:00 - EAT # Cameroon # Whitman says they switched to 1:00 in 1920; go with Shanks & Pottenger. Zone Africa/Douala 0:38:48 - LMT 1912 1:00 - WAT # Sierra Leone # From P Chan (2020-12-09): # Standard time of GMT-1 was adopted on 1913-07-01. Twenty minutes of DST was # introduce[d] in 1932 and was suspended in 1939. In 1941, GMT was adopted by # Defence Regulations. This was made permanent in 1946. # # Government Notice No. 121 of 1913, 1913-06-06, Sierra Leone Royal Gazette, # Vol. XLIV, No. 1384, 1913-06-14, p 347 # Alteration of Time Ordinance, 1932 (No. 34 of 1932) [1932-12-01] # Alteration of Time (Amendment) Ordinance, 1938 (No. 25 of 1938) [1938-11-24] # Defence Regulations (No. 9), 1939 (Regulations No. 9 of 1939), 1939-09-05 # Defence Regulations (No. 11), 1939 (Regulations No. 11 of 1939), 1939-09-27 # Defence (Amendment) (No. 17) Regulations, 1941 (Public Notice No. 157 of # 1941), 1914-12-04 # Alteration of Time (Amendment) Ordinance, 1946 (No. 2 of 1946) [1946-02-07] # From Tim Parenti (2021-03-02), per P Chan (2021-02-25): # For Sierra Leone in 1957-1962, the standard time was defined in the # Alteration of Time Ordinance, 1932 (as amended in 1946, renamed to Local Time # Ordinance in 1960 and Local Time Act in 1961). It was unamended throughout # that period. See references to "Time" in the Alphabetical Index of the # Legislation in force on the 31st day of December, # 1957: https://books.google.com/books?id=lvQ-AQAAIAAJ&pg=RA2-PA49 # 1958: https://books.google.com/books?id=4fQ-AQAAIAAJ&pg=RA2-PA50 # 1959: https://books.google.com/books?id=p_U-AQAAIAAJ&pg=RA2-PA55 # 1960: https://books.google.com/books?id=JPY-AQAAIAAJ&pg=RA3-PA37 # 1961: https://books.google.com/books?id=7vY-AQAAIAAJ&pg=RA3-PA41 # 1962: https://books.google.com/books?id=W_c-AQAAIAAJ&pg=RA3-PA44 # 1963: https://books.google.com/books?id=9vk-AQAAIAAJ&pg=RA1-PA47 # # Although Shanks & Pottenger had DST from Jun 1 00:00 to Sep 1 00:00 in this # period, many contemporaneous almanacs agree that it wasn't used: # https://mm.icann.org/pipermail/tz/2021-February/029866.html # Go with the above. Rule SL 1932 only - Dec 1 0:00 0:20 -0040 Rule SL 1933 1938 - Mar 31 24:00 0 -01 Rule SL 1933 1939 - Aug 31 24:00 0:20 -0040 Rule SL 1939 only - May 31 24:00 0 -01 Zone Africa/Freetown -0:53:00 - LMT 1882 -0:53:00 - FMT 1913 Jul 1 # Freetown MT -1:00 SL %s 1939 Sep 5 -1:00 - -01 1941 Dec 6 24:00 0:00 - GMT # Botswana # From Paul Eggert (2013-02-21): # Milne says they were regulated by the Cape Town Signal in 1899; # assume they switched to 2:00 when Cape Town did. Zone Africa/Gaborone 1:43:40 - LMT 1885 1:30 - SAST 1903 Mar 2:00 - CAT 1943 Sep 19 2:00 2:00 1:00 CAST 1944 Mar 19 2:00 2:00 - CAT # Zimbabwe Zone Africa/Harare 2:04:12 - LMT 1903 Mar 2:00 - CAT # Uganda Zone Africa/Kampala 2:09:40 - LMT 1928 Jul 3:00 - EAT 1930 2:30 - +0230 1948 2:45 - +0245 1957 3:00 - EAT # Rwanda Zone Africa/Kigali 2:00:16 - LMT 1935 Jun 2:00 - CAT # Democratic Republic of the Congo (west) Zone Africa/Kinshasa 1:01:12 - LMT 1897 Nov 9 1:00 - WAT # Gabon Zone Africa/Libreville 0:37:48 - LMT 1912 1:00 - WAT # Togo Zone Africa/Lome 0:04:52 - LMT 1893 0:00 - GMT # Angola # # From Tim Parenti (2024-07-01), per Paul Eggert (2018-02-16): # For timestamps before independence, see commentary for Europe/Lisbon. # Zone Africa/Luanda 0:52:56 - LMT 1892 0:52:04 - LMT 1911 Dec 31 23:00u # Luanda MT? 1:00 - WAT # Democratic Republic of the Congo (east) # # From Alois Treindl (2022-02-28): # My main source for its time zone history is # Henri le Corre, Régimes horaires pour l'Europe et l'Afrique. # Shanks follows le Corre. As does Françoise Schneider-Gauquelin in her book # Problèmes de l'heure résolus pour le monde entier. # Zone Africa/Lubumbashi 1:49:52 - LMT 1897 Nov 9 1:00 - WAT 1920 Apr 25 2:00 - CAT # Zambia Zone Africa/Lusaka 1:53:08 - LMT 1903 Mar 2:00 - CAT # Equatorial Guinea # # Although Shanks says that Malabo switched from UT +00 to +01 on 1963-12-15, # a Google Books search says that London Calling, Issues 432-465 (1948), p 19, # says that Spanish Guinea was at +01 back then. The Shanks data entries # are most likely wrong, but we have nothing better; use them here for now. # Zone Africa/Malabo 0:35:08 - LMT 1912 0:00 - GMT 1963 Dec 15 1:00 - WAT # Lesotho Zone Africa/Maseru 1:50:00 - LMT 1903 Mar 2:00 - SAST 1943 Sep 19 2:00 2:00 1:00 SAST 1944 Mar 19 2:00 2:00 - SAST # Eswatini (formerly Swaziland) Zone Africa/Mbabane 2:04:24 - LMT 1903 Mar 2:00 - SAST # Somalia Zone Africa/Mogadishu 3:01:28 - LMT 1893 Nov 3:00 - EAT 1931 2:30 - +0230 1957 3:00 - EAT # Niger Zone Africa/Niamey 0:08:28 - LMT 1912 -1:00 - -01 1934 Feb 26 0:00 - GMT 1960 1:00 - WAT # Mauritania Zone Africa/Nouakchott -1:03:48 - LMT 1912 0:00 - GMT 1934 Feb 26 -1:00 - -01 1960 Nov 28 0:00 - GMT # Burkina Faso Zone Africa/Ouagadougou -0:06:04 - LMT 1912 0:00 - GMT # Benin # Whitman says they switched to 1:00 in 1946, not 1934; # go with Shanks & Pottenger. Zone Africa/Porto-Novo 0:10:28 - LMT 1912 Jan 1 0:00 - GMT 1934 Feb 26 1:00 - WAT # Mali (northern) Zone Africa/Timbuktu -0:12:04 - LMT 1912 0:00 - GMT # Anguilla Zone America/Anguilla -4:12:16 - LMT 1912 Mar 2 -4:00 - AST # Antigua and Barbuda Zone America/Antigua -4:07:12 - LMT 1912 Mar 2 -5:00 - EST 1951 -4:00 - AST # Chubut, Argentina # The name "Comodoro Rivadavia" exceeds the 14-byte POSIX limit. Zone America/Argentina/ComodRivadavia -4:30:00 - LMT 1894 Oct 31 -4:16:48 - CMT 1920 May -4:00 - -04 1930 Dec -4:00 Arg -04/-03 1969 Oct 5 -3:00 Arg -03/-02 1991 Mar 3 -4:00 - -04 1991 Oct 20 -3:00 Arg -03/-02 1999 Oct 3 -4:00 Arg -04/-03 2000 Mar 3 -3:00 - -03 2004 Jun 1 -4:00 - -04 2004 Jun 20 -3:00 - -03 # Aruba Zone America/Aruba -4:40:24 - LMT 1912 Feb 12 # Oranjestad -4:30 - -0430 1965 -4:00 - AST # Atikokan, Ontario # From Paul Eggert (1997-10-17): # Mark Brader writes that an article in the 1997-10-14 Toronto Star # says that Atikokan, Ontario currently does not observe DST, # but will vote on 11-10 whether to use EST/EDT. # He also writes that the Ontario Time Act (1990, Chapter T.9) # http://www.gov.on.ca/MBS/english/publications/statregs/conttext.html # says that Ontario east of 90W uses EST/EDT, and west of 90W uses CST/CDT. # Officially Atikokan is therefore on CST/CDT, and most likely this report # concerns a non-official time observed as a matter of local practice. # # From Paul Eggert (2000-10-02): # Matthews and Vincent (1998) write that Atikokan, Pickle Lake, and # New Osnaburgh observe CST all year, that Big Trout Lake observes # CST/CDT, and that Upsala and Shebandowan observe EST/EDT, all in # violation of the official Ontario rules. # # From Paul Eggert (2006-07-09): # Chris Walton (2006-07-06) mentioned an article by Stephanie MacLellan in the # 2005-07-21 Chronicle-Journal, which said: # # The clocks in Atikokan stay set on standard time year-round. # This means they spend about half the time on central time and # the other half on eastern time. # # For the most part, the system works, Mayor Dennis Brown said. # # "The majority of businesses in Atikokan deal more with Eastern # Canada, but there are some that deal with Western Canada," he # said. "I don't see any changes happening here." # # Walton also writes "Supposedly Pickle Lake and Mishkeegogamang # [New Osnaburgh] follow the same practice." # From Garry McKinnon (2006-07-14) via Chris Walton: # I chatted with a member of my board who has an outstanding memory # and a long history in Atikokan (and in the telecom industry) and he # can say for certain that Atikokan has been practicing the current # time keeping since 1952, at least. # From Paul Eggert (2006-07-17): # Shanks & Pottenger say that Atikokan has agreed with Rainy River # ever since standard time was introduced, but the information from # McKinnon sounds more authoritative. For now, assume that Atikokan # switched to EST immediately after WWII era daylight saving time # ended. This matches the old (less populous) America/Coral_Harbour # entry since our cutoff date of 1970, so we can move # America/Coral_Harbour to the 'backward' file. Zone America/Atikokan -6:06:28 - LMT 1895 -6:00 Canada C%sT 1940 Sep 29 -6:00 1:00 CDT 1942 Feb 9 2:00s -6:00 Canada C%sT 1945 Sep 30 2:00 -5:00 - EST #PACKRATLIST zone.tab Link America/Atikokan America/Coral_Harbour # Quebec east of Natashquan # From Paul Eggert (2021-05-09): # H. David Matthews and Mary Vincent's map # "It's about TIME", _Canadian Geographic_ (September-October 1998) # http://www.canadiangeographic.ca/Magazine/SO98/alacarte.asp # says that Quebec east of the -63 meridian is supposed to observe # AST, but residents as far east as Natashquan use EST/EDT, and # residents east of Natashquan use AST. # The Quebec department of justice writes in # "The situation in Minganie and Basse-Côte-Nord" # https://www.justice.gouv.qc.ca/en/department/ministre/functions-and-responsabilities/legal-time-in-quebec/the-situation-in-minganie-and-basse-cote-nord/ # that the coastal strip from just east of Natashquan to Blanc-Sablon # observes Atlantic standard time all year round. # This common practice was codified into law as of 2007; see Legal Time Act, # CQLR c T-5.1 . # For lack of better info, guess this practice began around 1970, contra to # Shanks & Pottenger who have this region observing AST/ADT. Zone America/Blanc-Sablon -3:48:28 - LMT 1884 -4:00 Canada A%sT 1970 -4:00 - AST # Cayman Is Zone America/Cayman -5:25:32 - LMT 1890 # Georgetown -5:07:10 - KMT 1912 Feb # Kingston Mean Time -5:00 - EST # United States # # From Paul Eggert (2018-03-18): # America/Chillicothe would be tricky, as it was a city of two-timers: # "To prevent a constant mixup at Chillicothe, caused by the courthouse # clock running on central time and the city running on 'daylight saving' # time, a third hand was added to the dial of the courthouse clock." # -- Ohio news in brief. The Cedarville Herald. 1920-05-21;43(21):1 (col. 5) # https://digitalcommons.cedarville.edu/cedarville_herald/794 # Canada Zone America/Coral_Harbour -5:32:40 - LMT 1884 -5:00 NT_YK E%sT 1946 -5:00 - EST # From Chris Walton (2011-12-01): # There are two areas within the Canadian province of British Columbia # that do not currently observe daylight saving: # a) The Creston Valley (includes the town of Creston and surrounding area) # b) The eastern half of the Peace River Regional District # (includes the cities of Dawson Creek and Fort St. John) # Earlier this year I stumbled across a detailed article about the time # keeping history of Creston; it was written by Tammy Hardwick who is the # manager of the Creston & District Museum. The article was written in May 2009. # http://www.ilovecreston.com/?p=articles&t=spec&ar=260 # According to the article, Creston has not changed its clocks since June 1918. # i.e. Creston has been stuck on UT-7 for 93 years. # Dawson Creek, on the other hand, changed its clocks as recently as April 1972. # Unfortunately the exact date for the time change in June 1918 remains # unknown and will be difficult to ascertain. I e-mailed Tammy a few months # ago to ask if Sunday June 2 was a reasonable guess. She said it was just # as plausible as any other date (in June). She also said that after writing # the article she had discovered another time change in 1916; this is the # subject of another article which she wrote in October 2010. # http://www.creston.museum.bc.ca/index.php?module=comments&uop=view_comment&cm+id=56 # Here is a summary of the three clock change events in Creston's history: # 1. 1884 or 1885: adoption of Mountain Standard Time (GMT-7) # Exact date unknown # 2. Oct 1916: switch to Pacific Standard Time (GMT-8) # Exact date in October unknown; Sunday October 1 is a reasonable guess. # 3. June 1918: switch to Pacific Daylight Time (GMT-7) # Exact date in June unknown; Sunday June 2 is a reasonable guess. # note 1: # On Oct 27/1918 when daylight saving ended in the rest of Canada, # Creston did not change its clocks. # note 2: # During WWII when the Federal Government legislated a mandatory clock change, # Creston did not oblige. # note 3: # There is no guarantee that Creston will remain on Mountain Standard Time # (UTC-7) forever. # The subject was debated at least once this year by the town Council. # http://www.bclocalnews.com/kootenay_rockies/crestonvalleyadvance/news/116760809.html # During a period WWII, summer time (Daylight saying) was mandatory in Canada. # In Creston, that was handled by shifting the area to PST (-8:00) then applying # summer time to cause the offset to be -7:00, the same as it had been before # the change. It can be argued that the timezone abbreviation during this # period should be PDT rather than MST, but that doesn't seem important enough # (to anyone) to further complicate the rules. # The transition dates (and times) are guesses. Zone America/Creston -7:46:04 - LMT 1884 -7:00 - MST 1916 Oct 1 -8:00 - PST 1918 Jun 2 -7:00 - MST # Curaçao # Milne gives 4:35:46.9 for Curaçao mean time; round to nearest. # # From Paul Eggert (2006-03-22): # Shanks & Pottenger say that The Bottom and Philipsburg have been at # -4:00 since standard time was introduced on 1912-03-02; and that # Kralendijk and Rincon used Kralendijk Mean Time (-4:33:08) from # 1912-02-02 to 1965-01-01. The former is dubious, since S&P also say # Saba Island has been like Curaçao. # This all predates our 1970 cutoff, though. # # By July 2007 Curaçao and St Maarten are planned to become # associated states within the Netherlands, much like Aruba; # Bonaire, Saba and St Eustatius would become directly part of the # Netherlands as Kingdom Islands. This won't affect their time zones # though, as far as we know. # Zone America/Curacao -4:35:47 - LMT 1912 Feb 12 # Willemstad -4:30 - -0430 1965 -4:00 - AST Link America/Curacao America/Kralendijk Link America/Curacao America/Lower_Princes # Dominica Zone America/Dominica -4:05:36 - LMT 1911 Jul 1 0:01 # Roseau -4:00 - AST # Baja California # See 'northamerica' for why this entry is here rather than there. Zone America/Ensenada -7:46:28 - LMT 1922 Jan 1 0:13:32 -8:00 - PST 1927 Jun 10 23:00 -7:00 - MST 1930 Nov 16 -8:00 - PST 1942 Apr -7:00 - MST 1949 Jan 14 -8:00 - PST 1996 -8:00 Mexico P%sT # Grenada Zone America/Grenada -4:07:00 - LMT 1911 Jul # St George's -4:00 - AST # Guadeloupe Zone America/Guadeloupe -4:06:08 - LMT 1911 Jun 8 # Pointe-à-Pitre -4:00 - AST # Canada # # From Paul Eggert (2015-03-24): # Since 1970 most of Quebec has been like Toronto; see # America/Toronto. However, earlier versions of the tz database # mistakenly relied on data from Shanks & Pottenger saying that Quebec # differed from Ontario after 1970, and the following rules and zone # were created for most of Quebec from the incorrect Shanks & # Pottenger data. The post-1970 entries have been corrected, but the # pre-1970 entries are unchecked and probably have errors. # Rule Mont 1917 only - Mar 25 2:00 1:00 D Rule Mont 1917 only - Apr 24 0:00 0 S Rule Mont 1919 only - Mar 31 2:30 1:00 D Rule Mont 1919 only - Oct 25 2:30 0 S Rule Mont 1920 only - May 2 2:30 1:00 D Rule Mont 1920 1922 - Oct Sun>=1 2:30 0 S Rule Mont 1921 only - May 1 2:00 1:00 D Rule Mont 1922 only - Apr 30 2:00 1:00 D Rule Mont 1924 only - May 17 2:00 1:00 D Rule Mont 1924 1926 - Sep lastSun 2:30 0 S Rule Mont 1925 1926 - May Sun>=1 2:00 1:00 D Rule Mont 1927 1937 - Apr lastSat 24:00 1:00 D Rule Mont 1927 1937 - Sep lastSat 24:00 0 S Rule Mont 1938 1940 - Apr lastSun 0:00 1:00 D Rule Mont 1938 1939 - Sep lastSun 0:00 0 S Rule Mont 1946 1973 - Apr lastSun 2:00 1:00 D Rule Mont 1945 1948 - Sep lastSun 2:00 0 S Rule Mont 1949 1950 - Oct lastSun 2:00 0 S Rule Mont 1951 1956 - Sep lastSun 2:00 0 S Rule Mont 1957 1973 - Oct lastSun 2:00 0 S Zone America/Montreal -4:54:16 - LMT 1884 -5:00 Mont E%sT 1918 -5:00 Canada E%sT 1919 -5:00 Mont E%sT 1942 Feb 9 2:00s -5:00 Canada E%sT 1946 -5:00 Mont E%sT 1974 -5:00 Canada E%sT # Montserrat # From Paul Eggert (2006-03-22): # In 1995 volcanic eruptions forced evacuation of Plymouth, the capital. # world.gazetteer.com says Cork Hill is the most populous location now. Zone America/Montserrat -4:08:52 - LMT 1911 Jul 1 0:01 # Cork Hill -4:00 - AST # The Bahamas # # For 1899 Milne gives -5:09:29.5; round that. # # From P Chan (2020-11-27, corrected on 2020-12-02): # There were two periods of DST observed in 1942-1945: 1942-05-01 # midnight to 1944-12-31 midnight and 1945-02-01 to 1945-10-17 midnight. # "midnight" should mean 24:00 from the context. # # War Time Order 1942 [1942-05-01] and War Time (No. 2) Order 1942 [1942-09-29] # Appendix to the Statutes of 7 George VI. and the Year 1942. p 34, 43 # https://books.google.com/books?id=5rlNAQAAIAAJ&pg=RA3-PA34 # https://books.google.com/books?id=5rlNAQAAIAAJ&pg=RA3-PA43 # # War Time Order 1943 [1943-03-31] and War Time Order 1944 [1943-12-29] # Appendix to the Statutes of 8 George VI. and the Year 1943. p 9-10, 28-29 # https://books.google.com/books?id=5rlNAQAAIAAJ&pg=RA4-PA9 # https://books.google.com/books?id=5rlNAQAAIAAJ&pg=RA4-PA28 # # War Time Order 1945 [1945-01-31] and the Order which revoke War Time Order # 1945 [1945-10-16] Appendix to the Statutes of 9 George VI. and the Year # 1945. p 160, 247-248 # https://books.google.com/books?id=5rlNAQAAIAAJ&pg=RA6-PA160 # https://books.google.com/books?id=5rlNAQAAIAAJ&pg=RA6-PA247 # # From Sue Williams (2006-12-07): # The Bahamas announced about a month ago that they plan to change their DST # rules to sync with the U.S. starting in 2007.... # http://www.jonesbahamas.com/?c=45&a=10412 Rule Bahamas 1942 only - May 1 24:00 1:00 W Rule Bahamas 1944 only - Dec 31 24:00 0 S Rule Bahamas 1945 only - Feb 1 0:00 1:00 W Rule Bahamas 1945 only - Aug 14 23:00u 1:00 P # Peace Rule Bahamas 1945 only - Oct 17 24:00 0 S Rule Bahamas 1964 1975 - Oct lastSun 2:00 0 S Rule Bahamas 1964 1975 - Apr lastSun 2:00 1:00 D Zone America/Nassau -5:09:30 - LMT 1912 Mar 2 -5:00 Bahamas E%sT 1976 -5:00 US E%sT # Canada # From Chris Walton (2022-10-15): # I would also like to see America/Nipigon and America/Rainy_River converted # into link entries because I have zero faith in the current Shanks based data. # From Paul Eggert (2022-10-15): # These are now links in the primary data. Also see America/Thunder_Bay. Zone America/Nipigon -5:53:04 - LMT 1895 -5:00 Canada E%sT 1940 Sep 29 -5:00 1:00 EDT 1942 Feb 9 2:00s -5:00 Canada E%sT # From Rives McDow (1999-11-08): # On October 31, when the rest of Nunavut went to Central time, # Pangnirtung wobbled. Here is the result of their wobble: # # The following businesses and organizations in Pangnirtung use Central Time: # # First Air, Power Corp, Nunavut Construction, Health Center, RCMP, # Eastern Arctic National Parks, A & D Specialist # # The following businesses and organizations in Pangnirtung use Eastern Time: # # Hamlet office, All other businesses, Both schools, Airport operator # # This has made for an interesting situation there, which warranted the news. # No one there that I spoke with seems concerned, or has plans to # change the local methods of keeping time, as it evidently does not # really interfere with any activities or make things difficult locally. # They plan to celebrate New Year's turn-over twice, one hour apart, # so it appears that the situation will last at least that long. # The Nunavut Intergovernmental Affairs hopes that they will "come to # their senses", but the locals evidently don't see any problem with # the current state of affairs. # From Michaela Rodrigue, writing in the # Nunatsiaq News (1999-11-19): # http://www.nunatsiaqonline.ca/archives/nunavut991130/nvt91119_17.html # Clyde River, Pangnirtung and Sanikiluaq now operate with two time zones, # central - or Nunavut time - for government offices, and eastern time # for municipal offices and schools.... Igloolik [was similar but then] # made the switch to central time on Saturday, Nov. 6. # From Chris Walton (2022-11-06): # The implication is that Pangnirtung and Qikiqtarjuaq have observed Eastern # Time as far back as 1984 (and likely even further back than that). # It is possible that these communities never actually observed Atlantic # Time, but that would be difficult to prove. # From Paul Eggert (2022-11-06): # This is in 'backzone' as we have no good evidence that Pangnirtung differs # from America/Iqaluit since 1970. A Google Books snippet view of # volume 2, page 186 of "Pilot of Arctic Canada", published 1959 by # the Canadian Hydrographic Service, suggests (though does not state) # that Pangnirtung observed EST then. # # aka Panniqtuuq Zone America/Pangnirtung 0 - -00 1921 # trading post est. -5:00 NT_YK E%sT 1999 Oct 31 2:00 -6:00 Canada C%sT 2000 Oct 29 2:00 -5:00 Canada E%sT # United States # # From Paul Eggert (2018-03-18): # America/Palm_Springs would be tricky, as it kept two sets of clocks # in 1946/7. See the following notes. # # From Steve Allen (2018-01-19): # The shadow of Mt. San Jacinto brings darkness very early in the winter # months. In 1946 the chamber of commerce decided to put the clocks of Palm # Springs forward by an hour in the winter. # https://www.desertsun.com/story/life/2017/12/27/palm-springs-struggle-daylight-savings-time-and-idea-sun-time/984416001/ # Desert Sun, Number 18, 1 November 1946 # https://cdnc.ucr.edu/cgi-bin/cdnc?a=d&d=DS19461101 # has proposal for meeting on front page and page 21. # Desert Sun, Number 19, 5 November 1946 # https://cdnc.ucr.edu/cgi-bin/cdnc?a=d&d=DS19461105 # reports that Sun Time won at the meeting on front page and page 5. # Desert Sun, Number 37, 7 January 1947 # https://cdnc.ucr.edu/cgi-bin/cdnc?a=d&d=DS19470107.2.12 # front page reports request to abandon Sun Time and page 7 notes a "class war". # Desert Sun, Number 38, 10 January 1947 # https://cdnc.ucr.edu/cgi-bin/cdnc?a=d&d=DS19470110 # front page reports on end. # Trinidad and Tobago Zone America/Port_of_Spain -4:06:04 - LMT 1912 Mar 2 -4:00 - AST Link America/Port_of_Spain America/Marigot Link America/Port_of_Spain America/St_Barthelemy # Canada # From Chris Walton (2022-10-15): # I would also like to see America/Nipigon and America/Rainy_River converted # into link entries because I have zero faith in the current Shanks based data. # From Paul Eggert (2022-10-15): # These are now links in the primary data. Also see America/Thunder_Bay. Zone America/Rainy_River -6:18:16 - LMT 1895 -6:00 Canada C%sT 1940 Sep 29 -6:00 1:00 CDT 1942 Feb 9 2:00s -6:00 Canada C%sT # Argentina # This entry was intended for the following areas, but has been superseded by # more detailed zones. # Santa Fe (SF), Entre Ríos (ER), Corrientes (CN), Misiones (MN), Chaco (CC), # Formosa (FM), La Pampa (LP), Chubut (CH) Zone America/Rosario -4:02:40 - LMT 1894 Nov -4:16:44 - CMT 1920 May -4:00 - -04 1930 Dec -4:00 Arg -04/-03 1969 Oct 5 -3:00 Arg -03/-02 1991 Jul -3:00 - -03 1999 Oct 3 0:00 -4:00 Arg -04/-03 2000 Mar 3 0:00 -3:00 - -03 # St Kitts-Nevis Zone America/St_Kitts -4:10:52 - LMT 1912 Mar 2 # Basseterre -4:00 - AST # St Lucia Zone America/St_Lucia -4:04:00 - LMT 1890 # Castries -4:04:00 - CMT 1912 # Castries Mean Time -4:00 - AST # US Virgin Is Zone America/St_Thomas -4:19:44 - LMT 1911 Jul # Charlotte Amalie -4:00 - AST Link America/St_Thomas America/Virgin # St Vincent and the Grenadines Zone America/St_Vincent -4:04:56 - LMT 1890 # Kingstown -4:04:56 - KMT 1912 # Kingstown Mean Time -4:00 - AST # Canada # # From Paul Eggert (2003-07-27): # Willett (1914-03) writes (p. 17) "In the Cities of Fort William, and # Port Arthur, Ontario, the principle of the Bill has been in # operation for the past three years, and in the City of Moose Jaw, # Saskatchewan, for one year." # # From David Bryan via Tory Tronrud, Director/Curator, # Thunder Bay Museum (2003-11-12): # There is some suggestion, however, that, by-law or not, daylight # savings time was being practiced in Fort William and Port Arthur # before 1909.... [I]n 1910, the line between the Eastern and Central # Time Zones was permanently moved about two hundred miles west to # include the Thunder Bay area.... When Canada adopted daylight # savings time in 1916, Fort William and Port Arthur, having done so # already, did not change their clocks.... During the Second World # War,... [t]he cities agreed to implement DST during the summer # months for the remainder of the war years. # # From Jeffery Nichols (2020-02-06): # According to the [Shanks] atlas, those western Ontario zones are huge, # covering most of Ontario northwest of Sault Ste Marie and Timmins. # The zones seem to include towns bigger than the ones they're named after, # like Dryden in America/Rainy_River and Wawa (and maybe Attawapiskat) in # America/Nipigon. I assume it's too much trouble to change the name of the # zone (like when you found out that America/Glace_Bay includes Sydney, Nova # Scotia).... # # From Chris Walton (2022-10-15): # The TZ database currently shows that Thunder Bay has observed daylight # saving every year from 1970 onwards with the exception of 1973. # Back in July I raised some doubts on this mailing list about the 1973 data. # I now have more proof that it is wrong. # [attached Chronicle-Journal front pages, 1973-04-28 and 1973-10-27] # # From Paul Eggert (2022-10-15): # This is now a link in the primary data. The following entry is # from Shanks & Pottenger, with corrections as noted above. # Zone America/Thunder_Bay -5:57:00 - LMT 1895 -6:00 - CST 1910 -5:00 - EST 1942 -5:00 Canada E%sT 1970 -5:00 Toronto E%sT 1974 -5:00 Canada E%sT # British Virgin Is Zone America/Tortola -4:18:28 - LMT 1911 Jul # Road Town -4:00 - AST # Yellowknife, Northwest Territories Zone America/Yellowknife 0 - -00 1935 # Yellowknife founded? -7:00 NT_YK M%sT 1980 -7:00 Canada M%sT # Dumont d'Urville, Île des Pétrels, -6640+14001, since 1956-11 # (2005-12-05) # # Another base at Port-Martin, 50km east, began operation in 1947. # It was destroyed by fire on 1952-01-14. # Zone Antarctica/DumontDUrville 0 - -00 1947 10:00 - +10 1952 Jan 14 0 - -00 1956 Nov 10:00 - +10 # McMurdo, Ross Island, since 1955-12 Zone Antarctica/McMurdo 0 - -00 1956 12:00 NZ NZ%sT Link Antarctica/McMurdo Antarctica/South_Pole # Syowa, Antarctica # # From Hideyuki Suzuki (1999-02-06): # In all Japanese stations, +0300 is used as the standard time. # # Syowa station, which is the first antarctic station of Japan, # was established on 1957-01-29. Since Syowa station is still the main # station of Japan, it's appropriate for the principal location. # See: # NIPR Antarctic Research Activities (1999-08-17) # http://www.nipr.ac.jp/english/ara01.html Zone Antarctica/Syowa 0 - -00 1957 Jan 29 3:00 - +03 # Yemen # Milne says 2:59:54 was the meridian of the saluting battery at Aden, # and that Yemen was at 1:55:56, the meridian of the Hagia Sophia. Zone Asia/Aden 2:59:54 - LMT 1950 3:00 - +03 # Bahrain # # From Paul Eggert (2020-07-23): # Most of this data comes from: # Stewart A. Why Gulf Standard Time is far from standard: the fascinating story # behind the time zone's invention. The National (Abu Dhabi). 2020-07-22. # https://www.thenational.ae/arts-culture/why-gulf-standard-time-is-far-from-standard-the-fascinating-story-behind-the-time-zone-s-invention-1.1052589 # Stewart writes that before 1941 some companies in Bahrain were at +0330 and # others at +0323. Reginald George Alban, a British political agent based in # Manama, worked to standardize this, and from 1941-07-20 Bahrain was at # +0330. However, BOAC asked that clocks be moved to gain more light at day's # end, so Bahrain switched to +04 on 1944-01-01. # # Re the 1941 transition, Stewart privately sent me this citation: # "File 16/53 Enquiries Re: Calculation of Local Time", British Library: India # Office Records and Private Papers, IOR/R/15/2/1564, in Qatar Digital Library # https://www.qdl.qa/archive/81055/vdc_100000000282.0x00012b # It says there was no real standard in Bahrain before 1941-07-20. # +0330 was used by steamers of the British India Co, by Petroleum Concessions # and by Cable & Wireless; +0323 was used by the Eastern Bank Ltd, BOAC, and # Bahrein Petroleum (Bapco), and California Arabian Standard Oil Co (Casoc) # adopted DST effective 1941-05-24. Alban suggested adopting DST, R.B. Coomb # of C&W countersuggested +0330, and although C.A. Rodstrom of Casoc (formerly # of Bapco) stated that Bahrain had formerly used +0330 before Bapco arrived # but Bapco switched to +0323 because of "constant confusion", the consensus # was +0330. The government adopted +0330 in 1941-07-20 and companies seem to # have switched by 08-01. No time of day was given for the 1940s transitions. Zone Asia/Bahrain 3:22:20 - LMT 1941 Jul 20 # Manamah 3:30 - +0330 1944 Jan 1 4:00 - +04 1972 Jun 3:00 - +03 # Brunei Zone Asia/Brunei 7:39:40 - LMT 1926 Mar # Bandar Seri Begawan 7:30 - +0730 1933 8:00 - +08 # India # # From Paul Eggert (2014-09-06): # The 1876 Report of the Secretary of the [US] Navy, p 305 says that Madras # civil time was 5:20:57.3. # # From Paul Eggert (2014-08-21): # In tomorrow's The Hindu, Nitya Menon reports that India had two civil time # zones starting in 1884, one in Bombay and one in Calcutta, and that railways # used a third time zone based on Madras time (80° 18' 30" E). Also, # in 1881 Bombay briefly switched to Madras time, but switched back. See: # http://www.thehindu.com/news/cities/chennai/madras-375-when-madras-clocked-the-time/article6339393.ece #Zone Asia/Chennai [not enough info to complete] # China # Long-shu Time (probably due to Long and Shu being two names of that area) # Guangxi, Guizhou, Hainan, Ningxia, Sichuan, Shaanxi, and Yunnan; # most of Gansu; west Inner Mongolia; west Qinghai; and the Guangdong # counties Deqing, Enping, Kaiping, Luoding, Taishan, Xinxing, # Yangchun, Yangjiang, Yu'nan, and Yunfu. Zone Asia/Chongqing 7:06:20 - LMT 1928 # or Chungking 7:00 - +07 1980 May 8:00 PRC C%sT Link Asia/Chongqing Asia/Chungking # Vietnam # From Paul Eggert (2014-10-13): # See Asia/Ho_Chi_Minh for the source for this data. # Trần's book says the 1954-55 transition to 07:00 in Hanoi was in # October 1954, with exact date and time unspecified. Zone Asia/Hanoi 7:03:24 - LMT 1906 Jul 1 7:06:30 - PLMT 1911 May 1 7:00 - +07 1942 Dec 31 23:00 8:00 - +08 1945 Mar 14 23:00 9:00 - +09 1945 Sep 2 7:00 - +07 1947 Apr 1 8:00 - +08 1954 Oct 7:00 - +07 # China # Changbai Time ("Long-white Time", Long-white = Heilongjiang area) # Heilongjiang (except Mohe county), Jilin Zone Asia/Harbin 8:26:44 - LMT 1928 # or Haerbin 8:30 - +0830 1932 Mar 8:00 - CST 1940 9:00 - +09 1966 May 8:30 - +0830 1980 May 8:00 PRC C%sT # far west China Zone Asia/Kashgar 5:03:56 - LMT 1928 # or Kashi or Kaxgar 5:30 - +0530 1940 5:00 - +05 1980 May 8:00 PRC C%sT # peninsular Malaysia # taken from Mok Ly Yng (2003-10-30) # https://web.archive.org/web/20190822231045/http://www.math.nus.edu.sg/~mathelmr/teaching/timezone.html # This agrees with Singapore since 1905-06-01. Zone Asia/Kuala_Lumpur 6:46:46 - LMT 1901 Jan 1 6:55:25 - SMT 1905 Jun 1 # Singapore M.T. 7:00 - +07 1933 Jan 1 7:00 0:20 +0720 1936 Jan 1 7:20 - +0720 1941 Sep 1 7:30 - +0730 1942 Feb 16 9:00 - +09 1945 Sep 12 7:30 - +0730 1981 Dec 31 16:00u 8:00 - +08 # Kuwait Zone Asia/Kuwait 3:11:56 - LMT 1950 3:00 - +03 # Oman # Milne says 3:54:24 was the meridian of the Muscat Tidal Observatory. Zone Asia/Muscat 3:54:24 - LMT 1920 4:00 - +04 # India # From Tim Parenti (2024-07-01), per Paul Eggert (2014-08-11), after a # heads-up from Stephen Colebourne: # According to a Portuguese decree (1911-05-24), Portuguese India switched to # UT +05 on 1912-01-01 (see Europe/Lisbon). #Zone Asia/Panaji [not enough info to complete] # Cambodia # From an adoptive daughter of the late Cambodian ruler Prince Sihanouk, # via Alois Treindl (2019-08-08): # # King Sihanouk said that, during the Japanese occupation, starting with # what historians refer to as "le coup de force du 9 mars 1945", Cambodia, # like the entire French Indochina, used Tokyo time zone. After Japan # surrendered, 2 September 1945, Cambodia fell under French rule again and # adopted Hanoi time zone again. # # However, on 7 January 1946, Sihanouk and Tioulong managed to obtain a # status of "internal autonomy" from the government of Charles de Gaulle. # Although many fields remained under the administration of the French # (customs, taxes, justice, defence, foreign affairs, etc.), the Cambodian # administration was responsible for religious matters and traditional # celebrations, which included our calendar and time. The time zone was GMT # + 7 and _no_ DST was applied. # # After Sihanouk and Tioulong achieved full independence, on 9 November 1953, # GMT + 7 was maintained. # From Paul Eggert (2019-08-26): # See Asia/Ho_Chi_Minh for the source for most of rest of this data. Zone Asia/Phnom_Penh 6:59:40 - LMT 1906 Jul 1 7:06:30 - PLMT 1911 May 1 7:00 - +07 1942 Dec 31 23:00 8:00 - +08 1945 Mar 14 23:00 9:00 - +09 1945 Sep 2 7:00 - +07 # Israel Zone Asia/Tel_Aviv 2:19:04 - LMT 1880 2:21 - JMT 1918 2:00 Zion I%sT # Laos # From Paul Eggert (2014-10-11): # See Asia/Ho_Chi_Minh for the source for most of this data. # Trần's book says that Laos reverted to UT +07 on 1955-04-15. # Also, guess that Laos reverted to +07 on 1945-09-02, when Vietnam did; # this is probably wrong but it's better than guessing no transition. Zone Asia/Vientiane 6:50:24 - LMT 1906 Jul 1 7:06:30 - PLMT 1911 May 1 7:00 - +07 1942 Dec 31 23:00 8:00 - +08 1945 Mar 14 23:00 9:00 - +09 1945 Sep 2 7:00 - +07 1947 Apr 1 8:00 - +08 1955 Apr 15 7:00 - +07 # Jan Mayen # From Whitman: Zone Atlantic/Jan_Mayen -1:00 - -01 # Iceland # # From Adam David (1993-11-06): # The name of the timezone in Iceland for system / mail / news purposes is GMT. # # (1993-12-05): # This material is paraphrased from the 1988 edition of the University of # Iceland Almanak. # # From January 1st, 1908 the whole of Iceland was standardised at 1 hour # behind GMT. Previously, local mean solar time was used in different parts # of Iceland, the almanak had been based on Reykjavík mean solar time which # was 1 hour and 28 minutes behind GMT. # # "first day of winter" referred to [below] means the first day of the 26 weeks # of winter, according to the old icelandic calendar that dates back to the # time the norsemen first settled Iceland. The first day of winter is always # Saturday, but is not dependent on the Julian or Gregorian calendars. # # (1993-12-10): # I have a reference from the Oxford Icelandic-English dictionary for the # beginning of winter, which ties it to the ecclesiastical calendar (and thus # to the julian/gregorian calendar) over the period in question. # the winter begins on the Saturday next before St. Luke's day # (old style), or on St. Luke's day, if a Saturday. # St. Luke's day ought to be traceable from ecclesiastical sources. "old style" # might be a reference to the Julian calendar as opposed to Gregorian, or it # might mean something else (???). # # From Paul Eggert (2014-11-22): # The information below is taken from the 1988 Almanak; see # http://www.almanak.hi.is/klukkan.html # Rule Iceland 1917 1919 - Feb 19 23:00 1:00 - Rule Iceland 1917 only - Oct 21 1:00 0 - Rule Iceland 1918 1919 - Nov 16 1:00 0 - Rule Iceland 1921 only - Mar 19 23:00 1:00 - Rule Iceland 1921 only - Jun 23 1:00 0 - Rule Iceland 1939 only - Apr 29 23:00 1:00 - Rule Iceland 1939 only - Oct 29 2:00 0 - Rule Iceland 1940 only - Feb 25 2:00 1:00 - Rule Iceland 1940 1941 - Nov Sun>=2 1:00s 0 - Rule Iceland 1941 1942 - Mar Sun>=2 1:00s 1:00 - # 1943-1946 - first Sunday in March until first Sunday in winter Rule Iceland 1943 1946 - Mar Sun>=1 1:00s 1:00 - Rule Iceland 1942 1948 - Oct Sun>=22 1:00s 0 - # 1947-1967 - first Sunday in April until first Sunday in winter Rule Iceland 1947 1967 - Apr Sun>=1 1:00s 1:00 - # 1949 and 1967 Oct transitions delayed by 1 week Rule Iceland 1949 only - Oct 30 1:00s 0 - Rule Iceland 1950 1966 - Oct Sun>=22 1:00s 0 - Rule Iceland 1967 only - Oct 29 1:00s 0 - Zone Atlantic/Reykjavik -1:28 - LMT 1908 -1:00 Iceland -01/+00 1968 Apr 7 1:00s 0:00 - GMT Link Atlantic/Reykjavik Iceland # St Helena Zone Atlantic/St_Helena -0:22:48 - LMT 1890 # Jamestown -0:22:48 - JMT 1951 # Jamestown Mean Time 0:00 - GMT # King Island Zone Australia/Currie 9:35:28 - LMT 1895 Sep 10:00 AT AE%sT 1919 Oct 24 10:00 Aus AE%sT 1968 Oct 15 10:00 AT AE%sT # Netherlands # Howse writes that the Netherlands' railways used GMT between 1892 and 1940, # but for other purposes the Netherlands used Amsterdam mean time. # However, Robert H. van Gent writes (2001-04-01): # Howse's statement is only correct up to 1909. From 1909-05-01 (00:00:00 # Amsterdam mean time) onwards, the whole of the Netherlands (including # the Dutch railways) was required by law to observe Amsterdam mean time # (19 minutes 32.13 seconds ahead of GMT). This had already been the # common practice (except for the railways) for many decades but it was # not until 1909 when the Dutch government finally defined this by law. # On 1937-07-01 this was changed to 20 minutes (exactly) ahead of GMT and # was generally known as Dutch Time ("Nederlandse Tijd"). # # (2001-04-08): # 1892-05-01 was the date when the Dutch railways were by law required to # observe GMT while the remainder of the Netherlands adhered to the common # practice of following Amsterdam mean time. # # (2001-04-09): # In 1835 the authorities of the province of North Holland requested the # municipal authorities of the towns and cities in the province to observe # Amsterdam mean time but I do not know in how many cases this request was # actually followed. # # From 1852 onwards the Dutch telegraph offices were by law required to # observe Amsterdam mean time. As the time signals from the observatory of # Leiden were also distributed by the telegraph system, I assume that most # places linked up with the telegraph (and railway) system automatically # adopted Amsterdam mean time. # # Although the early Dutch railway companies initially observed a variety # of times, most of them had adopted Amsterdam mean time by 1858 but it # was not until 1866 when they were all required by law to observe # Amsterdam mean time. # The data entries before 1945 are taken from # https://www.staff.science.uu.nl/~gent0113/wettijd/wettijd.htm # From Paul Eggert (2021-05-09): # I invented the abbreviations AMT for Amsterdam Mean Time and NST for # Netherlands Summer Time, used in the Netherlands from 1835 to 1937. Rule Neth 1916 only - May 1 0:00 1:00 NST # Netherlands Summer Time Rule Neth 1916 only - Oct 1 0:00 0 AMT # Amsterdam Mean Time Rule Neth 1917 only - Apr 16 2:00s 1:00 NST Rule Neth 1917 only - Sep 17 2:00s 0 AMT Rule Neth 1918 1921 - Apr Mon>=1 2:00s 1:00 NST Rule Neth 1918 1921 - Sep lastMon 2:00s 0 AMT Rule Neth 1922 only - Mar lastSun 2:00s 1:00 NST Rule Neth 1922 1936 - Oct Sun>=2 2:00s 0 AMT Rule Neth 1923 only - Jun Fri>=1 2:00s 1:00 NST Rule Neth 1924 only - Mar lastSun 2:00s 1:00 NST Rule Neth 1925 only - Jun Fri>=1 2:00s 1:00 NST # From 1926 through 1939 DST began 05-15, except that it was delayed by a week # in years when 05-15 fell in the Pentecost weekend. Rule Neth 1926 1931 - May 15 2:00s 1:00 NST Rule Neth 1932 only - May 22 2:00s 1:00 NST Rule Neth 1933 1936 - May 15 2:00s 1:00 NST Rule Neth 1937 only - May 22 2:00s 1:00 NST Rule Neth 1937 only - Jul 1 0:00 1:00 S Rule Neth 1937 1939 - Oct Sun>=2 2:00s 0 - Rule Neth 1938 1939 - May 15 2:00s 1:00 S Rule Neth 1945 only - Apr 2 2:00s 1:00 S Rule Neth 1945 only - Sep 16 2:00s 0 - #STDOFF 0:19:32.13 Zone Europe/Amsterdam 0:19:32 - LMT 1835 0:19:32 Neth %s 1937 Jul 1 0:20 Neth +0020/+0120 1940 May 16 0:00 1:00 C-Eur CE%sT 1945 Apr 2 2:00 1:00 Neth CE%sT 1977 1:00 EU CE%sT # Northern Ireland Zone Europe/Belfast -0:23:40 - LMT 1880 Aug 2 -0:25:21 - DMT 1916 May 21 2:00 # DMT = Dublin/Dunsink MT -0:25:21 1:00 IST 1916 Oct 1 2:00s # IST = Irish Summer Time 0:00 GB-Eire %s 1968 Oct 27 1:00 - BST 1971 Oct 31 2:00u 0:00 GB-Eire %s 1996 0:00 EU GMT/BST # Denmark # From Jesper Nørgaard Welen (2005-04-26): # the law [introducing standard time] was in effect from 1894-01-01.... # The page https://www.retsinformation.dk/eli/lta/1893/83 # confirms this, and states that the law was put forth 1893-03-29. # # The EU [actually, EEC and Euratom] treaty with effect from 1973: # https://www.retsinformation.dk/eli/lta/1972/21100 # # This provoked a new law from 1974 to make possible summer time changes # in subsequent decrees with the law # https://www.retsinformation.dk/eli/lta/1974/223 # # It seems however that no decree was set forward until 1980. I have # not found any decree, but in another related law, the effecting DST # changes are stated explicitly to be from 1980-04-06 at 02:00 to # 1980-09-28 at 02:00. If this is true, this differs slightly from # the EU rule in that DST runs to 02:00, not 03:00. We don't know # when Denmark began using the EU rule correctly, but we have only # confirmation of the 1980-time, so I presume it was correct in 1981: # The law is about the management of the extra hour, concerning # working hours reported and effect on obligatory-rest rules (which # was suspended on that night): # https://web.archive.org/web/20140104053304/https://www.retsinformation.dk/Forms/R0710.aspx?id=60267 # From Jesper Nørgaard Welen (2005-06-11): # The Herning Folkeblad (1980-09-26) reported that the night between # Saturday and Sunday the clock is set back from three to two. # From Paul Eggert (2005-06-11): # Hence the "02:00" of the 1980 law refers to standard time, not # wall-clock time, and so the EU rules were in effect in 1980. Rule Denmark 1916 only - May 14 23:00 1:00 S Rule Denmark 1916 only - Sep 30 23:00 0 - Rule Denmark 1940 only - May 15 0:00 1:00 S Rule Denmark 1945 only - Apr 2 2:00s 1:00 S Rule Denmark 1945 only - Aug 15 2:00s 0 - Rule Denmark 1946 only - May 1 2:00s 1:00 S Rule Denmark 1946 only - Sep 1 2:00s 0 - Rule Denmark 1947 only - May 4 2:00s 1:00 S Rule Denmark 1947 only - Aug 10 2:00s 0 - Rule Denmark 1948 only - May 9 2:00s 1:00 S Rule Denmark 1948 only - Aug 8 2:00s 0 - # Zone Europe/Copenhagen 0:50:20 - LMT 1890 0:50:20 - CMT 1894 Jan 1 # Copenhagen MT 1:00 Denmark CE%sT 1942 Nov 2 2:00s 1:00 C-Eur CE%sT 1945 Apr 2 2:00 1:00 Denmark CE%sT 1980 1:00 EU CE%sT # Guernsey # Data from Joseph S. Myers # https://mm.icann.org/pipermail/tz/2013-September/019883.html # References to be added # LMT is for Town Church, St. Peter Port, 49° 27' 17" N, 2° 32' 10" W. Zone Europe/Guernsey -0:10:09 - LMT 1913 Jun 18 0:00 GB-Eire %s 1940 Jul 2 1:00 C-Eur CE%sT 1945 May 8 0:00 GB-Eire %s 1968 Oct 27 1:00 - BST 1971 Oct 31 2:00u 0:00 GB-Eire %s 1996 0:00 EU GMT/BST # Isle of Man # # From Lester Caine (2013-09-04): # The Isle of Man legislation is now on-line at # , starting with the original Statutory # Time Act in 1883 and including additional confirmation of some of # the dates of the 'Summer Time' orders originating at # Westminster. There is a little uncertainty as to the starting date # of the first summer time in 1916 which may have been announced a # couple of days late. There is still a substantial number of # documents to work through, but it is thought that every GB change # was also implemented on the island. # # AT4 of 1883 - The Statutory Time et cetera Act 1883 - # LMT Location - 54.1508N -4.4814E - Tynwald Hill ( Manx parliament ) Zone Europe/Isle_of_Man -0:17:55 - LMT 1883 Mar 30 0:00s 0:00 GB-Eire %s 1968 Oct 27 1:00 - BST 1971 Oct 31 2:00u 0:00 GB-Eire %s 1996 0:00 EU GMT/BST # Jersey # Data from Joseph S. Myers # https://mm.icann.org/pipermail/tz/2013-September/019883.html # References to be added # LMT is for Parish Church, St. Helier, 49° 11' 0.57" N, 2° 6' 24.33" W. Zone Europe/Jersey -0:08:26 - LMT 1898 Jun 11 16:00u 0:00 GB-Eire %s 1940 Jul 2 1:00 C-Eur CE%sT 1945 May 8 0:00 GB-Eire %s 1968 Oct 27 1:00 - BST 1971 Oct 31 2:00u 0:00 GB-Eire %s 1996 0:00 EU GMT/BST # Slovenia Zone Europe/Ljubljana 0:58:04 - LMT 1884 1:00 - CET 1941 Apr 18 23:00 1:00 C-Eur CE%sT 1945 May 8 2:00s 1:00 1:00 CEST 1945 Sep 16 2:00s 1:00 - CET 1982 Nov 27 1:00 EU CE%sT # Luxembourg # Whitman disagrees with most of these dates in minor ways; # go with Shanks & Pottenger. Rule Lux 1916 only - May 14 23:00 1:00 S Rule Lux 1916 only - Oct 1 1:00 0 - Rule Lux 1917 only - Apr 28 23:00 1:00 S Rule Lux 1917 only - Sep 17 1:00 0 - Rule Lux 1918 only - Apr Mon>=15 2:00s 1:00 S Rule Lux 1918 only - Sep Mon>=15 2:00s 0 - Rule Lux 1919 only - Mar 1 23:00 1:00 S Rule Lux 1919 only - Oct 5 3:00 0 - Rule Lux 1920 only - Feb 14 23:00 1:00 S Rule Lux 1920 only - Oct 24 2:00 0 - Rule Lux 1921 only - Mar 14 23:00 1:00 S Rule Lux 1921 only - Oct 26 2:00 0 - Rule Lux 1922 only - Mar 25 23:00 1:00 S Rule Lux 1922 only - Oct Sun>=2 1:00 0 - Rule Lux 1923 only - Apr 21 23:00 1:00 S Rule Lux 1923 only - Oct Sun>=2 2:00 0 - Rule Lux 1924 only - Mar 29 23:00 1:00 S Rule Lux 1924 1928 - Oct Sun>=2 1:00 0 - Rule Lux 1925 only - Apr 5 23:00 1:00 S Rule Lux 1926 only - Apr 17 23:00 1:00 S Rule Lux 1927 only - Apr 9 23:00 1:00 S Rule Lux 1928 only - Apr 14 23:00 1:00 S Rule Lux 1929 only - Apr 20 23:00 1:00 S Zone Europe/Luxembourg 0:24:36 - LMT 1904 Jun 1:00 Lux CE%sT 1918 Nov 25 0:00 Lux WE%sT 1929 Oct 6 2:00s 0:00 Belgium WE%sT 1940 May 14 3:00 1:00 C-Eur WE%sT 1944 Sep 18 3:00 1:00 Belgium CE%sT 1977 1:00 EU CE%sT # Monaco # # From Michael Deckers (2020-06-12): # In the "Journal de Monaco" of 1892-05-24, online at # https://journaldemonaco.gouv.mc/var/jdm/storage/original/application/b1c67c12c5af11b41ea888fb048e4fe8.pdf # we read: ... # [In virtue of a Sovereign Ordinance of the May 13 of the current [year], # legal time in the Principality will be set to, from the date of June 1, # 1892 onwards, to the meridian of Paris, as in France.] # In the "Journal de Monaco" of 1911-03-28, online at # https://journaldemonaco.gouv.mc/var/jdm/storage/original/application/de74ffb7db53d4f599059fe8f0ed482a.pdf # we read an ordinance of 1911-03-16: ... # [Legal time in the Principality will be set, from the date of promulgation # of the present ordinance, to legal time in France.... Consequently, legal # time will be retarded by 9 minutes and 21 seconds.] # Zone Europe/Monaco 0:29:32 - LMT 1892 Jun 1 0:09:21 - PMT 1911 Mar 29 # Paris Mean Time 0:00 France WE%sT 1945 Sep 16 3:00 1:00 France CE%sT 1977 1:00 EU CE%sT # Norway # http://met.no/met/met_lex/q_u/sommertid.html (2004-01) agrees with Shanks & # Pottenger. Rule Norway 1916 only - May 22 1:00 1:00 S Rule Norway 1916 only - Sep 30 0:00 0 - Rule Norway 1945 only - Apr 2 2:00s 1:00 S Rule Norway 1945 only - Oct 1 2:00s 0 - Rule Norway 1959 1964 - Mar Sun>=15 2:00s 1:00 S Rule Norway 1959 1965 - Sep Sun>=15 2:00s 0 - Rule Norway 1965 only - Apr 25 2:00s 1:00 S Zone Europe/Oslo 0:43:00 - LMT 1895 Jan 1 1:00 Norway CE%sT 1940 Aug 10 23:00 1:00 C-Eur CE%sT 1945 Apr 2 2:00 1:00 Norway CE%sT 1980 1:00 EU CE%sT Link Europe/Oslo Arctic/Longyearbyen #PACKRATLIST zone.tab Link Europe/Oslo Atlantic/Jan_Mayen # Bosnia and Herzegovina Zone Europe/Sarajevo 1:13:40 - LMT 1884 1:00 - CET 1941 Apr 18 23:00 1:00 C-Eur CE%sT 1945 May 8 2:00s 1:00 1:00 CEST 1945 Sep 16 2:00s 1:00 - CET 1982 Nov 27 1:00 EU CE%sT # North Macedonia Zone Europe/Skopje 1:25:44 - LMT 1884 1:00 - CET 1941 Apr 18 23:00 1:00 C-Eur CE%sT 1945 May 8 2:00s 1:00 1:00 CEST 1945 Sep 16 2:00s 1:00 - CET 1982 Nov 27 1:00 EU CE%sT # Sweden # From Ivan Nilsson (2001-04-13), superseding Shanks & Pottenger: # # The law "Svensk författningssamling 1878, no 14" about standard time in 1879: # From the beginning of 1879 (that is 01-01 00:00) the time for all # places in the country is "the mean solar time for the meridian at # three degrees, or twelve minutes of time, to the west of the # meridian of the Observatory of Stockholm". The law is dated 1878-05-31. # # The observatory at that time had the meridian 18° 03' 30" # eastern longitude = 01:12:14 in time. Less 12 minutes gives the # national standard time as 01:00:14 ahead of GMT.... # # About the beginning of CET in Sweden. The lawtext ("Svensk # författningssamling 1899, no 44") states, that "from the beginning # of 1900... ... the same as the mean solar time for the meridian at # the distance of one hour of time from the meridian of the English # observatory at Greenwich, or at 12 minutes 14 seconds to the west # from the meridian of the Observatory of Stockholm". The law is dated # 1899-06-16. In short: At 1900-01-01 00:00:00 the new standard time # in Sweden is 01:00:00 ahead of GMT. # # 1916: The lawtext ("Svensk författningssamling 1916, no 124") states # that "1916-05-15 is considered to begin one hour earlier". It is # pretty obvious that at 05-14 23:00 the clocks are set to 05-15 00:00.... # Further the law says, that "1916-09-30 is considered to end one hour later". # # The laws regulating [DST] are available on the site of the Swedish # Parliament beginning with 1985 - the laws regulating 1980/1984 are # not available on the site (to my knowledge they are only available # in Swedish): (type # "sommartid" without the quotes in the field "Fritext" and then click # the Sök-button). # # (2001-05-13): # # I have now found a newspaper stating that at 1916-10-01 01:00 # summertime the church-clocks etc were set back one hour to show # 1916-10-01 00:00 standard time. The article also reports that some # people thought the switch to standard time would take place already # at 1916-10-01 00:00 summer time, but they had to wait for another # hour before the event took place. # # Source: The newspaper "Dagens Nyheter", 1916-10-01, page 7 upper left. # An extra-special abbreviation style is SET for Swedish Time (svensk # normaltid) 1879-1899, 3° west of the Stockholm Observatory. Zone Europe/Stockholm 1:12:12 - LMT 1879 Jan 1 1:00:14 - SET 1900 Jan 1 # Swedish Time 1:00 - CET 1916 May 14 23:00 1:00 1:00 CEST 1916 Oct 1 1:00 1:00 - CET 1980 1:00 EU CE%sT # Moldova / Transnistria Zone Europe/Tiraspol 1:58:32 - LMT 1880 1:55 - CMT 1918 Feb 15 # Chisinau MT 1:44:24 - BMT 1931 Jul 24 # Bucharest MT 2:00 Romania EE%sT 1940 Aug 15 2:00 1:00 EEST 1941 Jul 17 1:00 C-Eur CE%sT 1944 Aug 24 3:00 Russia MSK/MSD 1991 Mar 31 2:00 2:00 Russia EE%sT 1992 Jan 19 2:00 3:00 Russia MSK/MSD # Ukraine # # Although Shanks & Pottenger say Transcarpathia used CET 1990/1991, # this unreliable source contradicts contemporaneous government resolutions # (see the commentary for Ukraine in the 'europe' file) # so for now this dubious zone is in 'backzone'. # "Uzhhorod" is the transliteration of the Ukrainian spelling, but # "Uzhgorod" was a common English spelling when this dubious zone was # added to TZDB in 1999. Zone Europe/Uzhgorod 1:29:12 - LMT 1890 Oct 1:00 - CET 1940 1:00 C-Eur CE%sT 1944 Oct 1:00 1:00 CEST 1944 Oct 26 1:00 - CET 1945 Jun 29 3:00 Russia MSK/MSD 1990 3:00 - MSK 1990 Jul 1 2:00 1:00 - CET 1991 Mar 31 3:00 2:00 - EET 1992 Mar 20 2:00 C-Eur EE%sT 1996 May 13 2:00 EU EE%sT # Liechtenstein # From Paul Eggert (2022-07-21): # Shanks & Pottenger say Vaduz is like Zurich starting June 1894. # From Alois Treindl (2019-07-04): # I was able to access the online archive of the Vaduz paper Vaterland ... # I could confirm from the paper that Liechtenstein did in fact follow # the same DST in 1941 and 1942 as Switzerland did. Zone Europe/Vaduz 0:38:04 - LMT 1894 Jun 1:00 Swiss CE%sT 1981 1:00 EU CE%sT # Croatia Zone Europe/Zagreb 1:03:52 - LMT 1884 1:00 - CET 1941 Apr 18 23:00 1:00 C-Eur CE%sT 1945 May 8 2:00s 1:00 1:00 CEST 1945 Sep 16 2:00s 1:00 - CET 1982 Nov 27 1:00 EU CE%sT # Ukraine # Although Shanks & Pottenger say Zaporizhzhia and eastern Lugansk # observed DST 1990/1991, this unreliable source contradicts contemporaneous # government resolutions (see the commentary for Ukraine in the 'europe' file) # so for now this dubious zone is in 'backzone'. # "Zaporizhzhia" is the transliteration of the Ukrainian name, but # "Zaporozhye" was a common English spelling when this dubious zone was # added to TZDB in 1999. Zone Europe/Zaporozhye 2:20:40 - LMT 1880 2:20 - +0220 1924 May 2 2:00 - EET 1930 Jun 21 3:00 - MSK 1941 Aug 25 1:00 C-Eur CE%sT 1943 Oct 25 3:00 Russia MSK/MSD 1991 Mar 31 2:00 2:00 E-Eur EE%sT 1992 Mar 20 2:00 C-Eur EE%sT 1996 May 13 2:00 EU EE%sT # Madagascar Zone Indian/Antananarivo 3:10:04 - LMT 1911 Jul 3:00 - EAT 1954 Feb 27 23:00s 3:00 1:00 EAST 1954 May 29 23:00s 3:00 - EAT # Christmas Zone Indian/Christmas 7:02:52 - LMT 1895 Feb 7:00 - +07 # Cocos (Keeling) Is # These islands were ruled by the Ross family from about 1830 to 1978. # We don't know when standard time was introduced; for now, we guess 1900. Zone Indian/Cocos 6:27:40 - LMT 1900 6:30 - +0630 # Comoros Zone Indian/Comoro 2:53:04 - LMT 1911 Jul # Moroni, Gran Comoro 3:00 - EAT # Kerguelen Zone Indian/Kerguelen 0 - -00 1950 # Port-aux-Français 5:00 - +05 # Seychelles # # From P Chan (2020-11-27): # Standard Time was adopted on 1907-01-01. # # Standard Time Ordinance (Chapter 237) # The Laws of Seychelles in Force on the 31st December, 1971, Vol. 6, p 571 # https://books.google.com/books?id=efE-AQAAIAAJ&pg=PA571 # # From Tim Parenti (2020-12-05): # A footnote on https://books.google.com/books?id=DYdDAQAAMAAJ&pg=PA1689 # confirms that Ordinance No. 9 of 1906 "was brought into force on the 1st # January, 1907." Zone Indian/Mahe 3:41:48 - LMT 1907 Jan 1 # Victoria 4:00 - +04 # From Paul Eggert (2001-05-30): # Aldabra, Farquhar, and Desroches, originally dependencies of the # Seychelles, were transferred to the British Indian Ocean Territory # in 1965 and returned to Seychelles control in 1976. We don't know # whether this affected their time zone, so omit this for now. # Possibly the islands were uninhabited. # Mayotte Zone Indian/Mayotte 3:00:56 - LMT 1911 Jul # Mamoutzou 3:00 - EAT # Réunion Zone Indian/Reunion 3:41:52 - LMT 1911 Jun # Saint-Denis 4:00 - +04 # # Scattered Islands (Îles Éparses) administered from Réunion are as follows. # The following information about them is taken from # Îles Éparses (, 1997-07-22, # in French; no longer available as of 1999-08-17). # We have no info about their time zone histories. # # Bassas da India - uninhabited # Europa Island - inhabited from 1905 to 1910 by two families # Glorioso Is - inhabited until at least 1958 # Juan de Nova - uninhabited # Tromelin - inhabited until at least 1958 # Micronesia # Also see Pacific/Pohnpei and commentary for Micronesia in 'australasia'. # # From Paul Eggert (2018-11-18): # Alan Eugene Davis writes (1996-03-16), # "I am certain, having lived there for the past decade, that 'Truk' # (now properly known as Chuuk) ... is in the time zone GMT+10." # Shanks & Pottenger write that Truk switched from UT +10 to +11 # on 1978-10-01; ignore this for now. Zone Pacific/Chuuk -13:52:52 - LMT 1844 Dec 31 10:07:08 - LMT 1901 10:00 - +10 1914 Oct 9:00 - +09 1919 Feb 1 10:00 - +10 1941 Apr 1 9:00 - +09 1945 Aug 10:00 - +10 Link Pacific/Chuuk Pacific/Truk Link Pacific/Chuuk Pacific/Yap # Phoenix Islands, Kiribati # From Paul Eggert (2021-05-27): # Enderbury was inhabited 1860/1880s to mine guano, and 1938-03-06/1942-02-09 # for aviation (ostensibly commercial, but military uses foreseen). # The 19th-century dates are approximate. See Pacific/Kanton for # the currently inhabited representative for this timezone. Zone Pacific/Enderbury 0 - -00 1860 -11:24:20 - LMT 1885 0 - -00 1938 Mar 6 -12:00 - -12 1942 Feb 9 0 - -00 # Tuvalu Zone Pacific/Funafuti 11:56:52 - LMT 1901 12:00 - +12 # Johnston Zone Pacific/Johnston -10:00 - HST # Marshall Is Zone Pacific/Majuro 11:24:48 - LMT 1901 11:00 - +11 1914 Oct 9:00 - +09 1919 Feb 1 11:00 - +11 1937 10:00 - +10 1941 Apr 1 9:00 - +09 1944 Jan 30 11:00 - +11 1969 Oct 12:00 - +12 # Midway # # From Mark Brader (2005-01-23): # [] # reproduced a Pan American Airways timetable from 1936, for their weekly # "Orient Express" flights between San Francisco and Manila, and connecting # flights to Chicago and the US East Coast. As it uses some time zone # designations that I've never seen before:.... # Fri. 6:30A Lv. HONOLULU (Pearl Harbor), H.I. . H.L.T. Ar. 5:30P Sun. # " 3:00P Ar. MIDWAY ISLAND . . . . . . . . . M.L.T. Lv. 6:00A " # Zone Pacific/Midway -11:49:28 - LMT 1901 -11:00 - -11 1956 Jun 3 -11:00 1:00 -10 1956 Sep 2 -11:00 - SST # S=Samoa # Micronesia # Also see Pacific/Chuuk and commentary for Micronesia in 'australasia'. Zone Pacific/Pohnpei -13:27:08 - LMT 1844 Dec 31 # Kolonia 10:32:52 - LMT 1901 11:00 - +11 1914 Oct 9:00 - +09 1919 Feb 1 11:00 - +11 1937 10:00 - +10 1941 Apr 1 9:00 - +09 1945 Aug 11:00 - +11 Link Pacific/Pohnpei Pacific/Ponape # N Mariana Is # # From Paul Eggert (2022-08-16): # Although Shanks & Pottenger say Saipan used +09 and then switched # to Guam time in October 1969, this is surely wrong. # Saipan used Guam time in the late 1950s; see page 4 of the minutes on the # conference of the 12th Saipan Legislature and the Select Committee on # Saipan Mission, 5th Guam Legislature (1959-09-11): # http://www.nmhcouncil.org/nmhc_archives/U.S.%20Navy%20Civil%20Affairs%20Files%201944-1962/1959/1959%2009%2017%20letter,%20minutes%20of%20conference,%20Borja.pdf # For now, assume Saipan switched to Guam time after the Battle of Saipan. # Zone Pacific/Saipan -14:17:00 - LMT 1844 Dec 31 9:43:00 - LMT 1901 9:00 - +09 1944 Jul 9 10:00 Guam G%sT 2000 Dec 23 10:00 - ChST # Chamorro Standard Time # Wake # From Vernice Anderson, Personal Secretary to Philip Jessup, # US Ambassador At Large (oral history interview, 1971-02-02): # # Saturday, the 14th [of October, 1950] - ... The time was all the # more confusing at that point, because we had crossed the # International Date Line, thus getting two Sundays. Furthermore, we # discovered that Wake Island had two hours of daylight saving time # making calculation of time in Washington difficult if not almost # impossible. # # https://www.trumanlibrary.org/oralhist/andrsonv.htm # From Paul Eggert (2003-03-23): # We have no other report of DST in Wake Island, so omit this info for now. # Also see commentary for Micronesia in 'australasia'. Zone Pacific/Wake 11:06:28 - LMT 1901 12:00 - +12 # Wallis and Futuna Zone Pacific/Wallis 12:15:20 - LMT 1901 12:00 - +12 # From Paul Eggert (2024-05-22): # The following zones pretend that standard time extends backward # indefinitely into the past, and so are ahistorical. # In current TZDB these entries are links to geographical locations # that agree with the ahistorical zones since 1970. # These are in numeric rather than alphabetic order. # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone HST -10:00 - HST Zone PST8PDT -8:00 US P%sT Zone MST -7:00 - MST Zone MST7MDT -7:00 US M%sT Zone CST6CDT -6:00 US C%sT Zone EST -5:00 - EST Zone EST5EDT -5:00 US E%sT Zone WET 0:00 EU WE%sT Zone CET 1:00 C-Eur CE%sT Zone MET 1:00 C-Eur ME%sT Zone EET 2:00 EU EE%sT # Local Variables: # coding: utf-8 # End: pr0m1th3as-datatypes-9c9a8d3/inst/tzdata/calendars000066400000000000000000000112341522766574100222010ustar00rootroot00000000000000----- Calendrical issues ----- As mentioned in Theory.html, although calendrical issues are out of scope for tzdb, they indicate the sort of problems that we would run into if we extended tzdb further into the past. The following information and sources go beyond Theory.html's brief discussion. They sometimes disagree. France Gregorian calendar adopted 1582-12-20. French Revolutionary calendar used 1793-11-24 through 1805-12-31, and (in Paris only) 1871-05-06 through 1871-05-23. Russia Soviet Russia adopted the Gregorian calendar on 1918-02-14. It also used 5- and 6-day work weeks at times, in parallel with the Gregorian calendar; see . Sweden (and Finland) From: Mark Brader Subject: Re: Gregorian reform - a part of locale? Date: 1996-07-06 In 1700, Denmark made the transition from Julian to Gregorian. Sweden decided to *start* a transition in 1700 as well, but rather than have one of those unsightly calendar gaps :-), they simply decreed that the next leap year after 1696 would be in 1744 - putting the whole country on a calendar different from both Julian and Gregorian for a period of 40 years. However, in 1704 something went wrong and the plan was not carried through; they did, after all, have a leap year that year. And one in 1708. In 1712 they gave it up and went back to Julian, putting 30 days in February that year!... Then in 1753, Sweden made the transition to Gregorian in the usual manner, getting there only 13 years behind the original schedule. (A previous posting of this story was challenged, and Swedish readers produced the following references to support it: "Tideräkning och historia" by Natanael Beckman (1924) and "Tid, en bok om tideräkning och kalenderväsen" by Lars-Olof Lodén (1968). Grotefend's data From: "Michael Palmer" [with two obvious typos fixed] Subject: Re: Gregorian Calendar (was Re: Another FHC related question Newsgroups: soc.genealogy.german Date: Tue, 9 Feb 1999 02:32:48 -800 ... The following is a(n incomplete) listing, arranged chronologically, of European states, with the date they converted from the Julian to the Gregorian calendar: 04/15 Oct 1582 - Italy (with exceptions), Spain, Portugal, Poland (Roman Catholics and Danzig only) 09/20 Dec 1582 - France, Lorraine 21 Dec 1582/ 01 Jan 1583 - Holland, Brabant, Flanders, Hennegau 10/21 Feb 1583 - bishopric of Liege (Lüttich) 13/24 Feb 1583 - bishopric of Augsburg 04/15 Oct 1583 - electorate of Trier 05/16 Oct 1583 - Bavaria, bishoprics of Freising, Eichstedt, Regensburg, Salzburg, Brixen 13/24 Oct 1583 - Austrian Oberelsaß and Breisgau 20/31 Oct 1583 - bishopric of Basel 02/13 Nov 1583 - duchy of Jülich-Berg 02/13 Nov 1583 - electorate and city of Köln 04/15 Nov 1583 - bishopric of Würzburg 11/22 Nov 1583 - electorate of Mainz 16/27 Nov 1583 - bishopric of Strassburg and the margraviate of Baden 17/28 Nov 1583 - bishopric of Münster and duchy of Cleve 14/25 Dec 1583 - Steiermark 06/17 Jan 1584 - Austria and Bohemia 11/22 Jan 1584 - Lucerne, Uri, Schwyz, Zug, Freiburg, Solothurn 12/23 Jan 1584 - Silesia and the Lausitz 22 Jan/ 02 Feb 1584 - Hungary (legally on 21 Oct 1587) Jun 1584 - Unterwalden 01/12 Jul 1584 - duchy of Westfalen 16/27 Jun 1585 - bishopric of Paderborn 14/25 Dec 1590 - Transylvania 22 Aug/ 02 Sep 1612 - duchy of Prussia 13/24 Dec 1614 - Pfalz-Neuburg 1617 - duchy of Kurland (reverted to the Julian calendar in 1796) 1624 - bishopric of Osnabrück 1630 - bishopric of Minden 15/26 Mar 1631 - bishopric of Hildesheim 1655 - Kanton Wallis 05/16 Feb 1682 - city of Strassburg 18 Feb/ 01 Mar 1700 - Protestant Germany (including Swedish possessions in Germany), Denmark, Norway 30 Jun/ 12 Jul 1700 - Gelderland, Zutphen 10 Nov/ 12 Dec 1700 - Utrecht, Overijssel 31 Dec 1700/ 12 Jan 1701 - Friesland, Groningen, Zürich, Bern, Basel, Geneva, Thurgau, and Schaffhausen 1724 - Glarus, Appenzell, and the city of St. Gallen 01 Jan 1750 - Pisa and Florence 02/14 Sep 1752 - Great Britain 17 Feb/ 01 Mar 1753 - Sweden 1760-1812 - Graubünden The Russian empire (including Finland and the Baltic states) did not convert to the Gregorian calendar until the Soviet revolution of 1917. Source: H. Grotefend, _Taschenbuch der Zeitrechnung des deutschen Mittelalters und der Neuzeit_, herausgegeben von Dr. O. Grotefend (Hannover: Hahnsche Buchhandlung, 1941), pp. 26-28. ----- This file is in the public domain, so clarified as of 2009-05-17 by Arthur David Olson. ----- Local Variables: coding: utf-8 End: pr0m1th3as-datatypes-9c9a8d3/inst/tzdata/etcetera000066400000000000000000000060641522766574100220460ustar00rootroot00000000000000# tzdb data for ships at sea and other miscellany # This file is in the public domain, so clarified as of # 2009-05-17 by Arthur David Olson. # These entries are for uses not otherwise covered by the tz database. # Their main practical use is for platforms like Android that lack # support for POSIX proleptic TZ strings. On such platforms these entries # can be useful if the timezone database is wrong or if a ship or # aircraft at sea is not in a timezone. # Starting with POSIX 1003.1-2001, the entries below are all # unnecessary as settings for the TZ environment variable. E.g., # instead of TZ='Etc/GMT+4' one can use the POSIX setting TZ='<-04>+4'. # # Do not use a POSIX TZ setting like TZ='GMT+4', which is four hours # behind GMT but uses the completely misleading abbreviation "GMT". # The following zone is used by tzcode functions like gmtime, # which load the "UTC" file to handle seconds properly. Zone Etc/UTC 0 - UTC # If leap second support is enabled, functions like gmtime # load the "GMT" file to handle leap seconds properly. # Vanguard section, which works with most .zi parsers. #Zone GMT 0 - GMT # Rearguard section, for TZUpdater 2.3.2 and earlier. Zone Etc/GMT 0 - GMT # The following link uses older naming conventions, # but it belongs here, not in the file 'backward', # as it is needed for tzcode releases through 2022a, # where functions like gmtime load "GMT" instead of the "Etc/UTC". # We want this to work even on installations that omit 'backward'. Link Etc/GMT GMT # End of rearguard section. # Be consistent with POSIX TZ settings in the Zone names, # even though this is the opposite of what many people expect. # POSIX has positive signs west of Greenwich, but many people expect # positive signs east of Greenwich. For example, TZ='Etc/GMT+4' uses # the abbreviation "-04" and corresponds to 4 hours behind UT # (i.e. west of Greenwich) even though many people would expect it to # mean 4 hours ahead of UT (i.e. east of Greenwich). # Earlier incarnations of this package were not POSIX-compliant, # and had lines such as # Zone GMT-12 -12 - GMT-1200 # We did not want things to change quietly if someone accustomed to the old # way does a # zic -l GMT-12 # so we moved the names into the Etc subdirectory. # Also, the time zone abbreviations are now compatible with %z. # There is no "Etc/Unknown" entry, as CLDR says that "Etc/Unknown" # corresponds to an unknown or invalid time zone, and things would get # confusing if Etc/Unknown were made valid here. Zone Etc/GMT-14 14 - %z Zone Etc/GMT-13 13 - %z Zone Etc/GMT-12 12 - %z Zone Etc/GMT-11 11 - %z Zone Etc/GMT-10 10 - %z Zone Etc/GMT-9 9 - %z Zone Etc/GMT-8 8 - %z Zone Etc/GMT-7 7 - %z Zone Etc/GMT-6 6 - %z Zone Etc/GMT-5 5 - %z Zone Etc/GMT-4 4 - %z Zone Etc/GMT-3 3 - %z Zone Etc/GMT-2 2 - %z Zone Etc/GMT-1 1 - %z Zone Etc/GMT+1 -1 - %z Zone Etc/GMT+2 -2 - %z Zone Etc/GMT+3 -3 - %z Zone Etc/GMT+4 -4 - %z Zone Etc/GMT+5 -5 - %z Zone Etc/GMT+6 -6 - %z Zone Etc/GMT+7 -7 - %z Zone Etc/GMT+8 -8 - %z Zone Etc/GMT+9 -9 - %z Zone Etc/GMT+10 -10 - %z Zone Etc/GMT+11 -11 - %z Zone Etc/GMT+12 -12 - %z pr0m1th3as-datatypes-9c9a8d3/inst/tzdata/europe000066400000000000000000005555371522766574100215670ustar00rootroot00000000000000# tzdb data for Europe and environs # This file is in the public domain, so clarified as of # 2009-05-17 by Arthur David Olson. # This file is by no means authoritative; if you think you know better, # go ahead and edit the file, and please send any changes to # the public mailing list tz@iana.org for general use in the future. # For more, please see the file CONTRIBUTING in the tz distribution. # From Paul Eggert (2017-02-10): # # Unless otherwise specified, the source for data through 1990 is: # Thomas G. Shanks and Rique Pottenger, The International Atlas (6th edition), # San Diego: ACS Publications, Inc. (2003). # Unfortunately this book contains many errors and cites no sources. # # Many years ago Gwillim Law wrote that a good source # for time zone data was the International Air Transport # Association's Standard Schedules Information Manual (IATA SSIM), # published semiannually. Law sent in several helpful summaries # of the IATA's data after 1990. Except where otherwise noted, # IATA SSIM is the source for entries after 1990. # # A reliable and entertaining source about time zones is # Derek Howse, Greenwich time and longitude, Philip Wilson Publishers (1997). # # Except where otherwise noted, Shanks & Pottenger is the source for # entries through 1991, and IATA SSIM is the source for entries afterwards. # # Other sources occasionally used include: # # Edward W. Whitman, World Time Differences, # Whitman Publishing Co, 2 Niagara Av, Ealing, London (undated), # which I found in the UCLA library. # # William Willett, The Waste of Daylight, 19th edition # # [PDF] (1914-03) # # Milne J. Civil time. Geogr J. 1899 Feb;13(2):173-94 # . He writes: # "It is requested that corrections and additions to these tables # may be sent to Mr. John Milne, Royal Geographical Society, # Savile Row, London." Nowadays please see the file CONTRIBUTING. # # Byalokoz EL. New Counting of Time in Russia since July 1, 1919. # This Russian-language source was consulted by Vladimir Karpinsky; see # https://mm.icann.org/pipermail/tz/2014-August/021320.html # The full Russian citation is: # Бялокоз, Евгений Людвигович. Новый счет времени в течении суток # введенный декретом Совета народных комиссаров для всей России с 1-го # июля 1919 г. / Изд. 2-е Междуведомственной комиссии. - Петроград: # Десятая гос. тип., 1919. # http://resolver.gpntb.ru/purl?docushare/dsweb/Get/Resource-2011/Byalokoz__E.L.__Novyy__schet__vremeni__v__techenie__sutok__izd__2(1).pdf # # Brazil's Divisão de Serviços da Hora (DISHO) # History of Summer Time # # (1998-09-21, in Portuguese) # # I invented the abbreviations marked '*' in the following table; # the rest are variants of the "xMT" pattern for a city's mean time, # or are from other sources. Corrections are welcome! # std dst 2dst # LMT Local Mean Time # -4:00 AST ADT Atlantic # 0:00 GMT BST BDST Greenwich, British Summer # 0:00 GMT IST Greenwich, Irish Summer # 0:00 WET WEST WEMT Western Europe # 1:00 BST British Standard (1968-1971) # 1:00 IST GMT Irish Standard (1968-) with winter DST # 1:00 CET CEST CEMT Central Europe # 1:00:14 SET Swedish (1879-1899) # 1:36:34 RMT* LST* Riga, Latvian Summer (1880-1926)* # 2:00 EET EEST Eastern Europe # 3:00 MSK MSD MDST* Moscow # From Peter Ilieve (1994-12-04), re EEC/EC/EU members: # The original six: Belgium, France, (West) Germany, Italy, # Luxembourg, the Netherlands. # Plus, from 1 Jan 73: Denmark, Ireland, United Kingdom. # Plus, from 1 Jan 81: Greece. # Plus, from 1 Jan 86: Spain, Portugal. # Plus, from 1 Jan 95: Austria, Finland, Sweden. (Norway negotiated terms for # entry but in a referendum on 28 Nov 94 the people voted No by 52.2% to 47.8% # on a turnout of 88.6%. This was almost the same result as Norway's previous # referendum in 1972, they are the only country to have said No twice. # Referendums in the other three countries voted Yes.) # ... # Estonia ... uses EU dates but not at 01:00 GMT, they use midnight GMT. # I don't think they know yet what they will do from 1996 onwards. # ... # There shouldn't be any [current members who are not using EU rules]. # A Directive has the force of law, member states are obliged to enact # national law to implement it. The only contentious issue was the # different end date for the UK and Ireland, and this was always allowed # in the Directive. ############################################################################### # Britain (United Kingdom) and Ireland (Eire) # From Peter Ilieve (1994-07-06): # # On 17 Jan 1994 the Independent, a UK quality newspaper, had a piece about # historical vistas along the Thames in west London. There was a photo # and a sketch map showing some of the sightlines involved. One paragraph # of the text said: # # 'An old stone obelisk marking a forgotten terrestrial meridian stands # beside the river at Kew. In the 18th century, before time and longitude # was standardised by the Royal Observatory in Greenwich, scholars observed # this stone and the movement of stars from Kew Observatory nearby. They # made their calculations and set the time for the Horse Guards and Parliament, # but now the stone is obscured by scrubwood and can only be seen by walking # along the towpath within a few yards of it.' # # I have a one inch to one mile map of London and my estimate of the stone's # position is 51° 28' 30" N, 0° 18' 45" W. The longitude should # be within about ±2". The Ordnance Survey grid reference is TQ172761. # # [This yields STDOFF = -0:01:15 for London LMT in the 18th century.] # From Paul Eggert (1993-11-18): # # Howse writes that Britain was the first country to use standard time. # The railways cared most about the inconsistencies of local mean time, # and it was they who forced a uniform time on the country. # The original idea was credited to Dr. William Hyde Wollaston (1766-1828) # and was popularized by Abraham Follett Osler (1808-1903). # The first railway to adopt London time was the Great Western Railway # in November 1840; other railways followed suit, and by 1847 most # (though not all) railways used London time. On 1847-09-22 the # Railway Clearing House, an industry standards body, recommended that GMT be # adopted at all stations as soon as the General Post Office permitted it. # The transition occurred on 12-01 for the L&NW, the Caledonian, # and presumably other railways; the January 1848 Bradshaw's lists many # railways as using GMT. By 1855 the vast majority of public # clocks in Britain were set to GMT (though some, like the great clock # on Tom Tower at Christ Church, Oxford, were fitted with two minute hands, # one for local time and one for GMT). The last major holdout was the legal # system, which stubbornly stuck to local time for many years, leading # to oddities like polls opening at 08:13 and closing at 16:13. # The legal system finally switched to GMT when the Statutes (Definition # of Time) Act took effect; it received the Royal Assent on 1880-08-02. # # In the tables below, we condense this complicated story into a single # transition date for London, namely 1847-12-01. We don't know as much # about Dublin, so we use 1880-08-02, the legal transition time. # From Paul Eggert (2014-07-19): # The ancients had no need for daylight saving, as they kept time # informally or via hours whose length depended on the time of year. # Daylight saving time in its modern sense was invented by the # New Zealand entomologist George Vernon Hudson (1867-1946), # whose day job as a postal clerk led him to value # after-hours daylight in which to pursue his research. # In 1895 he presented a paper to the Wellington Philosophical Society # that proposed a two-hour daylight-saving shift. See: # Hudson GV. On seasonal time-adjustment in countries south of lat. 30°. # Transactions and Proceedings of the New Zealand Institute. 1895;28:734 # http://rsnz.natlib.govt.nz/volume/rsnz_28/rsnz_28_00_006110.html # Although some interest was expressed in New Zealand, his proposal # did not find its way into law and eventually it was almost forgotten. # # In England, DST was independently reinvented by William Willett (1857-1915), # a London builder and member of the Royal Astronomical Society # who circulated a pamphlet "The Waste of Daylight" (1907) # that proposed advancing clocks 20 minutes on each of four Sundays in April, # and retarding them by the same amount on four Sundays in September. # A bill was drafted in 1909 and introduced in Parliament several times, # but it met with ridicule and opposition, especially from farming interests. # Later editions of the pamphlet proposed one-hour summer time, and # it was eventually adopted as a wartime measure in 1916. # See: Summer Time Arrives Early, The Times (2000-05-18). # A monument to Willett was unveiled on 1927-05-21, in an open space in # a 45-acre wood near Chislehurst, Kent that was purchased by popular # subscription and open to the public. On the south face of the monolith, # designed by G. W. Miller, is the William Willett Memorial Sundial, # which is permanently set to Summer Time. # From Winston Churchill (1934-04-28): # It is one of the paradoxes of history that we should owe the boon of # summer time, which gives every year to the people of this country # between 160 and 170 hours more daylight leisure, to a war which # plunged Europe into darkness for four years, and shook the # foundations of civilization throughout the world. # -- "A Silent Toast to William Willett", Pictorial Weekly; # republished in Finest Hour (Spring 2002) 1(114):26 # https://www.winstonchurchill.org/publications/finest-hour/finest-hour-114/a-silent-toast-to-william-willett-by-winston-s-churchill # From Paul Eggert (2015-08-08): # The OED Supplement says that the English originally said "Daylight Saving" # when they were debating the adoption of DST in 1908; but by 1916 this # term appears only in quotes taken from DST's opponents, whereas the # proponents (who eventually won the argument) are quoted as using "Summer". # The term "Summer Time" was introduced by Herbert Samuel, Home Secretary; see: # Viscount Samuel. Leisure in a Democracy. Cambridge University Press # ISBN 978-1-107-49471-8 (1949, reissued 2015), p 8. # From Arthur David Olson (1989-01-19): # A source at the British Information Office in New York avers that it's # known as "British" Summer Time in all parts of the United Kingdom. # Date: 4 Jan 89 08:57:25 GMT (Wed) # From: Jonathan Leffler # [British Summer Time] is fixed annually by Act of Parliament. # If you can predict what Parliament will do, you should be in # politics making a fortune, not computing. # From Chris Carrier (1996-06-14): # I remember reading in various wartime issues of the London Times the # acronym BDST for British Double Summer Time. Look for the published # time of sunrise and sunset in The Times, when BDST was in effect, and # if you find a zone reference it will say, "All times B.D.S.T." # From Joseph S. Myers (1999-09-02): # ... some military cables (WO 219/4100 - this is a copy from the # main SHAEF archives held in the US National Archives, SHAEF/5252/8/516) # agree that the usage is BDST (this appears in a message dated 17 Feb 1945). # From Joseph S. Myers (2000-10-03): # On 18th April 1941, Sir Stephen Tallents of the BBC wrote to Sir # Alexander Maxwell of the Home Office asking whether there was any # official designation; the reply of the 21st was that there wasn't # but he couldn't think of anything better than the "Double British # Summer Time" that the BBC had been using informally. # https://www.polyomino.org.uk/british-time/bbc-19410418.png # https://www.polyomino.org.uk/british-time/ho-19410421.png # From Sir Alexander Maxwell (1941-04-21) in the letter mentioned above: # [N]o official designation has as far as I know been adopted for the time # which is to be introduced in May.... # I cannot think of anything better than "Double British Summer Time" # which could not be said to run counter to any official description. # From Paul Eggert (2000-10-02): # Howse writes (p 157) 'DBST' too, but 'BDST' seems to have been common # and follows the more usual convention of putting the location name first, # so we use 'BDST'. # Peter Ilieve (1998-04-19) described at length # the history of summer time legislation in the United Kingdom. # Since 1998 Joseph S. Myers has been updating # and extending this list, which can be found in # https://www.polyomino.org.uk/british-time/ # From Joseph S. Myers (1998-01-06): # # The legal time in the UK outside of summer time is definitely GMT, not UTC; # see Lord Tanlaw's speech # https://www.publications.parliament.uk/pa/ld199798/ldhansrd/vo970611/text/70611-10.htm#70611-10_head0 # (Lords Hansard 11 June 1997 columns 964 to 976). # From Paul Eggert (2006-03-22): # # For lack of other data, follow Shanks & Pottenger for Eire in 1940-1948. # # Given Ilieve and Myers's data, the following claims by Shanks & Pottenger # are incorrect: # * Wales did not switch from GMT to daylight saving time until # 1921 Apr 3, when they began to conform with the rest of Great Britain. # Actually, Wales was identical after 1880. # * Eire had two transitions on 1916 Oct 1. # It actually just had one transition. # * Northern Ireland used single daylight saving time throughout WW II. # Actually, it conformed to Britain. # * GB-Eire changed standard time to 1 hour ahead of GMT on 1968-02-18. # Actually, that date saw the usual switch to summer time. # Standard time was not changed until 1968-10-27 (the clocks didn't change). # # Here is another incorrect claim by Shanks & Pottenger: # * Jersey, Guernsey, and the Isle of Man did not switch from GMT # to daylight saving time until 1921 Apr 3, when they began to # conform with Great Britain. # S.R.&O. 1916, No. 382 and HO 45/10811/312364 (quoted above) say otherwise. # # The following claim by Shanks & Pottenger is possible though doubtful; # we'll ignore it for now. # * Dublin's 1971-10-31 switch was at 02:00, even though London's was 03:00. # From Paul Eggert (2017-12-04): # # Dunsink Observatory (8 km NW of Dublin's center) was to Dublin as # Greenwich was to London. For example: # # "Timeball on the ballast office is down. Dunsink time." # -- James Joyce, Ulysses # # The abbreviation DMT stood for "Dublin Mean Time" or "Dunsink Mean Time"; # this being Ireland, opinions differed. # # Whitman says Dublin/Dunsink Mean Time was UT-00:25:21, which agrees # with measurements of recent visitors to the Meridian Room of Dunsink # Observatory; see Malone D. Dunsink and timekeeping. 2016-01-24. # . Malone # writes that the Nautical Almanac listed UT-00:25:22 until 1896, when # it moved to UT-00:25:21.1 (I confirmed that the 1893 edition used # the former and the 1896 edition used the latter). Evidently the # news of this change propagated slowly, as Milne 1899 still lists # UT-00:25:22 and cites the International Telegraph Bureau. As it is # not clear that there was any practical significance to the change # from UT-00:25:22 to UT-00:25:21.1 in civil timekeeping, omit this # transition for now and just use the latter value. # "Countess Markievicz ... claimed that the [1916] abolition of Dublin Mean Time # was among various actions undertaken by the 'English' government that # would 'put the whole country into the SF (Sinn Féin) camp'. She claimed # Irish 'public feeling (was) outraged by forcing of English time on us'." # -- Parsons M. Dublin lost its time zone - and 25 minutes - after 1916 Rising. # Irish Times 2014-10-27. # https://www.irishtimes.com/news/politics/dublin-lost-its-time-zone-and-25-minutes-after-1916-rising-1.1977411 # From Joseph S. Myers (2005-01-26): # Irish laws are available online at . # These include various relating to legal time, for example: # # ZZA13Y1923.html ZZA12Y1924.html ZZA8Y1925.html ZZSIV20PG1267.html # # ZZSI71Y1947.html ZZSI128Y1948.html ZZSI23Y1949.html ZZSI41Y1950.html # ZZSI27Y1951.html ZZSI73Y1952.html # # ZZSI11Y1961.html ZZSI232Y1961.html ZZSI182Y1962.html # ZZSI167Y1963.html ZZSI257Y1964.html ZZSI198Y1967.html # ZZA23Y1968.html ZZA17Y1971.html # # ZZSI67Y1981.html ZZSI212Y1982.html ZZSI45Y1986.html # ZZSI264Y1988.html ZZSI52Y1990.html ZZSI371Y1992.html # ZZSI395Y1994.html ZZSI484Y1997.html ZZSI506Y2001.html # # [These are all relative to the root, e.g., the first is # .] # # (These are those I found, but there could be more. In any case these # should allow various updates to the comments in the europe file to cover # the laws applicable in Ireland.) # # (Note that the time in the Republic of Ireland since 1968 has been defined # in terms of standard time being GMT+1 with a period of winter time when it # is GMT, rather than standard time being GMT with a period of summer time # being GMT+1.) # From Paul Eggert (1999-03-28): # Clive Feather (, 1997-03-31) # reports that Folkestone (Cheriton) Shuttle Terminal uses Concession Time # (CT), equivalent to French civil time. # Julian Hill (, 1998-09-30) reports that # trains between Dollands Moor (the freight facility next door) # and Frethun run in CT. # My admittedly uninformed guess is that the terminal has two authorities, # the French concession operators and the British civil authorities, # and that the time depends on who you're talking to. # If, say, the British police were called to the station for some reason, # I would expect the official police report to use GMT/BST and not CET/CEST. # This is a borderline case, but for now let's stick to GMT/BST. # From an anonymous contributor (1996-06-02): # The law governing time in Ireland is under Statutory Instrument SI 395/94, # which gives force to European Union 7th Council Directive No. 94/21/EC. # Under this directive, the Minister for Justice in Ireland makes appropriate # regulations. I spoke this morning with the Secretary of the Department of # Justice (tel +353 1 678 9711) who confirmed to me that the correct name is # "Irish Summer Time", abbreviated to "IST". # # From Paul Eggert (2017-12-07): # The 1996 anonymous contributor's goal was to determine the correct # abbreviation for summer time in Dublin and so the contributor # focused on the "IST", not on the "Irish Summer Time". Though the # "IST" was correct, the "Irish Summer Time" appears to have been an # error, as Ireland's Standard Time (Amendment) Act, 1971 states that # standard time in Ireland remains at UT +01 and is observed in # summer, and that Greenwich mean time is observed in winter. (Thanks # to Derick Rethans for pointing out the error.) That is, when # Ireland amended the 1968 act that established UT +01 as Irish # Standard Time, it left standard time unchanged and established GMT # as a negative daylight saving time in winter. So, in this database # IST stands for Irish Summer Time for timestamps before 1968, and for # Irish Standard Time after that. See: # http://www.irishstatutebook.ie/eli/1971/act/17/enacted/en/print # Michael Deckers (2017-06-01) gave the following URLs for Ireland's # Summer Time Act, 1925 and Summer Time Orders, 1926 and 1947: # http://www.irishstatutebook.ie/eli/1925/act/8/enacted/en/print # http://www.irishstatutebook.ie/eli/1926/sro/919/made/en/print # http://www.irishstatutebook.ie/eli/1947/sro/71/made/en/print # Rule NAME FROM TO - IN ON AT SAVE LETTER/S # Summer Time Act, 1916 Rule GB-Eire 1916 only - May 21 2:00s 1:00 BST Rule GB-Eire 1916 only - Oct 1 2:00s 0 GMT # S.R.&O. 1917, No. 358 Rule GB-Eire 1917 only - Apr 8 2:00s 1:00 BST Rule GB-Eire 1917 only - Sep 17 2:00s 0 GMT # S.R.&O. 1918, No. 274 Rule GB-Eire 1918 only - Mar 24 2:00s 1:00 BST Rule GB-Eire 1918 only - Sep 30 2:00s 0 GMT # S.R.&O. 1919, No. 297 Rule GB-Eire 1919 only - Mar 30 2:00s 1:00 BST Rule GB-Eire 1919 only - Sep 29 2:00s 0 GMT # S.R.&O. 1920, No. 458 Rule GB-Eire 1920 only - Mar 28 2:00s 1:00 BST # S.R.&O. 1920, No. 1844 Rule GB-Eire 1920 only - Oct 25 2:00s 0 GMT # S.R.&O. 1921, No. 363 Rule GB-Eire 1921 only - Apr 3 2:00s 1:00 BST Rule GB-Eire 1921 only - Oct 3 2:00s 0 GMT # S.R.&O. 1922, No. 264 Rule GB-Eire 1922 only - Mar 26 2:00s 1:00 BST Rule GB-Eire 1922 only - Oct 8 2:00s 0 GMT # The Summer Time Act, 1922 Rule GB-Eire 1923 only - Apr Sun>=16 2:00s 1:00 BST Rule GB-Eire 1923 1924 - Sep Sun>=16 2:00s 0 GMT Rule GB-Eire 1924 only - Apr Sun>=9 2:00s 1:00 BST Rule GB-Eire 1925 1926 - Apr Sun>=16 2:00s 1:00 BST # The Summer Time Act, 1925 Rule GB-Eire 1925 1938 - Oct Sun>=2 2:00s 0 GMT Rule GB-Eire 1927 only - Apr Sun>=9 2:00s 1:00 BST Rule GB-Eire 1928 1929 - Apr Sun>=16 2:00s 1:00 BST Rule GB-Eire 1930 only - Apr Sun>=9 2:00s 1:00 BST Rule GB-Eire 1931 1932 - Apr Sun>=16 2:00s 1:00 BST Rule GB-Eire 1933 only - Apr Sun>=9 2:00s 1:00 BST Rule GB-Eire 1934 only - Apr Sun>=16 2:00s 1:00 BST Rule GB-Eire 1935 only - Apr Sun>=9 2:00s 1:00 BST Rule GB-Eire 1936 1937 - Apr Sun>=16 2:00s 1:00 BST Rule GB-Eire 1938 only - Apr Sun>=9 2:00s 1:00 BST Rule GB-Eire 1939 only - Apr Sun>=16 2:00s 1:00 BST # S.R.&O. 1939, No. 1379 Rule GB-Eire 1939 only - Nov Sun>=16 2:00s 0 GMT # S.R.&O. 1940, No. 172 and No. 1883 Rule GB-Eire 1940 only - Feb Sun>=23 2:00s 1:00 BST # S.R.&O. 1941, No. 476 Rule GB-Eire 1941 only - May Sun>=2 1:00s 2:00 BDST Rule GB-Eire 1941 1943 - Aug Sun>=9 1:00s 1:00 BST # S.R.&O. 1942, No. 506 Rule GB-Eire 1942 1944 - Apr Sun>=2 1:00s 2:00 BDST # S.R.&O. 1944, No. 932 Rule GB-Eire 1944 only - Sep Sun>=16 1:00s 1:00 BST # S.R.&O. 1945, No. 312 Rule GB-Eire 1945 only - Apr Mon>=2 1:00s 2:00 BDST Rule GB-Eire 1945 only - Jul Sun>=9 1:00s 1:00 BST # S.R.&O. 1945, No. 1208 Rule GB-Eire 1945 1946 - Oct Sun>=2 2:00s 0 GMT Rule GB-Eire 1946 only - Apr Sun>=9 2:00s 1:00 BST # The Summer Time Act, 1947 Rule GB-Eire 1947 only - Mar 16 2:00s 1:00 BST Rule GB-Eire 1947 only - Apr 13 1:00s 2:00 BDST Rule GB-Eire 1947 only - Aug 10 1:00s 1:00 BST Rule GB-Eire 1947 only - Nov 2 2:00s 0 GMT # Summer Time Order, 1948 (S.I. 1948/495) Rule GB-Eire 1948 only - Mar 14 2:00s 1:00 BST Rule GB-Eire 1948 only - Oct 31 2:00s 0 GMT # Summer Time Order, 1949 (S.I. 1949/373) Rule GB-Eire 1949 only - Apr 3 2:00s 1:00 BST Rule GB-Eire 1949 only - Oct 30 2:00s 0 GMT # Summer Time Order, 1950 (S.I. 1950/518) # Summer Time Order, 1951 (S.I. 1951/430) # Summer Time Order, 1952 (S.I. 1952/451) Rule GB-Eire 1950 1952 - Apr Sun>=14 2:00s 1:00 BST Rule GB-Eire 1950 1952 - Oct Sun>=21 2:00s 0 GMT # revert to the rules of the Summer Time Act, 1925 Rule GB-Eire 1953 only - Apr Sun>=16 2:00s 1:00 BST Rule GB-Eire 1953 1960 - Oct Sun>=2 2:00s 0 GMT Rule GB-Eire 1954 only - Apr Sun>=9 2:00s 1:00 BST Rule GB-Eire 1955 1956 - Apr Sun>=16 2:00s 1:00 BST Rule GB-Eire 1957 only - Apr Sun>=9 2:00s 1:00 BST Rule GB-Eire 1958 1959 - Apr Sun>=16 2:00s 1:00 BST Rule GB-Eire 1960 only - Apr Sun>=9 2:00s 1:00 BST # Summer Time Order, 1961 (S.I. 1961/71) # Summer Time (1962) Order, 1961 (S.I. 1961/2465) # Summer Time Order, 1963 (S.I. 1963/81) Rule GB-Eire 1961 1963 - Mar lastSun 2:00s 1:00 BST Rule GB-Eire 1961 1968 - Oct Sun>=23 2:00s 0 GMT # Summer Time (1964) Order, 1963 (S.I. 1963/2101) # Summer Time Order, 1964 (S.I. 1964/1201) # Summer Time Order, 1967 (S.I. 1967/1148) Rule GB-Eire 1964 1967 - Mar Sun>=19 2:00s 1:00 BST # Summer Time Order, 1968 (S.I. 1968/117) Rule GB-Eire 1968 only - Feb 18 2:00s 1:00 BST # The British Standard Time Act, 1968 # (no summer time) # The Summer Time Act, 1972 Rule GB-Eire 1972 1980 - Mar Sun>=16 2:00s 1:00 BST Rule GB-Eire 1972 1980 - Oct Sun>=23 2:00s 0 GMT # Summer Time Order, 1980 (S.I. 1980/1089) # Summer Time Order, 1982 (S.I. 1982/1673) # Summer Time Order, 1986 (S.I. 1986/223) # Summer Time Order, 1988 (S.I. 1988/931) Rule GB-Eire 1981 1995 - Mar lastSun 1:00u 1:00 BST Rule GB-Eire 1981 1989 - Oct Sun>=23 1:00u 0 GMT # Summer Time Order, 1989 (S.I. 1989/985) # Summer Time Order, 1992 (S.I. 1992/1729) # Summer Time Order 1994 (S.I. 1994/2798) Rule GB-Eire 1990 1995 - Oct Sun>=22 1:00u 0 GMT # Summer Time Order 1997 (S.I. 1997/2982) # See EU for rules starting in 1996. # # Use Europe/London for Jersey, Guernsey, and the Isle of Man. # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone Europe/London -0:01:15 - LMT 1847 Dec 1 0:00 GB-Eire %s 1968 Oct 27 1:00 - BST 1971 Oct 31 2:00u 0:00 GB-Eire %s 1996 0:00 EU GMT/BST # From Paul Eggert (2018-02-15): # In January 2018 we discovered that the negative SAVE values in the # Eire rules cause problems with tests for ICU: # https://mm.icann.org/pipermail/tz/2018-January/025825.html # and with tests for OpenJDK: # https://mm.icann.org/pipermail/tz/2018-January/025822.html # # To work around this problem, the build procedure can translate the # following data into two forms, one with negative SAVE values and the # other form with a traditional approximation for Irish timestamps # after 1971-10-31 02:00 UTC; although this approximation has tm_isdst # flags that are reversed, its UTC offsets are correct and this often # suffices.... # # The following is like GB-Eire and EU, except with standard time in # summer and negative daylight saving time in winter. It is for when # negative SAVE values are used. # Rule NAME FROM TO - IN ON AT SAVE LETTER/S Rule Eire 1971 only - Oct 31 2:00u -1:00 - Rule Eire 1972 1980 - Mar Sun>=16 2:00u 0 - Rule Eire 1972 1980 - Oct Sun>=23 2:00u -1:00 - Rule Eire 1981 max - Mar lastSun 1:00u 0 - Rule Eire 1981 1989 - Oct Sun>=23 1:00u -1:00 - Rule Eire 1990 1995 - Oct Sun>=22 1:00u -1:00 - Rule Eire 1996 max - Oct lastSun 1:00u -1:00 - # Zone NAME STDOFF RULES FORMAT [UNTIL] #STDOFF -0:25:21.1 Zone Europe/Dublin -0:25:21 - LMT 1880 Aug 2 -0:25:21 - DMT 1916 May 21 2:00s -0:25:21 1:00 IST 1916 Oct 1 2:00s 0:00 GB-Eire %s 1921 Dec 6 # independence 0:00 GB-Eire GMT/IST 1940 Feb 25 2:00s 0:00 1:00 IST 1946 Oct 6 2:00s 0:00 - GMT 1947 Mar 16 2:00s 0:00 1:00 IST 1947 Nov 2 2:00s 0:00 - GMT 1948 Apr 18 2:00s 0:00 GB-Eire GMT/IST 1968 Oct 27 # Vanguard section, for zic and other parsers that support negative DST. 1:00 Eire IST/GMT # Rearguard section, for parsers lacking negative DST; see ziguard.awk. # 1:00 - IST 1971 Oct 31 2:00u # 0:00 GB-Eire GMT/IST 1996 # 0:00 EU GMT/IST # End of rearguard section. ############################################################################### # Europe # The following rules are for the European Union and for its # predecessor organization, the European Communities. # For brevity they are called "EU rules" elsewhere in this file. # Rule NAME FROM TO - IN ON AT SAVE LETTER/S Rule EU 1977 1980 - Apr Sun>=1 1:00u 1:00 S Rule EU 1977 only - Sep lastSun 1:00u 0 - Rule EU 1978 only - Oct 1 1:00u 0 - Rule EU 1979 1995 - Sep lastSun 1:00u 0 - Rule EU 1981 max - Mar lastSun 1:00u 1:00 S Rule EU 1996 max - Oct lastSun 1:00u 0 - # The most recent directive covers the years starting in 2002. See: # Directive 2000/84/EC of the European Parliament and of the Council # of 19 January 2001 on summer-time arrangements. # http://eur-lex.europa.eu/LexUriServ/LexUriServ.do?uri=CELEX:32000L0084:EN:NOT # W-Eur differs from EU only in that W-Eur uses standard time. Rule W-Eur 1977 1980 - Apr Sun>=1 1:00s 1:00 S Rule W-Eur 1977 only - Sep lastSun 1:00s 0 - Rule W-Eur 1978 only - Oct 1 1:00s 0 - Rule W-Eur 1979 1995 - Sep lastSun 1:00s 0 - Rule W-Eur 1981 max - Mar lastSun 1:00s 1:00 S Rule W-Eur 1996 max - Oct lastSun 1:00s 0 - # Older C-Eur rules are for convenience in the tables. # From 1977 on, C-Eur differs from EU only in that C-Eur uses standard time. Rule C-Eur 1916 only - Apr 30 23:00 1:00 S Rule C-Eur 1916 only - Oct 1 1:00 0 - Rule C-Eur 1917 1918 - Apr Mon>=15 2:00s 1:00 S Rule C-Eur 1917 1918 - Sep Mon>=15 2:00s 0 - Rule C-Eur 1940 only - Apr 1 2:00s 1:00 S Rule C-Eur 1942 only - Nov 2 2:00s 0 - Rule C-Eur 1943 only - Mar 29 2:00s 1:00 S Rule C-Eur 1943 only - Oct 4 2:00s 0 - Rule C-Eur 1944 1945 - Apr Mon>=1 2:00s 1:00 S # Whitman gives 1944 Oct 7; go with Shanks & Pottenger. Rule C-Eur 1944 only - Oct 2 2:00s 0 - # From Jesper Nørgaard Welen (2008-07-13): # # I found what is probably a typo of 2:00 which should perhaps be 2:00s # in the C-Eur rule from tz database version 2008d (this part was # corrected in version 2008d). The circumstantial evidence is simply the # tz database itself, as seen below: # # Zone Europe/Paris ... # 0:00 France WE%sT 1945 Sep 16 3:00 # # Zone Europe/Monaco ... # 0:00 France WE%sT 1945 Sep 16 3:00 # # Zone Europe/Belgrade ... # 1:00 1:00 CEST 1945 Sep 16 2:00s # # Rule France 1945 only - Sep 16 3:00 0 - # Rule Belgium 1945 only - Sep 16 2:00s 0 - # Rule Neth 1945 only - Sep 16 2:00s 0 - # # The rule line to be changed is: # # Rule C-Eur 1945 only - Sep 16 2:00 0 - # # It seems that Paris, Monaco, Rule France, Rule Belgium all agree on # 2:00 standard time, e.g. 3:00 local time. However there are no # countries that use C-Eur rules in September 1945, so the only items # affected are apparently these fictitious zones that translate acronyms # CET and MET: # # Zone CET 1:00 C-Eur CE%sT # Zone MET 1:00 C-Eur ME%sT # # It this is right then the corrected version would look like: # # Rule C-Eur 1945 only - Sep 16 2:00s 0 - # # A small step for mankind though 8-) Rule C-Eur 1945 only - Sep 16 2:00s 0 - Rule C-Eur 1977 1980 - Apr Sun>=1 2:00s 1:00 S Rule C-Eur 1977 only - Sep lastSun 2:00s 0 - Rule C-Eur 1978 only - Oct 1 2:00s 0 - Rule C-Eur 1979 1995 - Sep lastSun 2:00s 0 - Rule C-Eur 1981 max - Mar lastSun 2:00s 1:00 S Rule C-Eur 1996 max - Oct lastSun 2:00s 0 - # E-Eur differs from EU only in that E-Eur switches at midnight local time. Rule E-Eur 1977 1980 - Apr Sun>=1 0:00 1:00 S Rule E-Eur 1977 only - Sep lastSun 0:00 0 - Rule E-Eur 1978 only - Oct 1 0:00 0 - Rule E-Eur 1979 1995 - Sep lastSun 0:00 0 - Rule E-Eur 1981 max - Mar lastSun 0:00 1:00 S Rule E-Eur 1996 max - Oct lastSun 0:00 0 - # Daylight saving time for Russia and the Soviet Union # # The 1917-1921 decree URLs are from Alexander Belopolsky (2016-08-23). # Rule NAME FROM TO - IN ON AT SAVE LETTER/S Rule Russia 1917 only - Jul 1 23:00 1:00 MST # Moscow Summer Time # # Decree No. 142 (1917-12-22) http://istmat.info/node/28137 Rule Russia 1917 only - Dec 28 0:00 0 MMT # Moscow Mean Time # # Decree No. 497 (1918-05-30) http://istmat.info/node/30001 Rule Russia 1918 only - May 31 22:00 2:00 MDST # Moscow Double Summer Time Rule Russia 1918 only - Sep 16 1:00 1:00 MST # # Decree No. 258 (1919-05-29) http://istmat.info/node/37949 Rule Russia 1919 only - May 31 23:00 2:00 MDST # Rule Russia 1919 only - Jul 1 0:00u 1:00 MSD Rule Russia 1919 only - Aug 16 0:00 0 MSK # # Decree No. 63 (1921-02-03) http://istmat.info/node/45840 Rule Russia 1921 only - Feb 14 23:00 1:00 MSD # # Decree No. 121 (1921-03-07) http://istmat.info/node/45949 Rule Russia 1921 only - Mar 20 23:00 2:00 +05 # Rule Russia 1921 only - Sep 1 0:00 1:00 MSD Rule Russia 1921 only - Oct 1 0:00 0 - # Act No. 925 of the Council of Ministers of the USSR (1980-10-24): Rule Russia 1981 1984 - Apr 1 0:00 1:00 S Rule Russia 1981 1983 - Oct 1 0:00 0 - # Act No. 967 of the Council of Ministers of the USSR (1984-09-13), repeated in # Act No. 227 of the Council of Ministers of the USSR (1989-03-14): Rule Russia 1984 1995 - Sep lastSun 2:00s 0 - Rule Russia 1985 2010 - Mar lastSun 2:00s 1:00 S # Rule Russia 1996 2010 - Oct lastSun 2:00s 0 - # As described below, Russia's 2014 change affects Zone data, not Rule data. # From Stepan Golosunov (2016-03-07): # Wikipedia and other sources refer to the Act of the Council of # Ministers of the USSR from 1988-01-04 No. 5 and the Act of the # Council of Ministers of the USSR from 1989-03-14 No. 227. # # I did not find full texts of these acts. For the 1989 one we have # title at https://base.garant.ru/70754136/ : # "About change in calculation of time on the territories of # Lithuanian SSR, Latvian SSR and Estonian SSR, Astrakhan, # Kaliningrad, Kirov, Kuybyshev, Ulyanovsk and Uralsk oblasts". # And http://astrozet.net/files/Zones/DOC/RU/1980-925.txt appears to # contain quotes from both acts: Since last Sunday of March 1988 rules # of the second time belt are installed in Volgograd and Saratov # oblasts. Since last Sunday of March 1989: # a) Lithuanian SSR, Latvian SSR, Estonian SSR, Kaliningrad oblast: # second time belt rules without extra hour (Moscow-1); # b) Astrakhan, Kirov, Kuybyshev, Ulyanovsk oblasts: second time belt # rules (Moscow time) # c) Uralsk oblast: third time belt rules (Moscow+1). # From Stepan Golosunov (2016-03-27): # Unamended version of the act of the # Government of the Russian Federation No. 23 from 08.01.1992 # http://pravo.gov.ru/proxy/ips/?docbody=&nd=102014034&rdk=0 # says that every year clocks were to be moved forward on last Sunday # of March at 2 hours and moved backwards on last Sunday of September # at 3 hours. It was amended in 1996 to replace September with October. # From Alexander Krivenyshev (2011-06-14): # According to Kremlin press service, Russian President Dmitry Medvedev # signed a federal law "On calculation of time" on June 9, 2011. # According to the law Russia is abolishing daylight saving time. # # Medvedev signed a law "On the Calculation of Time" (in russian): # http://bmockbe.ru/events/?ID=7583 # # Medvedev signed a law on the calculation of the time (in russian): # https://www.regnum.ru/news/polit/1413906.html # From Arthur David Olson (2011-06-15): # Take "abolishing daylight saving time" to mean that time is now considered # to be standard. # Previous editions of this database used abbreviations like MET DST # for Central European Summer Time, but this didn't agree with common usage. # From Markus Kuhn (1996-07-12): # The official German names ... are # # Mitteleuropäische Zeit (MEZ) = UTC+01:00 # Mitteleuropäische Sommerzeit (MESZ) = UTC+02:00 # # as defined in the German Time Act (Gesetz über die Zeitbestimmung (ZeitG), # 1978-07-25, Bundesgesetzblatt, Jahrgang 1978, Teil I, S. 1110-1111).... # I wrote ... to the German Federal Physical-Technical Institution # # Physikalisch-Technische Bundesanstalt (PTB) # Laboratorium 4.41 "Zeiteinheit" # Postfach 3345 # D-38023 Braunschweig # phone: +49 531 592-0 # # ... I received today an answer letter from Dr. Peter Hetzel, head of the PTB # department for time and frequency transmission. He explained that the # PTB translates MEZ and MESZ into English as # # Central European Time (CET) = UTC+01:00 # Central European Summer Time (CEST) = UTC+02:00 # Albania # Rule NAME FROM TO - IN ON AT SAVE LETTER/S Rule Albania 1940 only - Jun 16 0:00 1:00 S Rule Albania 1942 only - Nov 2 3:00 0 - Rule Albania 1943 only - Mar 29 2:00 1:00 S Rule Albania 1943 only - Apr 10 3:00 0 - Rule Albania 1974 only - May 4 0:00 1:00 S Rule Albania 1974 only - Oct 2 0:00 0 - Rule Albania 1975 only - May 1 0:00 1:00 S Rule Albania 1975 only - Oct 2 0:00 0 - Rule Albania 1976 only - May 2 0:00 1:00 S Rule Albania 1976 only - Oct 3 0:00 0 - Rule Albania 1977 only - May 8 0:00 1:00 S Rule Albania 1977 only - Oct 2 0:00 0 - Rule Albania 1978 only - May 6 0:00 1:00 S Rule Albania 1978 only - Oct 1 0:00 0 - Rule Albania 1979 only - May 5 0:00 1:00 S Rule Albania 1979 only - Sep 30 0:00 0 - Rule Albania 1980 only - May 3 0:00 1:00 S Rule Albania 1980 only - Oct 4 0:00 0 - Rule Albania 1981 only - Apr 26 0:00 1:00 S Rule Albania 1981 only - Sep 27 0:00 0 - Rule Albania 1982 only - May 2 0:00 1:00 S Rule Albania 1982 only - Oct 3 0:00 0 - Rule Albania 1983 only - Apr 18 0:00 1:00 S Rule Albania 1983 only - Oct 1 0:00 0 - Rule Albania 1984 only - Apr 1 0:00 1:00 S # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone Europe/Tirane 1:19:20 - LMT 1914 1:00 - CET 1940 Jun 16 1:00 Albania CE%sT 1984 Jul 1:00 EU CE%sT # Andorra # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone Europe/Andorra 0:06:04 - LMT 1901 0:00 - WET 1946 Sep 30 1:00 - CET 1985 Mar 31 2:00 1:00 EU CE%sT # Austria # Milne says Vienna time was 1:05:21. # From Paul Eggert (2006-03-22): Shanks & Pottenger give 1918-06-16 and # 1945-11-18, but the Austrian Federal Office of Metrology and # Surveying (BEV) gives 1918-09-16 and for Vienna gives the "alleged" # date of 1945-04-12 with no time. For the 1980-04-06 transition # Shanks & Pottenger give 02:00, the BEV 00:00. Go with the BEV, # and guess 02:00 for 1945-04-12. # From Alois Treindl (2019-07-22): # In 1946 the end of DST was on Monday, 7 October 1946, at 3:00 am. # Shanks had this right. Source: Die Weltpresse, 5. Oktober 1946, page 5. # Rule NAME FROM TO - IN ON AT SAVE LETTER/S Rule Austria 1920 only - Apr 5 2:00s 1:00 S Rule Austria 1920 only - Sep 13 2:00s 0 - Rule Austria 1946 only - Apr 14 2:00s 1:00 S Rule Austria 1946 only - Oct 7 2:00s 0 - Rule Austria 1947 1948 - Oct Sun>=1 2:00s 0 - Rule Austria 1947 only - Apr 6 2:00s 1:00 S Rule Austria 1948 only - Apr 18 2:00s 1:00 S Rule Austria 1980 only - Apr 6 0:00 1:00 S Rule Austria 1980 only - Sep 28 0:00 0 - # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone Europe/Vienna 1:05:21 - LMT 1893 Apr 1:00 C-Eur CE%sT 1920 1:00 Austria CE%sT 1940 Apr 1 2:00s 1:00 C-Eur CE%sT 1945 Apr 2 2:00s 1:00 1:00 CEST 1945 Apr 12 2:00s 1:00 - CET 1946 1:00 Austria CE%sT 1981 1:00 EU CE%sT # Belarus # # From Stepan Golosunov (2016-07-02): # http://www.lawbelarus.com/repub/sub30/texf9611.htm # (Act of the Cabinet of Ministers of the Republic of Belarus from # 1992-03-25 No. 157) ... says clocks were to be moved forward at 2:00 # on last Sunday of March and backward at 3:00 on last Sunday of September # (the same as previous USSR and contemporary Russian regulations). # # From Yauhen Kharuzhy (2011-09-16): # By latest Belarus government act Europe/Minsk timezone was changed to # GMT+3 without DST (was GMT+2 with DST). # # Sources (Russian language): # http://www.belta.by/ru/all_news/society/V-Belarusi-otmenjaetsja-perexod-na-sezonnoe-vremja_i_572952.html # http://naviny.by/rubrics/society/2011/09/16/ic_articles_116_175144/ # https://news.tut.by/society/250578.html # # From Alexander Bokovoy (2014-10-09): # Belarussian government decided against changing to winter time.... # http://eng.belta.by/all_news/society/Belarus-decides-against-adjusting-time-in-Russias-wake_i_76335.html # # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone Europe/Minsk 1:50:16 - LMT 1880 1:50 - MMT 1924 May 2 # Minsk Mean Time 2:00 - EET 1930 Jun 21 3:00 - MSK 1941 Jun 28 1:00 C-Eur CE%sT 1944 Jul 3 3:00 Russia MSK/MSD 1990 3:00 - MSK 1991 Mar 31 2:00s 2:00 Russia EE%sT 2011 Mar 27 2:00s 3:00 - %z # Belgium # Luxembourg # Netherlands # # From Michael Deckers (2019-08-25): # The exposition in the web page # https://www.bestor.be/wiki/index.php/Voyager_dans_le_temps._L%E2%80%99introduction_de_la_norme_de_Greenwich_en_Belgique # gives several contemporary sources from which one can conclude that # the switch in Europe/Brussels on 1892-05-01 was from 00:17:30 to 00:00:00. # # From Paul Eggert (2019-08-28): # This quote helps explain the late-1914 situation: # In early November 1914, the Germans imposed the time zone used in central # Europe and forced the inhabitants to set their watches and public clocks # sixty minutes ahead. Many were reluctant to accept "German time" and # continued to use "Belgian time" among themselves. Reflecting the spirit of # resistance that arose in the population, a song made fun of this change.... # The song ended: # Putting your clock forward # Will but hasten the happy hour # When we kick out the Boches! # See: Pluvinage G. Brussels on German time. Cahiers Bruxellois - # Brusselse Cahiers. 2014;XLVI(1E):15-38. # https://www.cairn.info/revue-cahiers-bruxellois-2014-1E-page-15.htm # # Entries from 1914 through 1917 are taken from "De tijd in België" # . # Entries from 1918 through 1991 are taken from: # Annuaire de L'Observatoire Royal de Belgique, # Avenue Circulaire, 3, B-1180 BRUXELLES, CLVIIe année, 1991 # (Imprimerie HAYEZ, s.p.r.l., Rue Fin, 4, 1080 BRUXELLES, MCMXC), # pp 8-9. # Thanks to Pascal Delmoitie for the 1918/1991 references. # The 1918 rules are listed for completeness; they apply to unoccupied Belgium. # Assume Brussels switched to WET in 1918 when the armistice took effect. # # Rule NAME FROM TO - IN ON AT SAVE LETTER/S Rule Belgium 1918 only - Mar 9 0:00s 1:00 S Rule Belgium 1918 1919 - Oct Sat>=1 23:00s 0 - Rule Belgium 1919 only - Mar 1 23:00s 1:00 S Rule Belgium 1920 only - Feb 14 23:00s 1:00 S Rule Belgium 1920 only - Oct 23 23:00s 0 - Rule Belgium 1921 only - Mar 14 23:00s 1:00 S Rule Belgium 1921 only - Oct 25 23:00s 0 - Rule Belgium 1922 only - Mar 25 23:00s 1:00 S Rule Belgium 1922 1927 - Oct Sat>=1 23:00s 0 - Rule Belgium 1923 only - Apr 21 23:00s 1:00 S Rule Belgium 1924 only - Mar 29 23:00s 1:00 S Rule Belgium 1925 only - Apr 4 23:00s 1:00 S # DISHO writes that a royal decree of 1926-02-22 specified the Sun following 3rd # Sat in Apr (except if it's Easter, in which case it's one Sunday earlier), # to Sun following 1st Sat in Oct, and that a royal decree of 1928-09-15 # changed the transition times to 02:00 GMT. Rule Belgium 1926 only - Apr 17 23:00s 1:00 S Rule Belgium 1927 only - Apr 9 23:00s 1:00 S Rule Belgium 1928 only - Apr 14 23:00s 1:00 S Rule Belgium 1928 1938 - Oct Sun>=2 2:00s 0 - Rule Belgium 1929 only - Apr 21 2:00s 1:00 S Rule Belgium 1930 only - Apr 13 2:00s 1:00 S Rule Belgium 1931 only - Apr 19 2:00s 1:00 S Rule Belgium 1932 only - Apr 3 2:00s 1:00 S Rule Belgium 1933 only - Mar 26 2:00s 1:00 S Rule Belgium 1934 only - Apr 8 2:00s 1:00 S Rule Belgium 1935 only - Mar 31 2:00s 1:00 S Rule Belgium 1936 only - Apr 19 2:00s 1:00 S Rule Belgium 1937 only - Apr 4 2:00s 1:00 S Rule Belgium 1938 only - Mar 27 2:00s 1:00 S Rule Belgium 1939 only - Apr 16 2:00s 1:00 S Rule Belgium 1939 only - Nov 19 2:00s 0 - Rule Belgium 1940 only - Feb 25 2:00s 1:00 S Rule Belgium 1944 only - Sep 17 2:00s 0 - Rule Belgium 1945 only - Apr 2 2:00s 1:00 S Rule Belgium 1945 only - Sep 16 2:00s 0 - Rule Belgium 1946 only - May 19 2:00s 1:00 S Rule Belgium 1946 only - Oct 7 2:00s 0 - # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone Europe/Brussels 0:17:30 - LMT 1880 0:17:30 - BMT 1892 May 1 00:17:30 0:00 - WET 1914 Nov 8 1:00 - CET 1916 May 1 0:00 1:00 C-Eur CE%sT 1918 Nov 11 11:00u 0:00 Belgium WE%sT 1940 May 20 2:00s 1:00 C-Eur CE%sT 1944 Sep 3 1:00 Belgium CE%sT 1977 1:00 EU CE%sT # Bulgaria # # From Plamen Simenov via Steffen Thorsen (1999-09-09): # A document of Government of Bulgaria (No. 94/1997) says: # EET -> EETDST is in 03:00 Local time in last Sunday of March ... # EETDST -> EET is in 04:00 Local time in last Sunday of October # # Rule NAME FROM TO - IN ON AT SAVE LETTER/S Rule Bulg 1979 only - Mar 31 23:00 1:00 S Rule Bulg 1979 only - Oct 1 1:00 0 - Rule Bulg 1980 1982 - Apr Sat>=1 23:00 1:00 S Rule Bulg 1980 only - Sep 29 1:00 0 - Rule Bulg 1981 only - Sep 27 2:00 0 - # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone Europe/Sofia 1:33:16 - LMT 1880 1:56:56 - IMT 1894 Nov 30 # Istanbul MT? 2:00 - EET 1942 Nov 2 3:00 1:00 C-Eur CE%sT 1945 1:00 - CET 1945 Apr 2 3:00 2:00 - EET 1979 Mar 31 23:00 2:00 Bulg EE%sT 1982 Sep 26 3:00 2:00 C-Eur EE%sT 1991 2:00 E-Eur EE%sT 1997 2:00 EU EE%sT # Cyprus # Please see the 'asia' file for Asia/Nicosia. # Czech Republic (Czechia) # Slovakia # # From Ivan Benovic (2024-01-30): # https://www.slov-lex.sk/pravne-predpisy/SK/ZZ/1946/54/ # (This is an official link to the Czechoslovak Summer Time Act of # March 8, 1946 that authorizes the Czechoslovak government to set the # exact dates of change to summer time and back to Central European Time. # The act also implicitly confirms Central European Time as the # official time zone of Czechoslovakia and currently remains in force # in both the Czech Republic and Slovakia.) # https://www.psp.cz/eknih/1945pns/tisky/t0216_00.htm # (This is a link to the original legislative proposal dating back to # February 22, 1946. The accompanying memorandum to the proposal says # that an advisory committee on European railroad transportation that # met in Brussels in October 1945 decided that the change of time # should be carried out in all participating countries in a strictly # coordinated manner....) # # From Paul Eggert (2024-01-30): # The source for Czech data is: Kdy začíná a končí letní čas. # https://kalendar.beda.cz/kdy-zacina-a-konci-letni-cas # Its main text disagrees with its quoted sources only in 1918, # where the main text says spring and autumn transitions # occurred at 02:00 and 03:00 respectively (as usual), # whereas the 1918 source "Oznámení o zavedení letního času v roce 1918" # says transitions were at 01:00 and 02:00 respectively. # As the 1918 source appears to be a humorous piece, and it is # unlikely that Prague would have disagreed with its neighbors by an hour, # go with the main text for now. # # We know of no English-language name for historical Czech winter time; # abbreviate it as "GMT", as it happened to be GMT. # # Rule NAME FROM TO - IN ON AT SAVE LETTER/S Rule Czech 1945 only - Apr Mon>=1 2:00s 1:00 S Rule Czech 1945 only - Oct 1 2:00s 0 - Rule Czech 1946 only - May 6 2:00s 1:00 S Rule Czech 1946 1949 - Oct Sun>=1 2:00s 0 - Rule Czech 1947 1948 - Apr Sun>=15 2:00s 1:00 S Rule Czech 1949 only - Apr 9 2:00s 1:00 S # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone Europe/Prague 0:57:44 - LMT 1850 0:57:44 - PMT 1891 Oct # Prague Mean Time 1:00 C-Eur CE%sT 1945 May 9 1:00 Czech CE%sT 1946 Dec 1 3:00 # Vanguard section, for zic and other parsers that support negative DST. 1:00 -1:00 GMT 1947 Feb 23 2:00 # Rearguard section, for parsers lacking negative DST; see ziguard.awk. # 0:00 - GMT 1947 Feb 23 2:00 # End of rearguard section. 1:00 Czech CE%sT 1979 1:00 EU CE%sT # Faroe Is # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone Atlantic/Faroe -0:27:04 - LMT 1908 Jan 11 # Tórshavn 0:00 - WET 1981 0:00 EU WE%sT # Greenland # # From Paul Eggert (2026-01-22): # During World War II, Greenland was effectively independent of Denmark and # observed daylight saving time. TIME, volume 37, page 23 (1941-04-21) # says, # "Penfield and West made their way to the U.S.'s most northerly consulate. # They were astonished to find that Greenlanders, with almost 24 hours of # sunlight a day during the summer, have daylight saving time." # As the details are unknown they are omitted from the data for now. # # During World War II, Germany maintained secret manned weather stations in # East Greenland and Franz Josef Land, but we don't know their time zones. # Also, they're likely out of scope for the database # as we lack resources to track every bit of military activity. # My source for this is Wilhelm Dege's book mentioned under Svalbard. # # From Paul Eggert (2017-12-10): # Greenland joined the European Communities as part of Denmark, # obtained home rule on 1979-05-01, and left the European Communities # on 1985-02-01. It therefore should have been using EU # rules at least through 1984. Shanks & Pottenger say Scoresbysund and Godthåb # used C-Eur rules after 1980, but IATA SSIM (1991/1996) says they use EU # rules since at least 1991. Assume EU rules since 1980. # From Gwillim Law (2001-06-06), citing # (2001-03-15), # and with translations corrected by Steffen Thorsen: # # Greenland has four local times, and the relation to UTC # is according to the following time line: # # The military zone near Thule UTC-4 # Standard Greenland time UTC-3 # Scoresbysund UTC-1 # Danmarkshavn UTC # # In the military area near Thule and in Danmarkshavn DST will not be # introduced. # From Rives McDow (2001-11-01): # # I correspond regularly with the Dansk Polarcenter, and wrote them at # the time to clarify the situation in Thule. Unfortunately, I have # not heard back from them regarding my recent letter. [But I have # info from earlier correspondence.] # # According to the center, a very small local time zone around Thule # Air Base keeps the time according to UTC-4, implementing daylight # savings using North America rules, changing the time at 02:00 local time.... # # The east coast of Greenland north of the community of Scoresbysund # uses UTC in the same way as in Iceland, year round, with no dst. # There are just a few stations on this coast, including the # Danmarkshavn ICAO weather station mentioned in your September 29th # email. The other stations are two sledge patrol stations in # Mestersvig and Daneborg, the air force base at Station Nord, and the # DPC research station at Zackenberg. # # Scoresbysund and two small villages nearby keep time UTC-1 and use # the same daylight savings time period as in West Greenland (Godthåb). # # The rest of Greenland, including Godthåb (this area, although it # includes central Greenland, is known as west Greenland), keeps time # UTC-3, with daylight savings methods according to European rules. # # It is common procedure to use UTC 0 in the wilderness of East and # North Greenland, because it is mainly Icelandic aircraft operators # maintaining traffic in these areas. However, the official status of # this area is that it sticks with Godthåb time. This area might be # considered a dual time zone in some respects because of this. # From Rives McDow (2001-11-19): # I heard back from someone stationed at Thule; the time change took place # there at 2:00 AM. # From Paul Eggert (2006-03-22): # From 1997 on the CIA map shows Danmarkshavn on GMT; # the 1995 map as like Godthåb. # For lack of better info, assume they were like Godthåb before 1996. # startkart.no says Thule does not observe DST, but this is clearly an error, # so go with Shanks & Pottenger for Thule transitions until this year. # For 2007 on assume Thule will stay in sync with US DST rules. # From J William Piggott (2016-02-20): # "Greenland north of the community of Scoresbysund" is officially named # "National Park" by Executive Order: # http://naalakkersuisut.gl/~/media/Nanoq/Files/Attached%20Files/Engelske-tekster/Legislation/Executive%20Order%20National%20Park.rtf # It is their only National Park. # From Jonas Nyrup (2022-11-24): # On last Saturday in October 2023 when DST ends America/Nuuk will switch # from -03/-02 to -02/-01 # https://sermitsiaq.ag/forslagtidsforskel-danmark-mindskes-sommertid-beholdes # ... # https://sermitsiaq.ag/groenland-skifte-tidszone-trods-bekymringer # # From Jürgen Appel (2022-11-25): # https://ina.gl/samlinger/oversigt-over-samlinger/samling/dagsordener/dagsorden.aspx?lang=da&day=24-11-2022 # # From Thomas M. Steenholdt (2022-12-02): # - The bill to move America/Nuuk from UTC-03 to UTC-02 passed. # - The bill to stop observing DST did not (Greenland will stop observing DST # when EU does). # Details on the implementation are here (section 6): # https://ina.gl/dvd/EM%202022/pdf/media/2553529/pkt17_em2022_tidens_bestemmelse_bem_da.pdf # This is how the change will be implemented: # 1. The shift *to* DST in 2023 happens as normal. # 2. The shift *from* DST in 2023 happens as normal, but coincides with the # shift to UTC-02 normaltime (people will not change their clocks here). # 3. After this, DST is still observed, but as -02/-01 instead of -03/-02. # # From Múte Bourup Egede via Jógvan Svabo Samuelsen (2023-03-15): # Greenland will not switch to Daylight Saving Time this year, 2023, # because the standard time for Greenland will change from UTC -3 to UTC -2. # However, Greenland will change to Daylight Saving Time again in 2024 # and onwards. # From Jule Dabars (2023-10-29): # https://www.dr.dk/nyheder/seneste/i-nat-skal-uret-stilles-en-time-tilbage-men-foerste-gang-sker-det-ikke-i-groenland # with a link to that page: # https://naalakkersuisut.gl/Nyheder/2023/10/2710_sommertid # ... Ittoqqortoormiit joins the time of Nuuk at March 2024. # What would mean that America/Scoresbysund would either be in -01 year round # or in -02/-01 like America/Nuuk, but no longer in -01/+00. # # From Paul Eggert (2023-10-29): # For now, assume it will be like America/Nuuk. # Rule NAME FROM TO - IN ON AT SAVE LETTER/S Rule Thule 1991 1992 - Mar lastSun 2:00 1:00 D Rule Thule 1991 1992 - Sep lastSun 2:00 0 S Rule Thule 1993 2006 - Apr Sun>=1 2:00 1:00 D Rule Thule 1993 2006 - Oct lastSun 2:00 0 S Rule Thule 2007 max - Mar Sun>=8 2:00 1:00 D Rule Thule 2007 max - Nov Sun>=1 2:00 0 S # # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone America/Danmarkshavn -1:14:40 - LMT 1916 Jul 28 -3:00 - %z 1980 Apr 6 2:00 -3:00 EU %z 1996 0:00 - GMT # # Use the old name Scoresbysund, as the current name Ittoqqortoormiit # exceeds tzdb's 14-letter limit and has no common English abbreviation. Zone America/Scoresbysund -1:27:52 - LMT 1916 Jul 28 # Ittoqqortoormiit -2:00 - %z 1980 Apr 6 2:00 -2:00 C-Eur %z 1981 Mar 29 -1:00 EU %z 2024 Mar 31 -2:00 EU %z Zone America/Nuuk -3:26:56 - LMT 1916 Jul 28 # Godthåb -3:00 - %z 1980 Apr 6 2:00 -3:00 EU %z 2023 Mar 26 1:00u -2:00 - %z 2023 Oct 29 1:00u -2:00 EU %z Zone America/Thule -4:35:08 - LMT 1916 Jul 28 # Pituffik -4:00 Thule A%sT # Estonia # # From Paul Eggert (2016-03-18): # The 1989 transition is from USSR act No. 227 (1989-03-14). # # From Peter Ilieve (1994-10-15): # A relative in Tallinn confirms the accuracy of the data for 1989 onwards # [through 1994] and gives the legal authority for it, # a regulation of the Government of Estonia, No. 111 of 1989.... # # From Peter Ilieve (1996-10-28): # [IATA SSIM (1992/1996) claims that the Baltic republics switch at 01:00s, # but a relative confirms that Estonia still switches at 02:00s, writing:] # "I do not [know] exactly but there are some little different # (confusing) rules for International Air and Railway Transport Schedules # conversion in Sunday connected with end of summer time in Estonia.... # A discussion is running about the summer time efficiency and effect on # human physiology. It seems that Estonia maybe will not change to # summer time next spring." # From Peter Ilieve (1998-11-04), heavily edited: # The 1998-09-22 Estonian time law # http://trip.rk.ee/cgi-bin/thw?${BASE}=akt&${OOHTML}=rtd&TA=1998&TO=1&AN=1390 # refers to the Eighth Directive and cites the association agreement between # the EU and Estonia, ratified by the Estonian law (RT II 1995, 22-27, 120). # # I also asked [my relative] whether they use any standard abbreviation # for their standard and summer times. He says no, they use "suveaeg" # (summer time) and "talveaeg" (winter time). # From The Baltic Times (1999-09-09) # via Steffen Thorsen: # This year will mark the last time Estonia shifts to summer time, # a council of the ruling coalition announced Sept. 6.... # But what this could mean for Estonia's chances of joining the European # Union are still unclear. In 1994, the EU declared summer time compulsory # for all member states until 2001. Brussels has yet to decide what to do # after that. # From Mart Oruaas (2000-01-29): # Regulation No. 301 (1999-10-12) obsoletes previous regulation # No. 206 (1998-09-22) and thus sticks Estonia to +02:00 GMT for all # the year round. The regulation is effective 1999-11-01. # From Toomas Soome (2002-02-21): # The Estonian government has changed once again timezone politics. # Now we are using again EU rules. # # From Urmet Jänes (2002-03-28): # The legislative reference is Government decree No. 84 on 2002-02-21. # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone Europe/Tallinn 1:39:00 - LMT 1880 1:39:00 - TMT 1918 Feb # Tallinn Mean Time 1:00 C-Eur CE%sT 1919 Jul 1:39:00 - TMT 1921 May 2:00 - EET 1940 Aug 6 3:00 - MSK 1941 Sep 15 1:00 C-Eur CE%sT 1944 Sep 22 3:00 Russia MSK/MSD 1989 Mar 26 2:00s 2:00 1:00 EEST 1989 Sep 24 2:00s 2:00 C-Eur EE%sT 1998 Sep 22 2:00 EU EE%sT 1999 Oct 31 4:00 2:00 - EET 2002 Feb 21 2:00 EU EE%sT # Finland # From Hannu Strang (1994-09-25 06:03:37 UTC): # Well, here in Helsinki we're just changing from summer time to regular one, # and it's supposed to change at 4am... # From Janne Snabb (2010-07-15): # # I noticed that the Finland data is not accurate for years 1981 and 1982. # During these two first trial years the DST adjustment was made one hour # earlier than in forthcoming years. Starting 1983 the adjustment was made # according to the central European standards. # # This is documented in Heikki Oja: Aikakirja 2007, published by The Almanac # Office of University of Helsinki, ISBN 952-10-3221-9, available online (in # Finnish) at # https://almanakka.helsinki.fi/aikakirja/Aikakirja2007kokonaan.pdf # # Page 105 (56 in PDF version) has a handy table of all past daylight savings # transitions. It is easy enough to interpret without Finnish skills. # # This is also confirmed by Finnish Broadcasting Company's archive at: # http://www.yle.fi/elavaarkisto/?s=s&g=1&ag=5&t=&a=3401 # # The news clip from 1981 says that "the time between 2 and 3 o'clock does not # exist tonight." # From Konstantin Hyppönen (2014-06-13): # [Heikki Oja's book Aikakirja 2013] # https://almanakka.helsinki.fi/images/aikakirja/Aikakirja2013kokonaan.pdf # pages 104-105, including a scan from a newspaper published on Apr 2 1942 # say that ... [o]n Apr 2 1942, 24 o'clock (which means Apr 3 1942, # 00:00), clocks were moved one hour forward. The newspaper # mentions "on the night from Thursday to Friday".... # On Oct 4 1942, clocks were moved at 1:00 one hour backwards. # # From Paul Eggert (2014-06-14): # Go with Oja over Shanks. # Rule NAME FROM TO - IN ON AT SAVE LETTER/S Rule Finland 1942 only - Apr 2 24:00 1:00 S Rule Finland 1942 only - Oct 4 1:00 0 - Rule Finland 1981 1982 - Mar lastSun 2:00 1:00 S Rule Finland 1981 1982 - Sep lastSun 3:00 0 - # Milne says Helsinki (Helsingfors) time was 1:39:49.2 (official document). # Zone NAME STDOFF RULES FORMAT [UNTIL] #STDOFF 1:39:49.2 Zone Europe/Helsinki 1:39:49 - LMT 1878 May 31 1:39:49 - HMT 1921 May # Helsinki Mean Time 2:00 Finland EE%sT 1983 2:00 EU EE%sT # France # Monaco # From Robert H. van Gent (2025-07-21): # The most recent issue of the Annuaire [par le Bureau des Longitudes] # on Gallica (2021) ... lists information for France # https://gallica.bnf.fr/ark:/12148/bpt6k9127672b/f52.item # From Paul Eggert (2025-07-21): # Go with the 2020 Annuaire (published 2021) except as noted below. # From Ciro Discepolo (2000-12-20): # # Henri Le Corre, Régimes horaires pour le monde entier, Éditions # Traditionnelles - Paris 2 books, 1993 # # Gabriel, Traité de l'heure dans le monde, Guy Trédaniel, # Paris, 1991 # # Françoise Gauquelin, Problèmes de l'heure résolus en astrologie, # Guy Trédaniel, Paris 1987 # From Michael Deckers (2020-06-11): # the law of 1891 # was published on 1891-03-15, so it could only take force on 1891-03-16. # From Michael Deckers (2020-06-10): # Le Gaulois, 1911-03-11, page 1/6, online at # https://www.retronews.fr/societe/echo-de-presse/2018/01/29/1911-change-lheure-de-paris # ... [ Instantly, all pressure driven clock dials halted... Nine minutes and # twenty-one seconds later the hands resumed their circular motion. ] # There are also precise reports about how the change was prepared in train # stations: all the publicly visible clocks stopped at midnight railway time # (or were covered), only the chief of service had a watch, labeled # "Heure ancienne", that he kept running until it reached 00:04:21, when # he announced "Heure nouvelle". See the "Le Petit Journal 1911-03-11". # https://gallica.bnf.fr/ark:/12148/bpt6k6192911/f1.item.zoom # # From Michael Deckers (2020-06-12): # That "all French clocks stopped" for 00:09:21 is a misreading of French # newspapers; this sort of adjustment applies only to certain # remote-controlled clocks ("pendules pneumatiques", of which there existed # perhaps a dozen in Paris, and which simply could not be set back remotely), # but not to all the clocks in all French towns and villages. For instance, # the following story in the "Courrier de Saône-et-Loire" 1911-03-11, page 2: # only works if legal time was stepped back (was not monotone): ... # [One can observe that children who had been born at midnight less 5 # minutes and who had died at midnight of the old time, would turn out to # be dead before being born, time having been set back and having # suppressed 9 minutes and 25 seconds of their existence, that is, more # than they could spend.] # # From Paul Eggert (2020-06-12): # French time in railway stations was legally five minutes behind civil time, # which explains why railway "old time" ran to 00:04:21 instead of to 00:09:21. # The law's text (which Michael Deckers noted is at # ) says only that # at 1911-03-11 00:00 legal time was that of Paris mean time delayed by # nine minutes and twenty-one seconds, and does not say how the # transition from Paris mean time was to occur. # # tzdb has no way to represent stopped clocks. As the railway practice # was to keep a watch running on "old time" to decide when to restart # the other clocks, this could be modeled as a transition for "old time" at # 00:09:21. However, since the law was ambiguous and clocks outside railway # stations were probably done haphazardly with the popular impression being # that the transition was done at 00:00 "old time", simply leave the time # blank; this causes zic to default to 00:00 "old time" which is good enough. # Do something similar for the 1891-03-16 transition. There are similar # problems in Algiers, Monaco and Tunis. # # Rule NAME FROM TO - IN ON AT SAVE LETTER/S Rule France 1916 only - Jun 14 23:00s 1:00 S Rule France 1916 1919 - Oct Sun>=1 23:00s 0 - Rule France 1917 only - Mar 24 23:00s 1:00 S Rule France 1918 only - Mar 9 23:00s 1:00 S Rule France 1919 only - Mar 1 23:00s 1:00 S Rule France 1920 only - Feb 14 23:00s 1:00 S Rule France 1920 only - Oct 23 23:00s 0 - Rule France 1921 only - Mar 14 23:00s 1:00 S Rule France 1921 only - Oct 25 23:00s 0 - Rule France 1922 only - Mar 25 23:00s 1:00 S # From Robert H. van Gent (2025-07-22): # There is a curious history behind the erroneous date for the start of # daylight saving in France in 1923 as listed in the current issues of # the Annuaire du Bureau des Longitudes. [See:] # https://lists.iana.org/hyperkitty/list/tz@iana.org/message/MYQEJMSXO2AIEZ3UIXZKMTTAIPY7KNT2/ # From Brian Inglis (2025-07-23): # Légifrance JORF No. 0073 du 15 mars 1922 # https://www.legifrance.gouv.fr/jorf/jo/id/JORFCONT000000008324 # Légifrance JORF No. 0139 du 25 mai 1923 # https://www.legifrance.gouv.fr/jorf/jo/id/JORFCONT000000008416 # From Paul Eggert (2025-07-23): # The latter specifies March's last Saturday at 23:00 to October's first # Saturday at 24:00, except that if neighboring allies agree the dates # can be moved to April's third Saturday and September's third Saturday. # Apparently spring 1923 was tricky. DISHO writes that in 1930, # because of Easter, the transitions were Apr 12 and Oct 5. # Use the 2020 Annuaire dates, except for spring 1923 where # Shanks & Pottenger's May 26 matches the dates given in the 1924 and # 1961-2001 issues of the Annuaire. Rule France 1922 1938 - Oct Sat>=1 23:00s 0 - Rule France 1923 only - May 26 23:00s 1:00 S Rule France 1924 only - Mar 29 23:00s 1:00 S Rule France 1925 only - Apr 4 23:00s 1:00 S Rule France 1926 only - Apr 17 23:00s 1:00 S Rule France 1927 only - Apr 9 23:00s 1:00 S Rule France 1928 only - Apr 14 23:00s 1:00 S Rule France 1929 only - Apr 20 23:00s 1:00 S Rule France 1930 only - Apr 12 23:00s 1:00 S Rule France 1931 only - Apr 18 23:00s 1:00 S Rule France 1932 only - Apr 2 23:00s 1:00 S Rule France 1933 only - Mar 25 23:00s 1:00 S Rule France 1934 only - Apr 7 23:00s 1:00 S Rule France 1935 only - Mar 30 23:00s 1:00 S Rule France 1936 only - Apr 18 23:00s 1:00 S Rule France 1937 only - Apr 3 23:00s 1:00 S Rule France 1938 only - Mar 26 23:00s 1:00 S Rule France 1939 only - Apr 15 23:00s 1:00 S Rule France 1939 only - Nov 18 23:00s 0 - Rule France 1940 only - Feb 25 2:00 1:00 S # The French rules for 1941-1944 were not used in Paris, but Shanks & Pottenger # write that they were used in Monaco and in many French locations. # Le Corre writes that the upper limit of the free zone was Arnéguy, Orthez, # Mont-de-Marsan, Bazas, Langon, Lamothe-Montravel, Marœuil, La # Rochefoucauld, Champagne-Mouton, La Roche-Posay, La Haye-Descartes, # Loches, Montrichard, Vierzon, Bourges, Moulins, Digoin, # Paray-le-Monial, Montceau-les-Mines, Chalon-sur-Saône, Arbois, # Dole, Morez, St-Claude, and Collonges (Haute-Savoie). Rule France 1941 only - May 5 0:00 2:00 M # Midsummer # Shanks & Pottenger say this transition occurred at Oct 6 1:00, # but go with Denis Excoffier (1997-12-12), # who quotes the Ephémérides astronomiques for 1998 from Bureau des Longitudes # as saying 5/10/41 22hUT. Rule France 1941 only - Oct 6 0:00 1:00 S Rule France 1942 only - Mar 9 0:00 2:00 M Rule France 1942 only - Nov 2 3:00 1:00 S Rule France 1943 only - Mar 29 2:00 2:00 M Rule France 1943 only - Oct 4 3:00 1:00 S Rule France 1944 only - Apr 3 2:00 2:00 M Rule France 1944 only - Oct 8 1:00 1:00 S Rule France 1945 only - Apr 2 2:00 2:00 M Rule France 1945 only - Sep 16 3:00 0 - # Shanks & Pottenger give Mar 28 2:00 and Sep 26 3:00; # go with Excoffier's 28/3/76 0hUT and 25/9/76 23hUT. Rule France 1976 only - Mar 28 1:00 1:00 S Rule France 1976 only - Sep 26 1:00 0 - # Howse writes that the time in France was officially based # on PMT-0:09:21 until 1978-08-09, when the time base finally switched to UTC. # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone Europe/Paris 0:09:21 - LMT 1891 Mar 16 0:09:21 - PMT 1911 Mar 11 # Paris Mean Time # Shanks & Pottenger give 1940 Jun 14 0:00; go with Excoffier and Le Corre. 0:00 France WE%sT 1940 Jun 14 23:00 # Le Corre says Paris stuck with occupied-France time after the liberation; # go with Shanks & Pottenger. 1:00 C-Eur CE%sT 1944 Aug 25 0:00 France WE%sT 1945 Sep 16 3:00 1:00 France CE%sT 1977 1:00 EU CE%sT # Denmark # Germany # Norway # Sweden # From Markus Kuhn (1998-09-29): # The German time zone web site by the Physikalisch-Technische # Bundesanstalt contains DST information back to 1916. # [See tz-link.html for the URL.] # From Jörg Schilling (2002-10-23): # In 1945, Berlin was switched to Moscow Summer time (GMT+4) by # https://www.dhm.de/lemo/html/biografien/BersarinNikolai/ # General [Nikolai] Bersarin. # From Paul Eggert (2003-03-08): # http://www.parlament-berlin.de/pds-fraktion.nsf/727459127c8b66ee8525662300459099/defc77cb784f180ac1256c2b0030274b/$FILE/bersarint.pdf # says that Bersarin issued an order to use Moscow time on May 20. # However, Moscow did not observe daylight saving in 1945, so # this was equivalent to UT +03, not +04. # Svalbard & Jan Mayen # From Steffen Thorsen (2001-05-01): # Although I could not find it explicitly, it seems that Jan Mayen and # Svalbard have been using the same time as Norway at least since the # time they were declared as parts of Norway. Svalbard was declared # as a part of Norway by law of 1925-07-17 no 11, section 4 and Jan # Mayen by law of 1930-02-27 no 2, section 2. (From # and # ). The law/regulation # for normal/standard time in Norway is from 1894-06-29 no 1 (came # into operation on 1895-01-01) and Svalbard/Jan Mayen seem to be a # part of this law since 1925/1930. (From # ) I have not been # able to find if Jan Mayen used a different time zone (e.g. -0100) # before 1930. Jan Mayen has only been "inhabited" since 1921 by # Norwegian meteorologists and maybe used the same time as Norway ever # since 1921. Svalbard (Arctic/Longyearbyen) has been inhabited since # before 1895, and therefore probably changed the local time somewhere # between 1895 and 1925 (inclusive). # From Paul Eggert (2013-09-04): # # Actually, Jan Mayen was never occupied by Germany during World War II, # so it must have diverged from Oslo time during the war, as Oslo was # keeping Berlin time. # # says that the meteorologists # burned down their station in 1940 and left the island, but returned in # 1941 with a small Norwegian garrison and continued operations despite # frequent air attacks from Germans. In 1943 the Americans established a # radiolocating station on the island, called "Atlantic City". Possibly # the UT offset changed during the war, but I think it unlikely that # Jan Mayen used German daylight-saving rules. # # Svalbard is more complicated, as it was raided in August 1941 by an # Allied party that evacuated the civilian population to England (says # ). The Svalbard FAQ # says that the Germans were # expelled on 1942-05-14. However, small parties of Germans did return, # and according to Wilhelm Dege's book "War North of 80" (1954) # http://www.ucalgary.ca/UofC/departments/UP/1-55238/1-55238-110-2.html # the German armed forces at the Svalbard weather station code-named # Haudegen did not surrender to the Allies until September 1945. # # All these events predate our cutoff date of 1970, so use Europe/Berlin # for these regions. # Rule NAME FROM TO - IN ON AT SAVE LETTER/S Rule Germany 1946 only - Apr 14 2:00s 1:00 S Rule Germany 1946 only - Oct 7 2:00s 0 - Rule Germany 1947 1949 - Oct Sun>=1 2:00s 0 - # https://www.ptb.de/cms/en/ptb/fachabteilungen/abt4/fb-44/ag-441/realisation-of-legal-time-in-germany/dst-and-midsummer-dst-in-germany-until-1979.html # says the following transition occurred at 3:00 MEZ, not the 2:00 MEZ # given in Shanks & Pottenger. Go with the PTB. Rule Germany 1947 only - Apr 6 3:00s 1:00 S Rule Germany 1947 only - May 11 2:00s 2:00 M Rule Germany 1947 only - Jun 29 3:00 1:00 S Rule Germany 1948 only - Apr 18 2:00s 1:00 S Rule Germany 1949 only - Apr 10 2:00s 1:00 S Rule SovietZone 1945 only - May 24 2:00 2:00 M # Midsummer Rule SovietZone 1945 only - Sep 24 3:00 1:00 S Rule SovietZone 1945 only - Nov 18 2:00s 0 - # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone Europe/Berlin 0:53:28 - LMT 1893 Apr 1:00 C-Eur CE%sT 1945 May 24 2:00 1:00 SovietZone CE%sT 1946 1:00 Germany CE%sT 1980 1:00 EU CE%sT # Georgia # Please see the "asia" file for Asia/Tbilisi. # Herodotus (Histories, IV.45) says Georgia north of the Phasis (now Rioni) # is in Europe. Our reference location Tbilisi is in the Asian part. # Gibraltar # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone Europe/Gibraltar -0:21:24 - LMT 1880 Aug 2 0:00 GB-Eire %s 1957 Apr 14 2:00 1:00 - CET 1982 1:00 EU CE%sT # Greece # Rule NAME FROM TO - IN ON AT SAVE LETTER/S # Whitman gives 1932 Jul 5 - Nov 1; go with Shanks & Pottenger. Rule Greece 1932 only - Jul 7 0:00 1:00 S Rule Greece 1932 only - Sep 1 0:00 0 - # Whitman gives 1941 Apr 25 - ?; go with Shanks & Pottenger. Rule Greece 1941 only - Apr 7 0:00 1:00 S # Whitman gives 1942 Feb 2 - ?; go with Shanks & Pottenger. Rule Greece 1942 only - Nov 2 3:00 0 - Rule Greece 1943 only - Mar 30 0:00 1:00 S Rule Greece 1943 only - Oct 4 0:00 0 - # Whitman gives 1944 Oct 3 - Oct 31; go with Shanks & Pottenger. Rule Greece 1952 only - Jul 1 0:00 1:00 S Rule Greece 1952 only - Nov 2 0:00 0 - Rule Greece 1975 only - Apr 12 0:00s 1:00 S Rule Greece 1975 only - Nov 26 0:00s 0 - Rule Greece 1976 only - Apr 11 2:00s 1:00 S Rule Greece 1976 only - Oct 10 2:00s 0 - Rule Greece 1977 1978 - Apr Sun>=1 2:00s 1:00 S Rule Greece 1977 only - Sep 26 2:00s 0 - Rule Greece 1978 only - Sep 24 4:00 0 - Rule Greece 1979 only - Apr 1 9:00 1:00 S Rule Greece 1979 only - Sep 29 2:00 0 - Rule Greece 1980 only - Apr 1 0:00 1:00 S Rule Greece 1980 only - Sep 28 0:00 0 - # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone Europe/Athens 1:34:52 - LMT 1895 Sep 14 1:34:52 - AMT 1916 Jul 28 0:01 # Athens MT 2:00 Greece EE%sT 1941 Apr 30 1:00 Greece CE%sT 1944 Apr 4 2:00 Greece EE%sT 1981 # Shanks & Pottenger say it switched to C-Eur in 1981; # go with EU rules instead, since Greece joined Jan 1. 2:00 EU EE%sT # Hungary # From Michael Deckers (2020-06-09): # an Austrian encyclopedia of railroads of 1913, online at # http://www.zeno.org/Roell-1912/A/Eisenbahnzeit # says that the switch [to CET] happened on 1890-11-01. # From Géza Nyáry (2020-06-07): # Data for 1918-1983 are based on the archive database of Library Hungaricana. # The dates are collected from original, scanned governmental orders, # bulletins, instructions and public press. # [See URLs below.] # Rule NAME FROM TO - IN ON AT SAVE LETTER/S # https://library.hungaricana.hu/hu/view/OGYK_RT_1918/?pg=238 # https://library.hungaricana.hu/hu/view/OGYK_RT_1919/?pg=808 # https://library.hungaricana.hu/hu/view/OGYK_RT_1920/?pg=201 Rule Hungary 1918 1919 - Apr 15 2:00 1:00 S Rule Hungary 1918 1920 - Sep Mon>=15 3:00 0 - Rule Hungary 1920 only - Apr 5 2:00 1:00 S # https://library.hungaricana.hu/hu/view/OGYK_RT_1945/?pg=882 Rule Hungary 1945 only - May 1 23:00 1:00 S Rule Hungary 1945 only - Nov 1 1:00 0 - # https://library.hungaricana.hu/hu/view/Delmagyarorszag_1946_03/?pg=49 Rule Hungary 1946 only - Mar 31 2:00s 1:00 S # https://library.hungaricana.hu/hu/view/Delmagyarorszag_1946_09/?pg=54 Rule Hungary 1946 only - Oct 7 2:00 0 - # https://library.hungaricana.hu/hu/view/KulfBelfHirek_1947_04_1__001-123/?pg=90 # https://library.hungaricana.hu/hu/view/DunantuliNaplo_1947_09/?pg=128 # https://library.hungaricana.hu/hu/view/KulfBelfHirek_1948_03_3__001-123/?pg=304 # https://library.hungaricana.hu/hu/view/Zala_1948_09/?pg=64 # https://library.hungaricana.hu/hu/view/SatoraljaujhelyiLeveltar_ZempleniNepujsag_1948/?pg=53 # https://library.hungaricana.hu/hu/view/SatoraljaujhelyiLeveltar_ZempleniNepujsag_1948/?pg=160 # https://library.hungaricana.hu/hu/view/UjSzo_1949_01-04/?pg=102 # https://library.hungaricana.hu/hu/view/KeletMagyarorszag_1949_03/?pg=96 # https://library.hungaricana.hu/hu/view/Delmagyarorszag_1949_09/?pg=94 Rule Hungary 1947 1949 - Apr Sun>=4 2:00s 1:00 S Rule Hungary 1947 1949 - Oct Sun>=1 2:00s 0 - # https://library.hungaricana.hu/hu/view/DTT_KOZL_TanacsokKozlonye_1954/?pg=513 Rule Hungary 1954 only - May 23 0:00 1:00 S Rule Hungary 1954 only - Oct 3 0:00 0 - # https://library.hungaricana.hu/hu/view/DTT_KOZL_TanacsokKozlonye_1955/?pg=398 Rule Hungary 1955 only - May 22 2:00 1:00 S Rule Hungary 1955 only - Oct 2 3:00 0 - # https://library.hungaricana.hu/hu/view/HevesMegyeiNepujsag_1956_06/?pg=0 # https://library.hungaricana.hu/hu/view/EszakMagyarorszag_1956_06/?pg=6 # https://library.hungaricana.hu/hu/view/SzolnokMegyeiNeplap_1957_04/?pg=120 # https://library.hungaricana.hu/hu/view/PestMegyeiHirlap_1957_09/?pg=143 Rule Hungary 1956 1957 - Jun Sun>=1 2:00 1:00 S Rule Hungary 1956 1957 - Sep lastSun 3:00 0 - # https://library.hungaricana.hu/hu/view/DTT_KOZL_TanacsokKozlonye_1980/?pg=189 Rule Hungary 1980 only - Apr 6 0:00 1:00 S Rule Hungary 1980 only - Sep 28 1:00 0 - # https://library.hungaricana.hu/hu/view/DTT_KOZL_TanacsokKozlonye_1980/?pg=1227 # https://library.hungaricana.hu/hu/view/Delmagyarorszag_1981_01/?pg=79 # https://library.hungaricana.hu/hu/view/DTT_KOZL_TanacsokKozlonye_1982/?pg=115 # https://library.hungaricana.hu/hu/view/DTT_KOZL_TanacsokKozlonye_1983/?pg=85 Rule Hungary 1981 1983 - Mar lastSun 0:00 1:00 S Rule Hungary 1981 1983 - Sep lastSun 1:00 0 - # # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone Europe/Budapest 1:16:20 - LMT 1890 Nov 1 1:00 C-Eur CE%sT 1918 # https://library.hungaricana.hu/hu/view/OGYK_RT_1941/?pg=1204 # https://library.hungaricana.hu/hu/view/OGYK_RT_1942/?pg=3955 1:00 Hungary CE%sT 1941 Apr 7 23:00 1:00 C-Eur CE%sT 1945 1:00 Hungary CE%sT 1984 1:00 EU CE%sT # Italy # San Marino # Vatican City # # From Paul Eggert (2001-03-06): # Sicily and Sardinia each had their own time zones from 1866 to 1893, # called Palermo Time (+00:53:28) and Cagliari Time (+00:36:32). # During World War II, German-controlled Italy used German time. # But these events all occurred before the 1970 cutoff, # so record only the time in Rome. # # From Stephen Trainor (2019-05-06): # http://www.ac-ilsestante.it/MERIDIANE/ora_legale/ORA_LEGALE_ESTIVA_IN_ITALIA.htm # ... the [1866] law went into effect on 12 December 1866, rather than # the date of the decree (22 Sep 1866) # https://web.archive.org/web/20070824155341/http://www.iav.it/planetario/didastro/didastro/english.htm # ... "In Italy in 1866 there were 6 railway times (Torino, Verona, Firenze, # Roma, Napoli, Palermo). On that year it was decided to unify them, adopting # the average time of Rome (even if this city was not yet part of the # kingdom). On the 12th December 1866, on the starting of the winter time # table, it took effect in the railways, the post office and the telegraph, # not only for the internal service but also for the public.... Milano set # the public watches on the Rome time on the same day (12th December 1866), # Torino and Bologna on the 1st January 1867, Venezia the 1st May 1880 and the # last city was Cagliari in 1886." # # From Luigi Rosa (2019-05-07): # this is the scan of the decree: # http://www.radiomarconi.com/marconi/filopanti/1866c.jpg # # From Michael Deckers (2016-10-24): # http://www.ac-ilsestante.it/MERIDIANE/ora_legale quotes a law of 1893-08-10 # ... [translated as] "The preceding dispositions will enter into # force at the instant at which, according to the time specified in # the 1st article, the 1st of November 1893 will begin...." # # From Pierpaolo Bernardi (2016-10-20): # The authoritative source for time in Italy is the national metrological # institute, which has a summary page of historical DST data at # http://www.inrim.it/res/tf/ora_legale_i.shtml # [now at http://oldsite.inrim.it/res/tf/ora_legale_i.shtml as of 2017] # (2016-10-24): # http://www.renzobaldini.it/le-ore-legali-in-italia/ # has still different data for 1944. It divides Italy in two, as # there were effectively two governments at the time, north of Gothic # Line German controlled territory, official government RSI, and south # of the Gothic Line, controlled by allied armies. # # From Brian Inglis (2016-10-23): # Viceregal LEGISLATIVE DECREE. 14 September 1944, no. 219. # Restoration of Standard Time. (044U0219) (OJ 62 of 30.9.1944) ... # Given the R. law decreed on 1944-03-29, no. 92, by which standard time is # advanced to sixty minutes later starting at hour two on 1944-04-02; ... # Starting at hour three on the date 1944-09-17 standard time will be resumed. # # From Alois Treindl (2019-07-02): # I spent 6 Euros to buy two archive copies of Il Messaggero, a Roman paper, # for 1 and 2 April 1944. The edition of 2 April has this note: "Tonight at 2 # am, put forward the clock by one hour. Remember that in the night between # today and Monday the 'ora legale' will come in force again." That makes it # clear that in Rome the change was on Monday, 3 April 1944 at 2 am. # # From Paul Eggert (2021-10-05): # Go with INRiM for DST rules, except as corrected by Inglis for 1944 # for the Kingdom of Italy. This is consistent with Renzo Baldini. # Model Rome's occupation by using C-Eur rules from 1943-09-10 # to 1944-06-04; although Rome was an open city during this period, it # was effectively controlled by Germany. Using C-Eur is consistent # with Treindl's comment about Rome in April 1944, as the "Rule Italy" # lines during German occupation do not affect Europe/Rome # (though they do affect Europe/Malta). # # Rule NAME FROM TO - IN ON AT SAVE LETTER/S Rule Italy 1916 only - Jun 3 24:00 1:00 S Rule Italy 1916 1917 - Sep 30 24:00 0 - Rule Italy 1917 only - Mar 31 24:00 1:00 S Rule Italy 1918 only - Mar 9 24:00 1:00 S Rule Italy 1918 only - Oct 6 24:00 0 - Rule Italy 1919 only - Mar 1 24:00 1:00 S Rule Italy 1919 only - Oct 4 24:00 0 - Rule Italy 1920 only - Mar 20 24:00 1:00 S Rule Italy 1920 only - Sep 18 24:00 0 - Rule Italy 1940 only - Jun 14 24:00 1:00 S Rule Italy 1942 only - Nov 2 2:00s 0 - Rule Italy 1943 only - Mar 29 2:00s 1:00 S Rule Italy 1943 only - Oct 4 2:00s 0 - Rule Italy 1944 only - Apr 2 2:00s 1:00 S Rule Italy 1944 only - Sep 17 2:00s 0 - Rule Italy 1945 only - Apr 2 2:00 1:00 S Rule Italy 1945 only - Sep 15 1:00 0 - Rule Italy 1946 only - Mar 17 2:00s 1:00 S Rule Italy 1946 only - Oct 6 2:00s 0 - Rule Italy 1947 only - Mar 16 0:00s 1:00 S Rule Italy 1947 only - Oct 5 0:00s 0 - Rule Italy 1948 only - Feb 29 2:00s 1:00 S Rule Italy 1948 only - Oct 3 2:00s 0 - Rule Italy 1966 1968 - May Sun>=22 0:00s 1:00 S Rule Italy 1966 only - Sep 24 24:00 0 - Rule Italy 1967 1969 - Sep Sun>=22 0:00s 0 - Rule Italy 1969 only - Jun 1 0:00s 1:00 S Rule Italy 1970 only - May 31 0:00s 1:00 S Rule Italy 1970 only - Sep lastSun 0:00s 0 - Rule Italy 1971 1972 - May Sun>=22 0:00s 1:00 S Rule Italy 1971 only - Sep lastSun 0:00s 0 - Rule Italy 1972 only - Oct 1 0:00s 0 - Rule Italy 1973 only - Jun 3 0:00s 1:00 S Rule Italy 1973 1974 - Sep lastSun 0:00s 0 - Rule Italy 1974 only - May 26 0:00s 1:00 S Rule Italy 1975 only - Jun 1 0:00s 1:00 S Rule Italy 1975 1977 - Sep lastSun 0:00s 0 - Rule Italy 1976 only - May 30 0:00s 1:00 S Rule Italy 1977 1979 - May Sun>=22 0:00s 1:00 S Rule Italy 1978 only - Oct 1 0:00s 0 - Rule Italy 1979 only - Sep 30 0:00s 0 - # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone Europe/Rome 0:49:56 - LMT 1866 Dec 12 0:49:56 - RMT 1893 Oct 31 23:00u # Rome Mean 1:00 Italy CE%sT 1943 Sep 10 1:00 C-Eur CE%sT 1944 Jun 4 1:00 Italy CE%sT 1980 1:00 EU CE%sT # Latvia # From Liene Kanepe (1998-09-17): # I asked about this matter Scientific Secretary of the Institute of Astronomy # of The University of Latvia Dr. paed Mr. Ilgonis Vilks. I also searched the # correct data in juridical acts and I found some juridical documents about # changes in the counting of time in Latvia from 1981.... # # Act No. 35 of the Council of Ministers of Latvian SSR of 1981-01-22 ... # according to the Act No. 925 of the Council of Ministers of USSR of 1980-10-24 # ...: all year round the time of 2nd time zone + 1 hour, in addition turning # the hands of the clock 1 hour forward on 1 April at 00:00 (GMT 31 March 21:00) # and 1 hour backward on the 1 October at 00:00 (GMT 30 September 20:00). # # Act No. 592 of the Council of Ministers of Latvian SSR of 1984-09-24 ... # according to the Act No. 967 of the Council of Ministers of USSR of 1984-09-13 # ...: all year round the time of 2nd time zone + 1 hour, in addition turning # the hands of the clock 1 hour forward on the last Sunday of March at 02:00 # (GMT 23:00 on the previous day) and 1 hour backward on the last Sunday of # September at 03:00 (GMT 23:00 on the previous day). # # Act No. 81 of the Council of Ministers of Latvian SSR of 1989-03-22 ... # according to the Act No. 227 of the Council of Ministers of USSR of 1989-03-14 # ...: since the last Sunday of March 1989 in Lithuanian SSR, Latvian SSR, # Estonian SSR and Kaliningrad region of Russian Federation all year round the # time of 2nd time zone (Moscow time minus one hour). On the territory of Latvia # transition to summer time is performed on the last Sunday of March at 02:00 # (GMT 00:00), turning the hands of the clock 1 hour forward. The end of # daylight saving time is performed on the last Sunday of September at 03:00 # (GMT 00:00), turning the hands of the clock 1 hour backward. Exception is # 1989-03-26, when we must not turn the hands of the clock.... # # The Regulations of the Cabinet of Ministers of the Republic of Latvia of # 1997-01-21 on transition to Summer time ... established the same order of # daylight savings time settings as in the States of the European Union. # From Andrei Ivanov (2000-03-06): # This year Latvia will not switch to Daylight Savings Time (as specified in # The Regulations of the Cabinet of Ministers of the Rep. of Latvia of # 29-Feb-2000 (No. 79) , # in Latvian for subscribers only). # From RFE/RL Newsline # http://www.rferl.org/newsline/2001/01/3-CEE/cee-030101.html # (2001-01-03), noted after a heads-up by Rives McDow: # The Latvian government on 2 January decided that the country will # institute daylight-saving time this spring, LETA reported. # Last February the three Baltic states decided not to turn back their # clocks one hour in the spring.... # Minister of Economy Aigars Kalvītis noted that Latvia had too few # daylight hours and thus decided to comply with a draft European # Commission directive that provides for instituting daylight-saving # time in EU countries between 2002 and 2006. The Latvian government # urged Lithuania and Estonia to adopt a similar time policy, but it # appears that they will not do so.... # Rule NAME FROM TO - IN ON AT SAVE LETTER/S Rule Latvia 1989 1996 - Mar lastSun 2:00s 1:00 S Rule Latvia 1989 1996 - Sep lastSun 2:00s 0 - # Milne 1899 says Riga was 1:36:28 (Polytechnique House time). # Byalokoz 1919 says Latvia was 1:36:34. # Go with Byalokoz. # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone Europe/Riga 1:36:34 - LMT 1880 1:36:34 - RMT 1918 Apr 15 2:00 # Riga MT 1:36:34 1:00 LST 1918 Sep 16 3:00 # Latvian ST 1:36:34 - RMT 1919 Apr 1 2:00 1:36:34 1:00 LST 1919 May 22 3:00 1:36:34 - RMT 1926 May 11 2:00 - EET 1940 Aug 5 3:00 - MSK 1941 Jul 1:00 C-Eur CE%sT 1944 Oct 13 3:00 Russia MSK/MSD 1989 Mar lastSun 2:00s 2:00 1:00 EEST 1989 Sep lastSun 2:00s 2:00 Latvia EE%sT 1997 Jan 21 2:00 EU EE%sT 2000 Feb 29 2:00 - EET 2001 Jan 2 2:00 EU EE%sT # Lithuania # From Paul Eggert (2016-03-18): # The 1989 transition is from USSR act No. 227 (1989-03-14). # From Paul Eggert (1996-11-22): # IATA SSIM (1992/1996) says Lithuania uses W-Eur rules, but since it is # known to be wrong about Estonia and Latvia, assume it's wrong here too. # From Marius Gedminas (1998-08-07): # I would like to inform that in this year Lithuanian time zone # (Europe/Vilnius) was changed. # From ELTA No. 972 (2582) (1999-09-29) , # via Steffen Thorsen: # Lithuania has shifted back to the second time zone (GMT plus two hours) # to be valid here starting from October 31, # as decided by the national government on Wednesday.... # The Lithuanian government also announced plans to consider a # motion to give up shifting to summer time in spring, as it was # already done by Estonia. # From the Fact File, Lithuanian State Department of Tourism # (2000-03-27): # Local time is GMT+2 hours ..., no daylight saving. # From a user via Klaus Marten (2003-02-07): # As a candidate for membership of the European Union, Lithuania will # observe Summer Time in 2003, changing its clocks at the times laid # down in EU Directive 2000/84 of 19.I.01 (i.e. at the same times as its # neighbour Latvia). The text of the Lithuanian government Order of # 7.XI.02 to this effect can be found at # http://www.lrvk.lt/nut/11/n1749.htm # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone Europe/Vilnius 1:41:16 - LMT 1880 1:24:00 - WMT 1917 # Warsaw Mean Time 1:35:36 - KMT 1919 Oct 10 # Kaunas Mean Time 1:00 - CET 1920 Jul 12 2:00 - EET 1920 Oct 9 1:00 - CET 1940 Aug 3 3:00 - MSK 1941 Jun 24 1:00 C-Eur CE%sT 1944 Aug 3:00 Russia MSK/MSD 1989 Mar 26 2:00s 2:00 Russia EE%sT 1991 Sep 29 2:00s 2:00 C-Eur EE%sT 1998 2:00 - EET 1998 Mar 29 1:00u 1:00 EU CE%sT 1999 Oct 31 1:00u 2:00 - EET 2003 Jan 1 2:00 EU EE%sT # Malta # # From Paul Eggert (2016-10-21): # Assume 1900-1972 was like Rome, overriding Shanks. # # Rule NAME FROM TO - IN ON AT SAVE LETTER/S Rule Malta 1973 only - Mar 31 0:00s 1:00 S Rule Malta 1973 only - Sep 29 0:00s 0 - Rule Malta 1974 only - Apr 21 0:00s 1:00 S Rule Malta 1974 only - Sep 16 0:00s 0 - Rule Malta 1975 1979 - Apr Sun>=15 2:00 1:00 S Rule Malta 1975 1980 - Sep Sun>=15 2:00 0 - Rule Malta 1980 only - Mar 31 2:00 1:00 S # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone Europe/Malta 0:58:04 - LMT 1893 Nov 2 # Valletta 1:00 Italy CE%sT 1973 Mar 31 1:00 Malta CE%sT 1981 1:00 EU CE%sT # Moldova # From Stepan Golosunov (2016-03-07): # the act of the government of the Republic of Moldova Nr. 132 from 1990-05-04 # ... says that since 1990-05-06 on the territory of the Moldavian SSR # time would be calculated as the standard time of the second time belt # plus one hour of the "summer" time. To implement that clocks would be # adjusted one hour backwards at 1990-05-06 2:00. After that "summer" # time would be cancelled last Sunday of September at 3:00 and # reintroduced last Sunday of March at 2:00. # From Paul Eggert (2006-03-22): # A previous version of this database followed Shanks & Pottenger, who write # that Tiraspol switched to Moscow time on 1992-01-19 at 02:00. # However, this is most likely an error, as Moldova declared independence # on 1991-08-27 (the 1992-01-19 date is that of a Russian decree). # In early 1992 there was large-scale interethnic violence in the area # and it's possible that some Russophones continued to observe Moscow time. # But [two people] separately reported via # Jesper Nørgaard that as of 2001-01-24 Tiraspol was like Chisinau. # The Tiraspol entry has therefore been removed for now. # # From Alexander Krivenyshev (2011-10-17): # Pridnestrovian Moldavian Republic (PMR, also known as # "Pridnestrovie") has abolished seasonal clock change (no transition # to the Winter Time). # # News (in Russian): # http://www.kyivpost.ua/russia/news/pridnestrove-otkazalos-ot-perehoda-na-zimnee-vremya-30954.html # http://www.allmoldova.com/moldova-news/1249064116.html # # The substance of this change (reinstatement of the Tiraspol entry) # is from a patch from Petr Machata (2011-10-17) # # From Tim Parenti (2011-10-19) # In addition, being situated at +4651+2938 would give Tiraspol # a pre-1880 LMT offset of 1:58:32. # # (which agrees with the earlier entry that had been removed) # # From Alexander Krivenyshev (2011-10-26) # NO need to divide Moldova into two timezones at this point. # As of today, Transnistria (Pridnestrovie)- Tiraspol reversed its own # decision to abolish DST this winter. # Following Moldova and neighboring Ukraine- Transnistria (Pridnestrovie)- # Tiraspol will go back to winter time on October 30, 2011. # News from Moldova (in russian): # https://ru.publika.md/link_317061.html # From Roman Tudos (2015-07-02): # http://lex.justice.md/index.php?action=view&view=doc&lang=1&id=355077 # From Paul Eggert (2015-07-01): # The above-mentioned official link to IGO1445-868/2014 states that # 2014-10-26's fallback transition occurred at 03:00 local time. Also, # https://www.trm.md/en/social/la-30-martie-vom-trece-la-ora-de-vara # says the 2014-03-30 spring-forward transition was at 02:00 local time. # Guess that since 1997 Moldova has switched one hour before the EU. # From Heitor David Pinto (2026-02-22): # Soviet Moldovan resolution 132 of 1990 defined the summer time period from # the last Sunday in March at 2:00 to the last Sunday in September at 3:00, # matching the dates used in most of Europe at the time: # https://web.archive.org/web/20211107050832/http://lex.justice.md/viewdoc.php?action=view&view=doc&id=298782&lang=1 # # It seems that in 1996 Moldova changed the end date to October like most of # Europe, but kept the transitions at 2:00 and 3:00 rather than 1:00 UTC, # which would have been locally 3:00 and 4:00.... # # The notices in the Moldovan government website and broadcaster showed the # transitions at 2:00 and 3:00 until 2021: # 2015 https://old.gov.md/en/node/7304 # 2016 https://old.gov.md/en/node/12587 # 2017 https://old.gov.md/en/node/20654 # 2017 https://old.gov.md/en/content/moldova-upholds-winter-time-night-28-29-october # 2018 https://old.gov.md/en/content/moldova-switch-summer-time # 2018 https://old.gov.md/en/content/cabinet-ministers-informs-about-switch-winter-time-28-october # 2019 https://old.gov.md/en/content/moldova-switch-summer-time-31-march # 2019 https://old.gov.md/en/node/31122 # 2020 https://old.gov.md/en/node/32771 # 2020 https://old.gov.md/en/node/34497 # 2021 https://trm.md/ro/social/moldova-trece-in-aceasta-noapte-la-ora-de-vara # 2021 https://trm.md/en/social/republica-moldova-trece-la-ora-de-iarna1 # # However, since 2022, the notices showed the transitions at 3:00 and 4:00, # matching the EU rule at 1:00 UTC: # 2022 https://trm.md/en/social/in-acest-weekend-republica-moldova-trece-la-ora-de-vara # 2022 https://old.gov.md/en/content/moldova-switch-winter-time # 2023 https://moldova1.md/p/6587/ora-de-vara-2023-cum-schimbam-acele-ceasornicelor-si-cand-trecem-la-ora-de-vara # 2023 https://old.gov.md/en/node/46662 # 2024 https://moldova1.md/p/26535/republica-moldova-trece-la-ora-de-vara-in-acest-weekend # 2024 https://moldova1.md/p/37768/republica-moldova-trece-in-aceasta-noapte-la-ora-de-iarna # 2025 https://moldova1.md/p/46349/republica-moldova-trece-la-ora-de-vara-pe-30-martie-cum-ne-afecteaza-si-ce-recomanda-medicii # 2025 https://moldova1.md/p/60469/republica-moldova-trece-la-ora-de-iarna-ceasurile-se-dau-inapoi-cu-o-ora # # It seems that the changes to the end date and transition times were just # done in practice without formally changing the resolution. In late 2025, the # government said that the Soviet resolution was still in force, and proposed # a new resolution to replace it and formally establish the EU rule: # ... based on the notices, it seems that in practice Moldova already # uses the EU rule since 2022. This was also the year when Moldova applied to # join the EU. # # From Robert Bastian (2026-02-26): # This has been approved and published in the government gazette: # https://monitorul.gov.md/ro/monitorul/view/pdf/3234/part/2#page=27 # # From Paul Eggert (2026-02-24): # Also see Svetlana Rudenko, "Moldova abandons the 'Soviet era'", Logos Press, # 2026-02-21 . # Rule NAME FROM TO - IN ON AT SAVE LETTER/S Rule Moldova 1997 2021 - Mar lastSun 2:00 1:00 S Rule Moldova 1997 2021 - Oct lastSun 3:00 0 - # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone Europe/Chisinau 1:55:20 - LMT 1880 1:55 - CMT 1918 Feb 15 # Chisinau MT 1:44:24 - BMT 1931 Jul 24 # Bucharest MT 2:00 Romania EE%sT 1940 Aug 15 2:00 1:00 EEST 1941 Jul 17 1:00 C-Eur CE%sT 1944 Aug 24 3:00 Russia MSK/MSD 1990 May 6 2:00 2:00 Russia EE%sT 1992 2:00 E-Eur EE%sT 1997 # See Romania commentary for the guessed 1997 transition to EU rules. 2:00 Moldova EE%sT 2022 2:00 EU EE%sT # Poland # The 1919 dates and times can be found in Tygodnik Urzędowy nr 1 (1919-03-20), # pp 1-2. # Rule NAME FROM TO - IN ON AT SAVE LETTER/S Rule Poland 1918 1919 - Sep 16 2:00s 0 - Rule Poland 1919 only - Apr 15 2:00s 1:00 S Rule Poland 1944 only - Apr 3 2:00s 1:00 S # Whitman gives 1944 Nov 30; go with Shanks & Pottenger. Rule Poland 1944 only - Oct 4 2:00 0 - # For 1944-1948 Whitman gives the previous day; go with Shanks & Pottenger. Rule Poland 1945 only - Apr 29 0:00 1:00 S Rule Poland 1945 only - Nov 1 0:00 0 - # For 1946 on the source is Kazimierz Borkowski, # Toruń Center for Astronomy, Dept. of Radio Astronomy, Nicolaus Copernicus U., # https://www.astro.uni.torun.pl/~kb/Artykuly/U-PA/Czas2.htm#tth_tAb1 # Thanks to Przemysław Augustyniak (2005-05-28) for this reference. # He also gives these further references: # Mon Pol nr 13, poz 162 (1995) # Druk nr 2180 (2003) Rule Poland 1946 only - Apr 14 0:00s 1:00 S Rule Poland 1946 only - Oct 7 2:00s 0 - Rule Poland 1947 only - May 4 2:00s 1:00 S Rule Poland 1947 1949 - Oct Sun>=1 2:00s 0 - Rule Poland 1948 only - Apr 18 2:00s 1:00 S Rule Poland 1949 only - Apr 10 2:00s 1:00 S Rule Poland 1957 only - Jun 2 1:00s 1:00 S Rule Poland 1957 1958 - Sep lastSun 1:00s 0 - Rule Poland 1958 only - Mar 30 1:00s 1:00 S Rule Poland 1959 only - May 31 1:00s 1:00 S Rule Poland 1959 1961 - Oct Sun>=1 1:00s 0 - Rule Poland 1960 only - Apr 3 1:00s 1:00 S Rule Poland 1961 1964 - May lastSun 1:00s 1:00 S Rule Poland 1962 1964 - Sep lastSun 1:00s 0 - # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone Europe/Warsaw 1:24:00 - LMT 1880 1:24:00 - WMT 1915 Aug 5 # Warsaw Mean Time 1:00 C-Eur CE%sT 1918 Sep 16 3:00 2:00 Poland EE%sT 1922 Jun 1:00 Poland CE%sT 1940 Jun 23 2:00 1:00 C-Eur CE%sT 1944 Oct 1:00 Poland CE%sT 1977 1:00 W-Eur CE%sT 1988 1:00 EU CE%sT # Portugal # From Tim Parenti (2024-07-01), per Alois Treindl (2021-02-07) and Michael # Deckers (2021-02-10): # http://oal.ul.pt/documentos/2018/01/hl1911a2018.pdf/ # The Astronomical Observatory of Lisbon has published a list detailing the # historical transitions in legal time within continental Portugal. It # directly references many decrees and ordinances which are, in turn, # referenced below. They can be viewed in the public archives of the Diário da # República (until 1976-04-09 known as the Diário do Govêrno) at # https://dre.pt/ (in Portuguese). # # Most of the Rules below have been updated simply to match the Observatory's # listing for continental (mainland) Portugal. Although there are over 50 # referenced decrees and ordinances, only the handful with comments below have # been verified against the text, typically to provide additional confidence # wherever dates provided by Whitman and Shanks & Pottenger had disagreed. # See further below for the Azores and Madeira. # From Tim Parenti (2024-07-01), per Paul Eggert (2014-08-11), after a # heads-up from Stephen Colebourne: # According to a 1911-05-24 Portuguese decree, Lisbon was at -0:36:44.68, but # switched to GMT on 1912-01-01 at 00:00. # https://dre.pt/dr/detalhe/decreto/593090 # https://dre.pt/application/conteudo/593090 # The decree made legal time throughout Portugal and her possessions # "subordinate to the Greenwich meridian, according to the principle adopted at # the Washington Convention in 1884" and eliminated the "difference of five # minutes between the internal and external clocks of railway stations". # # The decree was gazetted in the 1911-05-30 issue of Diário do Govêrno, and is # considered to be dated 1911-05-24 by that issue's summary; however, the text # of the decree itself is dated 1911-05-26. The Diário da República website # notes the discrepancy, but later laws and the Observatory all seem to refer # to this decree by the 1911-05-24 date. # # From Michael Deckers (2018-02-15): # article 5 [of the 1911 decree; Deckers's translation] ...: # These dispositions shall enter into force at the instant at which, # according to the 2nd article, the civil day January 1, 1912 begins, # all clocks therefore having to be advanced or set back correspondingly ... # Rule NAME FROM TO - IN ON AT SAVE LETTER/S # From Tim Parenti (2024-07-01): # Decreto 1469 of 1915-03-30 ... had nothing to do with DST; # rather it regulated the sending of time signals. # But we do see linkage to Spain in the 1920s below. # https://dre.pt/dr/detalhe/decreto/1469-1915-285721 # https://dre.pt/application/conteudo/285721 # # According to the Observatory, standard time was first advanced by Decreto # 2433 of 1916-06-09 and restored by Decreto 2712 of 1916-10-28. While Whitman # gives 1916-10-31 for the latter transition, Shanks & Pottenger agrees more # closely with the decree, which stated that its provision "will start sixty # minutes after the end of 31 October, according to the current time," i.e., # 01:00 on 1 November. # https://dre.pt/dr/detalhe/decreto/2433-1916-267192 # https://dre.pt/application/conteudo/267192 # https://dre.pt/dr/detalhe/decreto/2712-1916-590937 # https://dre.pt/application/conteudo/590937 Rule Port 1916 only - Jun 17 23:00 1:00 S Rule Port 1916 only - Nov 1 1:00 0 - # From Tim Parenti (2024-07-01): # Article 7 of Decreto 2922 of 1916-12-30 stated that "the legal time will be # advanced by sixty minutes from 1 March to 31 October." Per Article 15, this # came into force from 1917-01-01. Just before the first fall back, Decreto # 3446 of 1917-10-11 changed the annual end date to 14 October. # https://dre.pt/dr/detalhe/decreto/2922-1916-261894 # https://dre.pt/application/conteudo/261894 # https://dre.pt/dr/detalhe/decreto/3446-1917-495161 # https://dre.pt/application/conteudo/495161 # This annual change was revoked by Decreto 8038 of 1922-02-18. # https://dre.pt/dr/detalhe/decreto/8038-1922-569751 # https://dre.pt/application/conteudo/569751 Rule Port 1917 1921 - Mar 1 0:00 1:00 S Rule Port 1917 1921 - Oct 14 24:00 0 - # From Tim Parenti (2024-07-01): # Decreto 9592 of 1924-04-14 noted that "France maintains the advance of legal # time in the summer and Spain has now adopted it for the first time" and # considered "that the absence of similar measures would cause serious # difficulties for international rail connections with consequent repercussions # on domestic service hours..." along with "inconvenient analogues...for postal # and telegraph services." Summer time would be in effect from 17 April to 4 # October, with the spring change explicitly specified by bringing clocks # forward from 16 April 23:00. # https://dre.pt/dr/detalhe/decreto/9592-1924-652133 # https://dre.pt/application/conteudo/652133 # # Decreto 10700, issued 1925-04-16, noted that Spain had not continued summer # time, declared that "the current legal hour prior to 17 April remains # unchanged from that day forward", and revoked legislation to the contrary, # just a day before summer time would have otherwise resumed. # https://dre.pt/dr/detalhe/decreto/10700-1925-437826 # https://dre.pt/application/conteudo/437826 Rule Port 1924 only - Apr 16 23:00s 1:00 S Rule Port 1924 only - Oct 4 23:00s 0 - Rule Port 1926 only - Apr 17 23:00s 1:00 S Rule Port 1926 1929 - Oct Sat>=1 23:00s 0 - Rule Port 1927 only - Apr 9 23:00s 1:00 S Rule Port 1928 only - Apr 14 23:00s 1:00 S Rule Port 1929 only - Apr 20 23:00s 1:00 S Rule Port 1931 only - Apr 18 23:00s 1:00 S # Whitman gives 1931 Oct 8; go with Shanks & Pottenger. Rule Port 1931 1932 - Oct Sat>=1 23:00s 0 - Rule Port 1932 only - Apr 2 23:00s 1:00 S Rule Port 1934 only - Apr 7 23:00s 1:00 S # Whitman gives 1934 Oct 5; go with Shanks & Pottenger. # Note: The 1935 law specified 10-06 00:00, not 10-05 24:00, but the following # is equivalent and more succinct. Rule Port 1934 1938 - Oct Sat>=1 23:00s 0 - # Shanks & Pottenger give 1935 Apr 30; go with Whitman. Rule Port 1935 only - Mar 30 23:00s 1:00 S Rule Port 1936 only - Apr 18 23:00s 1:00 S # Whitman gives 1937 Apr 2; go with Shanks & Pottenger. Rule Port 1937 only - Apr 3 23:00s 1:00 S Rule Port 1938 only - Mar 26 23:00s 1:00 S Rule Port 1939 only - Apr 15 23:00s 1:00 S # Whitman gives 1939 Oct 7; go with Shanks & Pottenger. Rule Port 1939 only - Nov 18 23:00s 0 - # From Tim Parenti (2024-07-01): # Portaria 9465 of 1940-02-17 advanced clocks from Saturday 1940-02-24 23:00. # The clocks were restored by Portaria 9658, issued Monday 1940-10-07, # effective from 24:00 that very night, which agrees with Shanks & Pottenger; # Whitman gives Saturday 1940-10-05 instead. # https://dre.pt/dr/detalhe/portaria/9465-1940-189096 # https://dre.pt/application/conteudo/189096 # https://dre.pt/dr/detalhe/portaria/9658-1940-196729 # https://dre.pt/application/conteudo/196729 Rule Port 1940 only - Feb 24 23:00s 1:00 S Rule Port 1940 only - Oct 7 23:00s 0 - Rule Port 1941 only - Apr 5 23:00s 1:00 S Rule Port 1941 only - Oct 5 23:00s 0 - Rule Port 1942 1945 - Mar Sat>=8 23:00s 1:00 S Rule Port 1942 only - Apr 25 22:00s 2:00 M # Midsummer Rule Port 1942 only - Aug 15 22:00s 1:00 S Rule Port 1942 1945 - Oct Sat>=24 23:00s 0 - Rule Port 1943 only - Apr 17 22:00s 2:00 M Rule Port 1943 1945 - Aug Sat>=25 22:00s 1:00 S Rule Port 1944 1945 - Apr Sat>=21 22:00s 2:00 M Rule Port 1946 only - Apr Sat>=1 23:00s 1:00 S Rule Port 1946 only - Oct Sat>=1 23:00s 0 - # From Tim Parenti (2024-07-01), per Alois Treindl (2021-02-07): # The Astronomical Observatory of Lisbon cites Portaria 11767 of 1947-03-28 for # 1947 and Portaria 12286 of 1948-02-19 for 1948. # https://dre.pt/dr/detalhe/portaria/11767-1947-414787 # https://dre.pt/application/conteudo/414787 # https://dre.pt/dr/detalhe/portaria/12286-1948-152953 # https://dre.pt/application/conteudo/152953 # # Although the latter ordinance explicitly had the 1948-10-03 transition # scheduled for 02:00 rather than 03:00 as had been used in 1947, Decreto-Lei # 37048 of 1948-09-07 recognized "that it is advisable to definitely set...the # 'summer time' regime", and fixed the fall transition at 03:00 moving forward. # https://dre.pt/dr/detalhe/decreto-lei/37048-1948-373810 # https://dre.pt/application/conteudo/373810 # While the Observatory only cites this act for 1949-1965 and not for 1948, it # does not appear to have had any provision delaying its effect, so assume that # it overrode the prior ordinance for 1948-10-03. # # Whitman says DST was not observed in 1950 and gives Oct lastSun for 1952 on. # The Observatory, however, agrees with Shanks & Pottenger that 1950 was not an # exception and that Oct Sun>=1 was maintained through 1965. Rule Port 1947 1966 - Apr Sun>=1 2:00s 1:00 S Rule Port 1947 1965 - Oct Sun>=1 2:00s 0 - # From Tim Parenti (2024-07-01): # Decreto-Lei 47233 of 1966-10-01 considered that the "duality" in time was # "the cause of serious disturbances" and noted that "the countries with which # we have the most frequent contacts...have already adopted" a solution # coinciding with the extant "summer time". It established that the former # "summer time" would apply year-round on the mainland and adjacent islands # with immediate effect, as the fall back would have otherwise occurred later # that evening. # https://dre.pt/dr/detalhe/decreto-lei/47233-1966-293729 # Model this by changing zones without changing clocks at the # previously-appointed fall back time. # # Decreto-Lei 309/76 of 1976-04-27 acknowledged that those international # contacts had returned to adopting seasonal times, and considered that the # year-round advancement "entails considerable sacrifices for the vast majority # of the working population during the winter months", including morning # visibility concerns for schoolchildren. It specified, beginning 1976-09-26 # 01:00, an annual return to UT+00 on the mainland from 00:00 UT on Sep lastSun # to 00:00 UT on Mar lastSun (unless the latter date fell on Easter, in which # case it was to be brought forward to the preceding Sunday). It also assigned # the Permanent Time Commission to study and propose revisions for the Azores # and Madeira, neither of which resumed DST until 1982 (as described further # below). # https://dre.pt/dr/detalhe/decreto-lei/309-1976-502063 Rule Port 1976 only - Sep lastSun 1:00 0 - Rule Port 1977 only - Mar lastSun 0:00s 1:00 S Rule Port 1977 only - Sep lastSun 0:00s 0 - # From Tim Parenti (2024-07-01): # Beginning in 1978, rather than triggering the Easter rule of the 1976 decree # (Easter fell on 1978-03-26), Article 5 was used instead, which allowed DST # dates to be changed by order of the Minister of Education and Scientific # Research, upon consultation with the Permanent Time Commission, "whenever # considered convenient." As such, a series of one-off ordinances were # promulgated for the mainland in 1978 through 1980, after which the 1976 # decree naturally came back into force from 1981. Rule Port 1978 1980 - Apr Sun>=1 1:00s 1:00 S Rule Port 1978 only - Oct 1 1:00s 0 - Rule Port 1979 1980 - Sep lastSun 1:00s 0 - Rule Port 1981 1986 - Mar lastSun 0:00s 1:00 S Rule Port 1981 1985 - Sep lastSun 0:00s 0 - # From Tim Parenti (2024-07-01): # Decreto-Lei 44-B/86 of 1986-03-07 switched mainland Portugal's transition # times from 0:00s to 1:00u to harmonize with the EEC from 1986-03-30. # https://dre.pt/dr/detalhe/decreto-lei/44-b-1986-628280 # (Transitions of 1:00s as previously reported and used by the W-Eur rules, # though equivalent, appear to have been fiction here.) Madeira continued to # use 0:00s for spring 1986 before joining with the mainland using 1:00u in the # fall; meanwhile, in the Azores the two were equivalent, so the law specifying # 0:00s wasn't touched until 1992. (See below for more on the islands.) # # From Rui Pedro Salgueiro (1992-11-12): # Portugal has recently (September, 27) changed timezone # (from WET to MET or CET) to harmonize with EEC. # # Martin Bruckmann (1996-02-29) reports via Peter Ilieve # that Portugal is reverting to 0:00 by not moving its clocks this spring. # The new Prime Minister was fed up with getting up in the dark in the winter. # # From Paul Eggert (1996-11-12): # IATA SSIM (1991-09) reports several 1991-09 and 1992-09 transitions # at 02:00u, not 01:00u. Assume that these are typos. # # Zone NAME STDOFF RULES FORMAT [UNTIL] #STDOFF -0:36:44.68 Zone Europe/Lisbon -0:36:45 - LMT 1884 -0:36:45 - LMT 1912 Jan 1 0:00u # Lisbon MT 0:00 Port WE%sT 1966 Oct 2 2:00s 1:00 - CET 1976 Sep 26 1:00 0:00 Port WE%sT 1986 0:00 EU WE%sT 1992 Sep 27 1:00u 1:00 EU CE%sT 1996 Mar 31 1:00u 0:00 EU WE%sT # From Tim Parenti (2024-07-01): # For the Azores and Madeira, legislation was followed from the laws currently # in force as listed at: # https://oal.ul.pt/hora-legal/legislacao/ # working backward through references of revocation and abrogation to # Decreto-Lei 47233 of 1966-10-01, the last time DST was abolished across the # mainland and its adjacent islands. # # From Tim Parenti (2026-05-26): # Observance of DST on the Azores and Madeira was explicitly covered by # mainland legislation in: # - Portaria 11767 of 1947-03-28 for 1947, # - Portaria 12286 of 1948-02-19 for 1948, and # - Decreto-Lei 37048 of 1948-09-07 through its revocation in 1966. # (See mainland commentary, above.) However, most legislation prior to 1947 # didn't explicitly call out these "adjacent islands", so we assume that DST # rules on the islands prior to 1947 were also like that of the mainland. Zone Atlantic/Azores -1:42:40 - LMT 1884 # Ponta Delgada -1:54:32 - HMT 1912 Jan 1 2:00u # Horta MT # Vanguard section, for zic and other parsers that support %z. -2:00 Port %z 1966 Oct 2 2:00s # From Tim Parenti (2024-07-01): # While Decreto-Lei 309/76 of 1976-04-27 reintroduced DST on the mainland by # falling back on 1976-09-26, it assigned the Permanent Time Commission to # study and propose revisions for the Azores and Madeira. Decreto Regional # 9/77/A of 1977-05-17 affirmed that "the legal time remained unchanged in the # Azores" at UT-1, and would remain there year-round. # https://dre.pt/dr/detalhe/decreto-regional/9-1977-252066 # # Decreto Regional 2/82/A, published 1982-03-02, adopted DST in the same # fashion as the mainland used at the time. # https://dre.pt/dr/detalhe/decreto-regional/2-1982-599965 # Though transitions in the Azores officially remained at 0:00s through 1992, # this was equivalent to the EU-style 1:00u adopted by the mainland in 1986, so # model it as such. -1:00 - %z 1982 Mar 28 0:00s -1:00 Port %z 1986 # Rearguard section, for parsers lacking %z; see ziguard.awk. # -2:00 Port -02/-01 1942 Apr 25 22:00s # -2:00 Port +00 1942 Aug 15 22:00s # -2:00 Port -02/-01 1943 Apr 17 22:00s # -2:00 Port +00 1943 Aug 28 22:00s # -2:00 Port -02/-01 1944 Apr 22 22:00s # -2:00 Port +00 1944 Aug 26 22:00s # -2:00 Port -02/-01 1945 Apr 21 22:00s # -2:00 Port +00 1945 Aug 25 22:00s # -2:00 Port -02/-01 1966 Oct 2 2:00s # -1:00 - -01 1982 Mar 28 0:00s # -1:00 Port -01/+00 1986 # End of rearguard section. # # From Paul Eggert (1996-11-12): # IATA SSIM (1991/1992) reports that the Azores were at -1:00. # IATA SSIM (1993-02) says +0:00; later issues (through 1996-09) say -1:00. # # From Tim Parenti (2024-07-01): # After mainland Portugal had shifted forward an hour from 1992-09-27, Decreto # Legislativo Regional 29/92/A of 1992-12-23 sought to "reduce the time # difference" by shifting the Azores forward as well from 1992-12-27. Just six # months later, this was revoked by Decreto Legislativo Regional 9/93/A, citing # "major changes in work habits and way of life." Though the revocation didn't # give a transition time, it was signed Wednesday 1993-06-16; assume it took # effect later that evening, and that an EU-style spring forward (to +01) was # still observed in the interim on 1993-03-28. # https://dre.pt/dr/detalhe/decreto-legislativo-regional/29-1992-621553 # https://dre.pt/dr/detalhe/decreto-legislativo-regional/9-1993-389633 -1:00 EU %z 1992 Dec 27 1:00s 0:00 EU WE%sT 1993 Jun 17 1:00u -1:00 EU %z Zone Atlantic/Madeira -1:07:36 - LMT 1884 # Funchal -1:07:36 - FMT 1912 Jan 1 1:00u # Funchal MT # Vanguard section, for zic and other parsers that support %z. -1:00 Port %z 1966 Oct 2 2:00s # Rearguard section, for parsers lacking %z; see ziguard.awk. # -1:00 Port -01/+00 1942 Apr 25 22:00s # -1:00 Port +01 1942 Aug 15 22:00s # -1:00 Port -01/+00 1943 Apr 17 22:00s # -1:00 Port +01 1943 Aug 28 22:00s # -1:00 Port -01/+00 1944 Apr 22 22:00s # -1:00 Port +01 1944 Aug 26 22:00s # -1:00 Port -01/+00 1945 Apr 21 22:00s # -1:00 Port +01 1945 Aug 25 22:00s # -1:00 Port -01/+00 1966 Oct 2 2:00s # End of rearguard section. # # From Tim Parenti (2024-07-01): # Decreto Regional 5/82/M, published 1982-04-03, established DST transitions at # 0:00u, which for Madeira is equivalent to the mainland's rules (0:00s) at the # time. It came into effect the day following its publication, Sunday # 1982-04-04, thus resuming Madeira's DST practice about a week later than the # mainland and the Azores. # https://dre.pt/dr/detalhe/decreto-regional/5-1982-608273 # # Decreto Legislativo Regional 18/86/M, published 1986-10-01, adopted EU-style # rules (1:00u) and entered into immediate force after being signed on # 1986-07-31. # https://dre.pt/dr/detalhe/decreto-legislativo-regional/18-1986-221705 0:00 - WET 1982 Apr 4 0:00 Port WE%sT 1986 Jul 31 0:00 EU WE%sT # Romania # # From Paul Eggert (1999-10-07): # Nine O'clock # (1998-10-23) reports that the switch occurred at # 04:00 local time in fall 1998. For lack of better info, # assume that Romania switched to EU rules in 1997, # the same year as Bulgaria. # # Rule NAME FROM TO - IN ON AT SAVE LETTER/S Rule Romania 1932 only - May 21 0:00s 1:00 S Rule Romania 1932 1939 - Oct Sun>=1 0:00s 0 - Rule Romania 1933 1939 - Apr Sun>=2 0:00s 1:00 S Rule Romania 1979 only - May 27 0:00 1:00 S Rule Romania 1979 only - Sep lastSun 0:00 0 - Rule Romania 1980 only - Apr 5 23:00 1:00 S Rule Romania 1980 only - Sep lastSun 1:00 0 - Rule Romania 1991 1993 - Mar lastSun 0:00s 1:00 S Rule Romania 1991 1993 - Sep lastSun 0:00s 0 - # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone Europe/Bucharest 1:44:24 - LMT 1891 Oct 1:44:24 - BMT 1931 Jul 24 # Bucharest MT 2:00 Romania EE%sT 1981 Mar 29 2:00s 2:00 C-Eur EE%sT 1991 2:00 Romania EE%sT 1994 2:00 E-Eur EE%sT 1997 2:00 EU EE%sT # Russia # From Alexander Krivenyshev (2011-09-15): # Based on last Russian Government Decree No. 725 on August 31, 2011 # (Government document # http://www.government.ru/gov/results/16355/print/ # in Russian) # there are few corrections have to be made for some Russian time zones... # All updated Russian Time Zones were placed in table and translated to English # by WorldTimeZone.com at the link below: # http://www.worldtimezone.com/dst_news/dst_news_russia36.htm # From Sanjeev Gupta (2011-09-27): # Scans of [Decree No. 23 of January 8, 1992] are available at: # http://government.consultant.ru/page.aspx?1223966 # They are in Cyrillic letters (presumably Russian). # From Arthur David Olson (2012-05-09): # Regarding the instant when clocks in time-zone-shifting parts of Russia # changed in September 2011: # # One source is # http://government.ru/gov/results/16355/ # which, according to translate.google.com, begins "Decree of August 31, # 2011 No. 725" and contains no other dates or "effective date" information. # # Another source is # https://rg.ru/2011/09/06/chas-zona-dok.html # which, according to translate.google.com, begins "Resolution of the # Government of the Russian Federation on August 31, 2011 N 725" and also # contains "Date first official publication: September 6, 2011 Posted on: # in the 'RG' - Federal Issue No. 5573 September 6, 2011" but which # does not contain any "effective date" information. # # Another source is # https://en.wikipedia.org/wiki/Oymyakonsky_District#cite_note-RuTime-7 # which, in note 8, contains "Resolution No. 725 of August 31, 2011... # Effective as of after 7 days following the day of the official publication" # but which does not contain any reference to September 6, 2011. # # The Wikipedia article refers to # http://base.consultant.ru/cons/cgi/online.cgi?req=doc;base=LAW;n=118896 # which seems to copy the text of the government.ru page. # # Tobias Conradi combines Wikipedia's # "as of after 7 days following the day of the official publication" # with www.rg.ru's "Date of first official publication: September 6, 2011" to # get September 13, 2011 as the cutover date (unusually, a Tuesday, as Tobias # Conradi notes). # # None of the sources indicates a time of day for changing clocks. # # Go with 2011-09-13 0:00s. # From Alexander Krivenyshev (2014-07-01): # According to the Russian news (ITAR-TASS News Agency) # http://en.itar-tass.com/russia/738562 # the State Duma has approved ... the draft bill on returning to # winter time standard and return Russia 11 time zones. The new # regulations will come into effect on October 26, 2014 at 02:00 ... # http://asozd2.duma.gov.ru/main.nsf/(Spravka)?OpenAgent&RN=431985-6&02 # Here is a link where we put together table (based on approved Bill N # 431985-6) with proposed 11 Russian time zones and corresponding # areas/cities/administrative centers in the Russian Federation (in English): # http://www.worldtimezone.com/dst_news/dst_news_russia65.html # # From Alexander Krivenyshev (2014-07-22): # Putin signed the Federal Law 431985-6 ... (in Russian) # http://itar-tass.com/obschestvo/1333711 # http://www.pravo.gov.ru:8080/page.aspx?111660 # http://www.kremlin.ru/acts/46279 # From October 26, 2014 the new Russian time zone map will look like this: # http://www.worldtimezone.com/dst_news/dst_news_russia-map-2014-07.html # From Paul Eggert (2006-03-22): # Moscow time zone abbreviations after 1919-07-01, and Moscow rules after 1991, # are from Andrey A. Chernov. The rest is from Shanks & Pottenger, # except we follow Chernov's report that 1992 DST transitions were Sat # 23:00, not Sun 02:00s. # # From Stanislaw A. Kuzikowski (1994-06-29): # But now it is some months since Novosibirsk is 3 hours ahead of Moscow! # I do not know why they have decided to make this change; # as far as I remember it was done exactly during winter->summer switching # so we (Novosibirsk) simply did not switch. # # From Andrey A. Chernov (1996-10-04): # 'MSK' and 'MSD' were born and used initially on Moscow computers with # UNIX-like OSes by several developer groups (e.g. Demos group, Kiae group).... # The next step was the UUCP network, the Relcom predecessor # (used mainly for mail), and MSK/MSD was actively used there. # # From Chris Carrier (1996-10-30): # According to a friend of mine who rode the Trans-Siberian Railroad from # Moscow to Irkutsk in 1995, public air and rail transport in Russia ... # still follows Moscow time, no matter where in Russia it is located. # # For Grozny, Chechnya, we have the following story from # John Daniszewski, "Scavengers in the Rubble", Los Angeles Times (2001-02-07): # News - often false - is spread by word of mouth. A rumor that it was # time to move the clocks back put this whole city out of sync with # the rest of Russia for two weeks - even soldiers stationed here began # enforcing curfew at the wrong time. # # From Gwillim Law (2001-06-05): # There's considerable evidence that Sakhalin Island used to be in # UTC+11, and has changed to UTC+10, in this decade. I start with the # SSIM, which listed Yuzhno-Sakhalinsk in zone RU10 along with Magadan # until February 1997, and then in RU9 with Khabarovsk and Vladivostok # since September 1997.... Although the Kuril Islands are # administratively part of Sakhalin oblast', they appear to have # remained on UTC+11 along with Magadan. # From Marat Nigametzianov (2018-07-16): # this is link to order from 1956 about timezone in USSR # http://astro.uni-altai.ru/~orion/blog/2011/11/novyie-granitsyi-chasovyih-poyasov-v-sssr/ # # From Paul Eggert (2018-07-16): # Perhaps someone could translate the link mentioned above, and use it # to correct our data for the ex-Soviet Union. It cites the following: # «Поясное время и новые границы часовых поясов» / сост. П.Н. Долгов, # отв. ред. Г.Д. Бурдун - М: Комитет стандартов, мер и измерительных # приборов при Совете Министров СССР, Междуведомственная комиссия # единой службы времени, 1956 г. # This book looks like it would be a helpful resource for the Soviet # Union through 1956. Although a copy was in the Scientific Library # of Tomsk State University, I have not been able to track down a copy nearby. # # From Stepan Golosunov (2018-07-21): # http://astro.uni-altai.ru/~orion/blog/2015/05/center-reforma-ischisleniya-vremeni-br-na-territorii-sssr-v-1957-godu-center/ # says that the 1956 decision to change time belts' borders was not # implemented as planned in 1956 and the change happened in 1957. # There is also the problem that actual time zones were different from # the official time belts (and from many time belts' maps) as there were # numerous exceptions to application of time belt rules. For example, # https://ru.wikipedia.org/wiki/Московское_время#Перемещение_границы_применения_московского_времени_на_восток # says that by 1962 there were many regions in the 3rd time belt that # were on Moscow time, referring to a 1962 map. By 1989 number of such # exceptions grew considerably. # From Tim Parenti (2014-07-06): # The comments detailing the coverage of each Russian zone are meant to assist # with maintenance only and represent our best guesses as to which regions # are covered by each zone. They are not meant to be taken as an authoritative # listing. The region codes listed come from # https://en.wikipedia.org/w/?title=Federal_subjects_of_Russia&oldid=611810498 # and are used for convenience only; no guarantees are made regarding their # future stability. ISO 3166-2:RU codes are also listed for first-level # divisions where available. # From Tim Parenti (2014-07-03): # Europe/Kaliningrad covers... # 39 RU-KGD Kaliningrad Oblast # From Paul Eggert (2019-07-25): # Although Shanks lists 1945-01-01 as the date for transition from # +01/+02 to +02/+03, more likely this is a placeholder. Guess that # the transition occurred at 1945-04-10 00:00, which is about when # Königsberg surrendered to Soviet troops. (Thanks to Alois Treindl.) # From Paul Eggert (2016-03-18): # The 1989 transition is from USSR act No. 227 (1989-03-14). # From Stepan Golosunov (2016-03-07): # http://www.rgo.ru/ru/kaliningradskoe-oblastnoe-otdelenie/ob-otdelenii/publikacii/kak-nam-zhilos-bez-letnego-vremeni # confirms that the 1989 change to Moscow-1 was implemented. # (The article, though, is misattributed to 1990 while saying that # summer->winter transition would be done on the 24 of September. But # 1990-09-24 was Monday, while 1989-09-24 was Sunday as expected.) # ... # http://www.kaliningradka.ru/site_pc/cherez/index.php?ELEMENT_ID=40091 # says that Kaliningrad switched to Moscow-1 on 1989-03-26, avoided # at the last moment switch to Moscow-1 on 1991-03-31, switched to # Moscow on 1991-11-03, switched to Moscow-1 on 1992-01-19. Zone Europe/Kaliningrad 1:22:00 - LMT 1893 Apr 1:00 C-Eur CE%sT 1945 Apr 10 2:00 Poland EE%sT 1946 Apr 7 3:00 Russia MSK/MSD 1989 Mar 26 2:00s 2:00 Russia EE%sT 2011 Mar 27 2:00s 3:00 - %z 2014 Oct 26 2:00s 2:00 - EET # From Paul Eggert (2016-02-21), per Tim Parenti (2014-07-03) and # Oscar van Vlijmen (2001-08-25): # Europe/Moscow covers... # 01 RU-AD Adygea, Republic of # 05 RU-DA Dagestan, Republic of # 06 RU-IN Ingushetia, Republic of # 07 RU-KB Kabardino-Balkar Republic # 08 RU-KL Kalmykia, Republic of # 09 RU-KC Karachay-Cherkess Republic # 10 RU-KR Karelia, Republic of # 11 RU-KO Komi Republic # 12 RU-ME Mari El Republic # 13 RU-MO Mordovia, Republic of # 15 RU-SE North Ossetia-Alania, Republic of # 16 RU-TA Tatarstan, Republic of # 20 RU-CE Chechen Republic # 21 RU-CU Chuvash Republic # 23 RU-KDA Krasnodar Krai # 26 RU-STA Stavropol Krai # 29 RU-ARK Arkhangelsk Oblast # 31 RU-BEL Belgorod Oblast # 32 RU-BRY Bryansk Oblast # 33 RU-VLA Vladimir Oblast # 35 RU-VLG Vologda Oblast # 36 RU-VOR Voronezh Oblast # 37 RU-IVA Ivanovo Oblast # 40 RU-KLU Kaluga Oblast # 44 RU-KOS Kostroma Oblast # 46 RU-KRS Kursk Oblast # 47 RU-LEN Leningrad Oblast # 48 RU-LIP Lipetsk Oblast # 50 RU-MOS Moscow Oblast # 51 RU-MUR Murmansk Oblast # 52 RU-NIZ Nizhny Novgorod Oblast # 53 RU-NGR Novgorod Oblast # 57 RU-ORL Oryol Oblast # 58 RU-PNZ Penza Oblast # 60 RU-PSK Pskov Oblast # 61 RU-ROS Rostov Oblast # 62 RU-RYA Ryazan Oblast # 67 RU-SMO Smolensk Oblast # 68 RU-TAM Tambov Oblast # 69 RU-TVE Tver Oblast # 71 RU-TUL Tula Oblast # 76 RU-YAR Yaroslavl Oblast # 77 RU-MOW Moscow # 78 RU-SPE Saint Petersburg # 83 RU-NEN Nenets Autonomous Okrug # From Paul Eggert (2016-08-23): # The Soviets switched to UT-based time in 1919. Decree No. 59 # (1919-02-08) http://istmat.info/node/35567 established UT-based time # zones, and Decree No. 147 (1919-03-29) http://istmat.info/node/35854 # specified a transition date of 1919-07-01, apparently at 00:00 UT. # No doubt only the Soviet-controlled regions switched on that date; # later transitions to UT-based time in other parts of Russia are # taken from what appear to be guesses by Shanks. # (Thanks to Alexander Belopolsky for pointers to the decrees.) # From Stepan Golosunov (2016-03-07): # 11. Regions-violators, 1981-1982. # Wikipedia refers to # http://maps.monetonos.ru/maps/raznoe/Old_Maps/Old_Maps/Articles/022/3_1981.html # http://besp.narod.ru/nauka_1981_3.htm # # The second link provides two articles scanned from the Nauka i Zhizn # magazine No. 3, 1981 and a scan of the short article attributed to # the Trud newspaper from February 1982. The first link provides the # same Nauka i Zhizn articles converted to the text form (but misses # time belt changes map). # # The second Nauka i Zhizn article says that in addition to # introduction of summer time on 1981-04-01 there are some time belt # border changes on 1981-10-01, mostly affecting Nenets Autonomous # Okrug, Krasnoyarsk Krai, Yakutia, Magadan Oblast and Chukotka # according to the provided map (colored one). In addition to that # "time violators" (regions which were not using rules of the time # belts in which they were located) would not be moving off the DST on # 1981-10-01 to restore the decree time usage. (Komi ASSR was # supposed to repeat that move in October 1982 to account for the 2 # hour difference.) Map depicting "time violators" before 1981-10-01 # is also provided. # # The article from Trud says that 1981-10-01 changes caused problems # and some territories would be moved to pre-1981-10-01 time by not # moving to summer time on 1982-04-01. Namely: Dagestan, # Kabardino-Balkar, Kalmyk, Komi, Mari, Mordovian, North Ossetian, # Tatar, Chechen-Ingush and Chuvash ASSR, Krasnodar and Stavropol # krais, Arkhangelsk, Vladimir, Vologda, Voronezh, Gorky, Ivanovo, # Kostroma, Lipetsk, Penza, Rostov, Ryazan, Tambov, Tyumen and # Yaroslavl oblasts, Nenets and Evenk autonomous okrugs, Khatangsky # district of Taymyr Autonomous Okrug. As a result Evenk Autonomous # Okrug and Khatangsky district of Taymyr Autonomous Okrug would end # up on Moscow+4, Tyumen Oblast on Moscow+2 and the rest on Moscow # time. # # http://astrozet.net/files/Zones/DOC/RU/1980-925.txt # attributes the 1982 changes to the Act of the Council of Ministers # of the USSR No. 126 from 18.02.1982. 1980-925.txt also adds # Udmurtia to the list of affected territories and lists Khatangsky # district separately from Taymyr Autonomous Okrug. Probably erroneously. # # The affected territories are currently listed under Europe/Moscow, # Asia/Yekaterinburg and Asia/Krasnoyarsk. # # 12. Udmurtia # The fact that Udmurtia is depicted as a violator in the Nauka i # Zhizn article hints at Izhevsk being on different time from # Kuybyshev before 1981-10-01. Udmurtia is not mentioned in the 1989 act. # http://astrozet.net/files/Zones/DOC/RU/1980-925.txt # implies Udmurtia was on Moscow time after 1982-04-01. # Wikipedia implies Udmurtia being on Moscow+1 until 1991. # # ... # # All Russian zones are supposed to have by default a -1 change at # 1991-03-31 2:00 (cancellation of the decree time in the USSR) and a +1 # change at 1992-01-19 2:00 (restoration of the decree time in Russia). # # There were some exceptions, though. # Wikipedia says newspapers listed Astrakhan, Saratov, Kirov, Volgograd, # Izhevsk, Grozny, Kazan and Samara as such exceptions for the 1992 # change. (Different newspapers providing different lists. And some # lists found in the internet are quite wild.) # # And apparently some exceptions were reverted in the last moment. # http://www.kaliningradka.ru/site_pc/cherez/index.php?ELEMENT_ID=40091 # says that Kaliningrad decided not to be an exception 2 days before the # 1991-03-31 switch and one person at # https://izhevsk.ru/forum_light_message/50/682597-m8369040.html # says he remembers that Samara opted out of the 1992-01-19 exception # 2 days before the switch. # # From Alois Treindl (2022-02-15): # the Russian wikipedia page # https://ru.wikipedia.org/wiki/Московское_время#Перемещение_границы_применения_московского_времени_на_восток # contains the sentence (in Google translation) "In the autumn of # 1981, Arkhangelsk, Vologda, Yaroslavl, Ivanovo, Vladimir, Ryazan, # Lipetsk, Voronezh, Rostov-on-Don, Krasnodar and regions to the east # of those named (about 30 in total) parted ways with Moscow time. # However, the convenience of common time with Moscow turned out to be # decisive - in 1982, these regions again switched to Moscow time." # Shanks International atlas has similar information, and also the # Russian book Zaitsev A., Kutalev D. A new astrologer's reference # book. Coordinates of cities and time corrections, - The World of # Urania, 2012 (Russian: Зайцев А., Куталёв Д., Новый справочник # астролога. Координаты городов и временные поправки). # To me it seems that an extra zone is needed, which starts with LMT # util 1919, later follows Moscow since 1930, but deviates from it # between 1 October 1981 until 1 April 1982. # # # From Paul Eggert (2022-02-15): # Given the above, we appear to be missing some Zone entries for the # chaotic early 1980s in Russia. It's not clear what these entries # should be. For now, sweep this under the rug and just document the # time in Moscow. # From Vladimir Karpinsky (2014-07-08): # LMT in Moscow (before Jul 3, 1916) is 2:30:17, that was defined by Moscow # Observatory (coordinates: 55° 45' 29.70", 37° 34' 05.30").... # LMT in Moscow since Jul 3, 1916 is 2:31:01 as a result of new standard. # (The info is from the book by Byalokoz ... p. 18.) # The time in St. Petersburg as capital of Russia was defined by # Pulkov observatory, near St. Petersburg. In 1916 LMT Moscow # was synchronized with LMT St. Petersburg (+30 minutes), (Pulkov observatory # coordinates: 59° 46' 18.70", 30° 19' 40.70") so 30° 19' 40.70" > # 2h01m18.7s = 2:01:19. LMT Moscow = LMT St.Petersburg + 30m 2:01:19 + 0:30 = # 2:31:19 ... # # From Paul Eggert (2014-07-08): # Milne does not list Moscow, but suggests that its time might be listed in # Résumés mensuels et annuels des observations météorologiques (1895). # Presumably this is OCLC 85825704, a journal published with parallel text in # Russian and French. This source has not been located; go with Karpinsky. Zone Europe/Moscow 2:30:17 - LMT 1880 2:30:17 - MMT 1916 Jul 3 # Moscow Mean Time 2:31:19 Russia %s 1919 Jul 1 0:00u 3:00 Russia %s 1921 Oct 3:00 Russia MSK/MSD 1922 Oct 2:00 - EET 1930 Jun 21 3:00 Russia MSK/MSD 1991 Mar 31 2:00s 2:00 Russia EE%sT 1992 Jan 19 2:00s 3:00 Russia MSK/MSD 2011 Mar 27 2:00s 4:00 - MSK 2014 Oct 26 2:00s 3:00 - MSK # From Paul Eggert (2016-12-06): # Europe/Simferopol covers Crimea. Zone Europe/Simferopol 2:16:24 - LMT 1880 2:16 - SMT 1924 May 2 # Simferopol Mean T 2:00 - EET 1930 Jun 21 3:00 - MSK 1941 Nov 1:00 C-Eur CE%sT 1944 Apr 13 3:00 Russia MSK/MSD 1990 3:00 - MSK 1990 Jul 1 2:00 2:00 - EET 1992 Mar 20 # Central Crimea used Moscow time 1994/1997. # # From Paul Eggert (2022-07-21): # The _Economist_ (1994-05-28, p 45) reported that central Crimea switched # from Kyiv to Moscow time sometime after the January 1994 elections. # Shanks (1999) says "date of change uncertain", but implies that it happened # sometime between the 1994 DST switches. Shanks & Pottenger simply say # 1994-09-25 03:00, but that can't be right. For now, guess it # changed in May. This change evidently didn't last long; see below. 2:00 C-Eur EE%sT 1994 May # From IATA SSIM (1994/1997), which also said that Kerch is still like Kyiv. 3:00 C-Eur MSK/MSD 1996 Mar 31 0:00s 3:00 1:00 MSD 1996 Oct 27 3:00s # IATA SSIM (1997-09) said Crimea switched to EET/EEST. # Assume it happened in March by not changing the clocks. 3:00 - MSK 1997 Mar lastSun 1:00u # From Alexander Krivenyshev (2014-03-17): # time change at 2:00 (2am) on March 30, 2014 # https://vz.ru/news/2014/3/17/677464.html # From Tim Parenti (2022-07-01), per Paul Eggert (2014-03-30): # The clocks at the railway station in Simferopol were put forward from 22:00 # to 24:00 the previous day in a "symbolic ceremony"; however, per # contemporaneous news reports, "ordinary Crimeans [made] the daylight savings # time switch at 2am" on Sunday. # https://www.business-standard.com/article/pti-stories/crimea-to-set-clocks-to-russia-time-114033000014_1.html # https://www.reuters.com/article/us-ukraine-crisis-crimea-time/crimea-switches-to-moscow-time-amid-incorporation-frenzy-idUKBREA2S0LT20140329 # https://www.bbc.com/news/av/world-europe-26806583 2:00 EU EE%sT 2014 Mar 30 2:00 4:00 - MSK 2014 Oct 26 2:00s 3:00 - MSK # From Paul Eggert (2016-03-18): # Europe/Astrakhan covers: # 30 RU-AST Astrakhan Oblast # # The 1989 transition is from USSR act No. 227 (1989-03-14). # From Alexander Krivenyshev (2016-01-12): # On February 10, 2016 Astrakhan Oblast got approval by the Federation # Council to change its time zone to UTC+4 (from current UTC+3 Moscow time).... # This Federal Law shall enter into force on 27 March 2016 at 02:00. # From Matt Johnson (2016-03-09): # http://publication.pravo.gov.ru/Document/View/0001201602150056 Zone Europe/Astrakhan 3:12:12 - LMT 1924 May 3:00 - %z 1930 Jun 21 4:00 Russia %z 1989 Mar 26 2:00s 3:00 Russia %z 1991 Mar 31 2:00s 4:00 - %z 1992 Mar 29 2:00s 3:00 Russia %z 2011 Mar 27 2:00s 4:00 - %z 2014 Oct 26 2:00s 3:00 - %z 2016 Mar 27 2:00s 4:00 - %z # From Paul Eggert (2016-11-11): # Europe/Volgograd covers: # 34 RU-VGG Volgograd Oblast # The 1988 transition is from USSR act No. 5 (1988-01-04). # From Alexander Fetisov (2018-09-20): # Volgograd region in southern Russia (Europe/Volgograd) change # timezone from UTC+3 to UTC+4 from 28oct2018. # http://sozd.parliament.gov.ru/bill/452878-7 # # From Stepan Golosunov (2018-10-11): # The law has been published today on # http://publication.pravo.gov.ru/Document/View/0001201810110037 # From Alexander Krivenyshev (2020-11-27): # The State Duma approved (Nov 24, 2020) the transition of the Volgograd # region to the Moscow time zone.... # https://sozd.duma.gov.ru/bill/1012130-7 # # From Stepan Golosunov (2020-12-05): # Currently proposed text for the second reading (expected on December 8) ... # changes the date to December 27. https://v1.ru/text/gorod/2020/12/04/69601031/ # # From Stepan Golosunov (2020-12-22): # The law was published today on # http://publication.pravo.gov.ru/Document/View/0001202012220002 Zone Europe/Volgograd 2:57:40 - LMT 1920 Jan 3 3:00 - %z 1930 Jun 21 4:00 - %z 1961 Nov 11 4:00 Russia %z 1988 Mar 27 2:00s 3:00 Russia MSK/MSD 1991 Mar 31 2:00s 4:00 - %z 1992 Mar 29 2:00s 3:00 Russia MSK/MSD 2011 Mar 27 2:00s 4:00 - MSK 2014 Oct 26 2:00s 3:00 - MSK 2018 Oct 28 2:00s 4:00 - %z 2020 Dec 27 2:00s 3:00 - MSK # From Paul Eggert (2016-11-11): # Europe/Saratov covers: # 64 RU-SAR Saratov Oblast # From Yuri Konotopov (2016-11-11): # Dec 4, 2016 02:00 UTC+3.... Saratov Region's local time will be ... UTC+4. # From Stepan Golosunov (2016-11-11): # ... Byalokoz listed Saratov on 03:04:18. # From Stepan Golosunov (2016-11-22): # http://publication.pravo.gov.ru/Document/View/0001201611220031 Zone Europe/Saratov 3:04:18 - LMT 1919 Jul 1 0:00u 3:00 - %z 1930 Jun 21 4:00 Russia %z 1988 Mar 27 2:00s 3:00 Russia %z 1991 Mar 31 2:00s 4:00 - %z 1992 Mar 29 2:00s 3:00 Russia %z 2011 Mar 27 2:00s 4:00 - %z 2014 Oct 26 2:00s 3:00 - %z 2016 Dec 4 2:00s 4:00 - %z # From Paul Eggert (2016-03-18): # Europe/Kirov covers: # 43 RU-KIR Kirov Oblast # The 1989 transition is from USSR act No. 227 (1989-03-14). # Zone Europe/Kirov 3:18:48 - LMT 1919 Jul 1 0:00u 3:00 - %z 1930 Jun 21 4:00 Russia %z 1989 Mar 26 2:00s 3:00 Russia MSK/MSD 1991 Mar 31 2:00s 4:00 - %z 1992 Mar 29 2:00s 3:00 Russia MSK/MSD 2011 Mar 27 2:00s 4:00 - MSK 2014 Oct 26 2:00s 3:00 - MSK # From Tim Parenti (2014-07-03), per Oscar van Vlijmen (2001-08-25): # Europe/Samara covers... # 18 RU-UD Udmurt Republic # 63 RU-SAM Samara Oblast # From Paul Eggert (2016-03-18): # Byalokoz 1919 says Samara was 3:20:20. # The 1989 transition is from USSR act No. 227 (1989-03-14). Zone Europe/Samara 3:20:20 - LMT 1919 Jul 1 0:00u 3:00 - %z 1930 Jun 21 4:00 - %z 1935 Jan 27 4:00 Russia %z 1989 Mar 26 2:00s 3:00 Russia %z 1991 Mar 31 2:00s 2:00 Russia %z 1991 Sep 29 2:00s 3:00 - %z 1991 Oct 20 3:00 4:00 Russia %z 2010 Mar 28 2:00s 3:00 Russia %z 2011 Mar 27 2:00s 4:00 - %z # From Paul Eggert (2016-03-18): # Europe/Ulyanovsk covers: # 73 RU-ULY Ulyanovsk Oblast # The 1989 transition is from USSR act No. 227 (1989-03-14). # From Alexander Krivenyshev (2016-02-17): # Ulyanovsk ... on their way to change time zones by March 27, 2016 at 2am. # Ulyanovsk Oblast ... from MSK to MSK+1 (UTC+3 to UTC+4) ... # 920582-6 ... 02/17/2016 The State Duma passed the bill in the first reading. # From Matt Johnson (2016-03-09): # http://publication.pravo.gov.ru/Document/View/0001201603090051 Zone Europe/Ulyanovsk 3:13:36 - LMT 1919 Jul 1 0:00u 3:00 - %z 1930 Jun 21 4:00 Russia %z 1989 Mar 26 2:00s 3:00 Russia %z 1991 Mar 31 2:00s 2:00 Russia %z 1992 Jan 19 2:00s 3:00 Russia %z 2011 Mar 27 2:00s 4:00 - %z 2014 Oct 26 2:00s 3:00 - %z 2016 Mar 27 2:00s 4:00 - %z # From Tim Parenti (2014-07-03), per Oscar van Vlijmen (2001-08-25): # Asia/Yekaterinburg covers... # 02 RU-BA Bashkortostan, Republic of # 90 RU-PER Perm Krai # 45 RU-KGN Kurgan Oblast # 56 RU-ORE Orenburg Oblast # 66 RU-SVE Sverdlovsk Oblast # 72 RU-TYU Tyumen Oblast # 74 RU-CHE Chelyabinsk Oblast # 86 RU-KHM Khanty-Mansi Autonomous Okrug - Yugra # 89 RU-YAN Yamalo-Nenets Autonomous Okrug # # Note: Effective 2005-12-01, (59) Perm Oblast and (81) Komi-Permyak # Autonomous Okrug merged to form (90, RU-PER) Perm Krai. # Milne says Yekaterinburg was 4:02:32.9. # Byalokoz 1919 says its provincial time was based on Perm, at 3:45:05. # Assume it switched on 1916-07-03, the time of the new standard. # The 1919 and 1930 transitions are from Shanks. #STDOFF 4:02:32.9 Zone Asia/Yekaterinburg 4:02:33 - LMT 1916 Jul 3 3:45:05 - PMT 1919 Jul 15 4:00 4:00 - %z 1930 Jun 21 5:00 Russia %z 1991 Mar 31 2:00s 4:00 Russia %z 1992 Jan 19 2:00s 5:00 Russia %z 2011 Mar 27 2:00s 6:00 - %z 2014 Oct 26 2:00s 5:00 - %z # From Tim Parenti (2014-07-03), per Oscar van Vlijmen (2001-08-25): # Asia/Omsk covers... # 55 RU-OMS Omsk Oblast # Byalokoz 1919 says Omsk was 4:53:30. Zone Asia/Omsk 4:53:30 - LMT 1919 Nov 14 5:00 - %z 1930 Jun 21 6:00 Russia %z 1991 Mar 31 2:00s 5:00 Russia %z 1992 Jan 19 2:00s 6:00 Russia %z 2011 Mar 27 2:00s 7:00 - %z 2014 Oct 26 2:00s 6:00 - %z # From Paul Eggert (2016-02-22): # Asia/Barnaul covers: # 04 RU-AL Altai Republic # 22 RU-ALT Altai Krai # Data before 1991 are from Shanks & Pottenger. # From Stepan Golosunov (2016-03-07): # Letter of Bank of Russia from 1995-05-25 # http://www.bestpravo.ru/rossijskoje/lj-akty/y3a.htm # suggests that Altai Republic transitioned to Moscow+3 on # 1995-05-28. # # https://regnum.ru/news/society/1957270.html # has some historical data for Altai Krai: # before 1957: west part on UT+6, east on UT+7 # after 1957: UT+7 # since 1995: UT+6 # http://barnaul.rusplt.ru/index/pochemu_altajskij_kraj_okazalsja_v_neprivychnom_chasovom_pojase-17648.html # confirms that and provides more details including 1995-05-28 transition date. # From Alexander Krivenyshev (2016-02-17): # Altai Krai and Altai Republic on their way to change time zones # by March 27, 2016 at 2am.... # Altai Republic / Gorno-Altaysk MSK+3 to MSK+4 (UTC+6 to UTC+7) ... # Altai Krai / Barnaul MSK+3 to MSK+4 (UTC+6 to UTC+7) # From Matt Johnson (2016-03-09): # http://publication.pravo.gov.ru/Document/View/0001201603090043 # http://publication.pravo.gov.ru/Document/View/0001201603090038 Zone Asia/Barnaul 5:35:00 - LMT 1919 Dec 10 6:00 - %z 1930 Jun 21 7:00 Russia %z 1991 Mar 31 2:00s 6:00 Russia %z 1992 Jan 19 2:00s 7:00 Russia %z 1995 May 28 6:00 Russia %z 2011 Mar 27 2:00s 7:00 - %z 2014 Oct 26 2:00s 6:00 - %z 2016 Mar 27 2:00s 7:00 - %z # From Paul Eggert (2016-03-18): # Asia/Novosibirsk covers: # 54 RU-NVS Novosibirsk Oblast # From Stepan Golosunov (2016-05-30): # http://asozd2.duma.gov.ru/main.nsf/(Spravka)?OpenAgent&RN=1085784-6 # moves Novosibirsk oblast from UTC+6 to UTC+7. # From Stepan Golosunov (2016-07-04): # The law was signed yesterday and published today on # http://publication.pravo.gov.ru/Document/View/0001201607040064 Zone Asia/Novosibirsk 5:31:40 - LMT 1919 Dec 14 6:00 6:00 - %z 1930 Jun 21 7:00 Russia %z 1991 Mar 31 2:00s 6:00 Russia %z 1992 Jan 19 2:00s 7:00 Russia %z 1993 May 23 # say Shanks & P. 6:00 Russia %z 2011 Mar 27 2:00s 7:00 - %z 2014 Oct 26 2:00s 6:00 - %z 2016 Jul 24 2:00s 7:00 - %z # From Paul Eggert (2016-03-18): # Asia/Tomsk covers: # 70 RU-TOM Tomsk Oblast # From Stepan Golosunov (2016-03-24): # Byalokoz listed Tomsk at 5:39:51. # From Stanislaw A. Kuzikowski (1994-06-29): # Tomsk is still 4 hours ahead of Moscow. # From Stepan Golosunov (2016-03-19): # http://pravo.gov.ru/proxy/ips/?docbody=&nd=102075743 # (fifth time belt being UTC+5+1(decree time) # / UTC+5+1(decree time)+1(summer time)) ... # Note that time belts (numbered from 2 (Moscow) to 12 according to their # GMT/UTC offset and having too many exceptions like regions formally # belonging to one belt but using time from another) were replaced # with time zones in 2011 with different numbering (there was a # 2-hour gap between second and third zones in 2011-2014). # From Stepan Golosunov (2016-04-12): # http://asozd2.duma.gov.ru/main.nsf/(SpravkaNew)?OpenAgent&RN=1006865-6 # This bill was approved in the first reading today. It moves Tomsk oblast # from UTC+6 to UTC+7 and is supposed to come into effect on 2016-05-29 at # 2:00. The bill needs to be approved in the second and the third readings by # the State Duma, approved by the Federation Council, signed by the President # and published to become a law. Minor changes in the text are to be expected # before the second reading (references need to be updated to account for the # recent changes). # # Judging by the ultra-short one-day amendments period, recent similar laws, # the State Duma schedule and the Federation Council schedule # http://www.duma.gov.ru/legislative/planning/day-shedule/por_vesna_2016/ # http://council.gov.ru/activity/meetings/schedule/63303 # I speculate that the final text of the bill will be proposed tomorrow, the # bill will be approved in the second and the third readings on Friday, # approved by the Federation Council on 2016-04-20, signed by the President and # published as a law around 2016-04-26. # From Matt Johnson (2016-04-26): # http://publication.pravo.gov.ru/Document/View/0001201604260048 Zone Asia/Tomsk 5:39:51 - LMT 1919 Dec 22 6:00 - %z 1930 Jun 21 7:00 Russia %z 1991 Mar 31 2:00s 6:00 Russia %z 1992 Jan 19 2:00s 7:00 Russia %z 2002 May 1 3:00 6:00 Russia %z 2011 Mar 27 2:00s 7:00 - %z 2014 Oct 26 2:00s 6:00 - %z 2016 May 29 2:00s 7:00 - %z # From Tim Parenti (2014-07-03): # Asia/Novokuznetsk covers... # 42 RU-KEM Kemerovo Oblast # From Alexander Krivenyshev (2009-10-13): # Kemerovo oblast' (Kemerovo region) in Russia will change current time zone on # March 28, 2010: # from current Russia Zone 6 - Krasnoyarsk Time Zone (KRA) UTC +0700 # to Russia Zone 5 - Novosibirsk Time Zone (NOV) UTC +0600 # # This is according to Government of Russia decree No. 740, on September # 14, 2009 "Application in the territory of the Kemerovo region the Fifth # time zone." ("Russia Zone 5" or old "USSR Zone 5" is GMT +0600) # # Russian Government web site (Russian language) # http://www.government.ru/content/governmentactivity/rfgovernmentdecisions/archive/2009/09/14/991633.htm # or Russian-English translation by WorldTimeZone.com with reference # map to local region and new Russia Time Zone map after March 28, 2010 # http://www.worldtimezone.com/dst_news/dst_news_russia03.html # # Thus, when Russia will switch to DST on the night of March 28, 2010 # Kemerovo region (Kemerovo oblast') will not change the clock. # From Tim Parenti (2014-07-02), per Alexander Krivenyshev (2014-07-02): # The Kemerovo region will remain at UTC+7 through the 2014-10-26 change, thus # realigning itself with KRAT. Zone Asia/Novokuznetsk 5:48:48 - LMT 1924 May 1 6:00 - %z 1930 Jun 21 7:00 Russia %z 1991 Mar 31 2:00s 6:00 Russia %z 1992 Jan 19 2:00s 7:00 Russia %z 2010 Mar 28 2:00s 6:00 Russia %z 2011 Mar 27 2:00s 7:00 - %z # From Tim Parenti (2014-07-03), per Oscar van Vlijmen (2001-08-25): # Asia/Krasnoyarsk covers... # 17 RU-TY Tuva Republic # 19 RU-KK Khakassia, Republic of # 24 RU-KYA Krasnoyarsk Krai # # Note: Effective 2007-01-01, (88) Evenk Autonomous Okrug and (84) Taymyr # Autonomous Okrug were merged into (24, RU-KYA) Krasnoyarsk Krai. # Byalokoz 1919 says Krasnoyarsk was 6:11:26. Zone Asia/Krasnoyarsk 6:11:26 - LMT 1920 Jan 6 6:00 - %z 1930 Jun 21 7:00 Russia %z 1991 Mar 31 2:00s 6:00 Russia %z 1992 Jan 19 2:00s 7:00 Russia %z 2011 Mar 27 2:00s 8:00 - %z 2014 Oct 26 2:00s 7:00 - %z # From Tim Parenti (2014-07-03), per Oscar van Vlijmen (2001-08-25): # Asia/Irkutsk covers... # 03 RU-BU Buryatia, Republic of # 38 RU-IRK Irkutsk Oblast # # Note: Effective 2008-01-01, (85) Ust-Orda Buryat Autonomous Okrug was # merged into (38, RU-IRK) Irkutsk Oblast. # Milne 1899 says Irkutsk was 6:57:15. # Byalokoz 1919 says Irkutsk was 6:57:05. # Go with Byalokoz. Zone Asia/Irkutsk 6:57:05 - LMT 1880 6:57:05 - IMT 1920 Jan 25 # Irkutsk Mean Time 7:00 - %z 1930 Jun 21 8:00 Russia %z 1991 Mar 31 2:00s 7:00 Russia %z 1992 Jan 19 2:00s 8:00 Russia %z 2011 Mar 27 2:00s 9:00 - %z 2014 Oct 26 2:00s 8:00 - %z # From Tim Parenti (2014-07-06): # Asia/Chita covers... # 92 RU-ZAB Zabaykalsky Krai # # Note: Effective 2008-03-01, (75) Chita Oblast and (80) Agin-Buryat # Autonomous Okrug merged to form (92, RU-ZAB) Zabaykalsky Krai. # From Alexander Krivenyshev (2016-01-02): # [The] time zone in the Trans-Baikal Territory (Zabaykalsky Krai) - # Asia/Chita [is changing] from UTC+8 to UTC+9. Effective date will # be March 27, 2016 at 2:00am.... # http://publication.pravo.gov.ru/Document/View/0001201512300107 Zone Asia/Chita 7:33:52 - LMT 1919 Dec 15 8:00 - %z 1930 Jun 21 9:00 Russia %z 1991 Mar 31 2:00s 8:00 Russia %z 1992 Jan 19 2:00s 9:00 Russia %z 2011 Mar 27 2:00s 10:00 - %z 2014 Oct 26 2:00s 8:00 - %z 2016 Mar 27 2:00 9:00 - %z # From Tim Parenti (2014-07-03), per Oscar van Vlijmen (2009-11-29): # Asia/Yakutsk covers... # 28 RU-AMU Amur Oblast # # ...and parts of (14, RU-SA) Sakha (Yakutia) Republic: # 14-02 **** Aldansky District # 14-04 **** Amginsky District # 14-05 **** Anabarsky District # 14-06 **** Bulunsky District # 14-07 **** Verkhnevilyuysky District # 14-10 **** Vilyuysky District # 14-11 **** Gorny District # 14-12 **** Zhigansky District # 14-13 **** Kobyaysky District # 14-14 **** Lensky District # 14-15 **** Megino-Kangalassky District # 14-16 **** Mirninsky District # 14-18 **** Namsky District # 14-19 **** Neryungrinsky District # 14-21 **** Nyurbinsky District # 14-23 **** Olenyoksky District # 14-24 **** Olyokminsky District # 14-26 **** Suntarsky District # 14-27 **** Tattinsky District # 14-29 **** Ust-Aldansky District # 14-32 **** Khangalassky District # 14-33 **** Churapchinsky District # 14-34 **** Eveno-Bytantaysky National District # From Tim Parenti (2014-07-03): # Our commentary seems to have lost mention of (14-19) Neryungrinsky District. # Since the surrounding districts of Sakha are all YAKT, assume this is, too. # Also assume its history has been the same as the rest of Asia/Yakutsk. # Byalokoz 1919 says Yakutsk was 8:38:58. Zone Asia/Yakutsk 8:38:58 - LMT 1919 Dec 15 8:00 - %z 1930 Jun 21 9:00 Russia %z 1991 Mar 31 2:00s 8:00 Russia %z 1992 Jan 19 2:00s 9:00 Russia %z 2011 Mar 27 2:00s 10:00 - %z 2014 Oct 26 2:00s 9:00 - %z # From Tim Parenti (2014-07-03), per Oscar van Vlijmen (2009-11-29): # Asia/Vladivostok covers... # 25 RU-PRI Primorsky Krai # 27 RU-KHA Khabarovsk Krai # 79 RU-YEV Jewish Autonomous Oblast # # ...and parts of (14, RU-SA) Sakha (Yakutia) Republic: # 14-09 **** Verkhoyansky District # 14-31 **** Ust-Yansky District # Milne 1899 says Vladivostok was 8:47:33.5. # Byalokoz 1919 says Vladivostok was 8:47:31. # Go with Byalokoz. Zone Asia/Vladivostok 8:47:31 - LMT 1922 Nov 15 9:00 - %z 1930 Jun 21 10:00 Russia %z 1991 Mar 31 2:00s 9:00 Russia %z 1992 Jan 19 2:00s 10:00 Russia %z 2011 Mar 27 2:00s 11:00 - %z 2014 Oct 26 2:00s 10:00 - %z # From Tim Parenti (2014-07-03): # Asia/Khandyga covers parts of (14, RU-SA) Sakha (Yakutia) Republic: # 14-28 **** Tomponsky District # 14-30 **** Ust-Maysky District # From Arthur David Olson (2022-03-21): # Tomponsky and Ust-Maysky switched from Vladivostok time to Yakutsk time # in 2011. # From Paul Eggert (2012-11-25): # Shanks and Pottenger (2003) has Khandyga on Yakutsk time. # Make a wild guess that it switched to Vladivostok time in 2004. # This transition is no doubt wrong, but we have no better info. Zone Asia/Khandyga 9:02:13 - LMT 1919 Dec 15 8:00 - %z 1930 Jun 21 9:00 Russia %z 1991 Mar 31 2:00s 8:00 Russia %z 1992 Jan 19 2:00s 9:00 Russia %z 2004 10:00 Russia %z 2011 Mar 27 2:00s 11:00 - %z 2011 Sep 13 0:00s # Decree 725? 10:00 - %z 2014 Oct 26 2:00s 9:00 - %z # From Tim Parenti (2014-07-03): # Asia/Sakhalin covers... # 65 RU-SAK Sakhalin Oblast # ...with the exception of: # 65-11 **** Severo-Kurilsky District (North Kuril Islands) # From Matt Johnson (2016-02-22): # Asia/Sakhalin is moving (in entirety) from UTC+10 to UTC+11 ... # (2016-03-09): # http://publication.pravo.gov.ru/Document/View/0001201603090044 # The Zone name should be Asia/Yuzhno-Sakhalinsk, but that's too long. Zone Asia/Sakhalin 9:30:48 - LMT 1905 Aug 23 9:00 - %z 1945 Aug 25 11:00 Russia %z 1991 Mar 31 2:00s # Sakhalin T 10:00 Russia %z 1992 Jan 19 2:00s 11:00 Russia %z 1997 Mar lastSun 2:00s 10:00 Russia %z 2011 Mar 27 2:00s 11:00 - %z 2014 Oct 26 2:00s 10:00 - %z 2016 Mar 27 2:00s 11:00 - %z # From Tim Parenti (2014-07-03), per Oscar van Vlijmen (2009-11-29): # Asia/Magadan covers... # 49 RU-MAG Magadan Oblast # From Tim Parenti (2014-07-06), per Alexander Krivenyshev (2014-07-02): # Magadan Oblast is moving from UTC+12 to UTC+10 on 2014-10-26; however, # several districts of Sakha Republic as well as Severo-Kurilsky District of # the Sakhalin Oblast (also known as the North Kuril Islands), represented # until now by Asia/Magadan, will instead move to UTC+11. These regions will # need their own zone. # From Alexander Krivenyshev (2016-03-27): # ... draft bill 948300-6 to change its time zone from UTC+10 to UTC+11 ... # will take ... effect ... on April 24, 2016 at 2 o'clock # # From Matt Johnson (2016-04-05): # ... signed by the President today ... # http://publication.pravo.gov.ru/Document/View/0001201604050038 Zone Asia/Magadan 10:03:12 - LMT 1924 May 2 10:00 - %z 1930 Jun 21 # Magadan Time 11:00 Russia %z 1991 Mar 31 2:00s 10:00 Russia %z 1992 Jan 19 2:00s 11:00 Russia %z 2011 Mar 27 2:00s 12:00 - %z 2014 Oct 26 2:00s 10:00 - %z 2016 Apr 24 2:00s 11:00 - %z # From Tim Parenti (2014-07-06): # Asia/Srednekolymsk covers parts of (14, RU-SA) Sakha (Yakutia) Republic: # 14-01 **** Abyysky District # 14-03 **** Allaikhovsky District # 14-08 **** Verkhnekolymsky District # 14-17 **** Momsky District # 14-20 **** Nizhnekolymsky District # 14-25 **** Srednekolymsky District # # ...and parts of (65, RU-SAK) Sakhalin Oblast: # 65-11 **** Severo-Kurilsky District (North Kuril Islands) # From Tim Parenti (2014-07-02): # Oymyakonsky District of Sakha Republic (represented by Ust-Nera), along with # most of Sakhalin Oblast (represented by Sakhalin) will be moving to UTC+10 on # 2014-10-26 to stay aligned with VLAT/SAKT; however, Severo-Kurilsky District # of the Sakhalin Oblast (also known as the North Kuril Islands, represented by # Severo-Kurilsk) will remain on UTC+11. # From Tim Parenti (2014-07-06): # Assume North Kuril Islands have history like Magadan before 2011-03-27. # There is a decent chance this is wrong, in which case a new zone # Asia/Severo-Kurilsk would become necessary. # # Srednekolymsk and Zyryanka are the most populous places amongst these # districts, but have very similar populations. In fact, Wikipedia currently # lists them both as having 3528 people, exactly 1668 males and 1860 females # each! (Yikes!) # https://en.wikipedia.org/w/?title=Srednekolymsky_District&oldid=603435276 # https://en.wikipedia.org/w/?title=Verkhnekolymsky_District&oldid=594378493 # Assume this is a mistake, albeit an amusing one. # # Looking at censuses, the populations of the two municipalities seem to have # fluctuated recently. Zyryanka was more populous than Srednekolymsk in the # 1989 and 2002 censuses, but Srednekolymsk was more populous in the most # recent (2010) census, 3525 to 3170. (See pages 195 and 197 of # http://www.gks.ru/free_doc/new_site/perepis2010/croc/Documents/Vol1/pub-01-05.pdf # in Russian.) In addition, Srednekolymsk appears to be a much older # settlement and the population of Zyryanka seems to be declining. # Go with Srednekolymsk. Zone Asia/Srednekolymsk 10:14:52 - LMT 1924 May 2 10:00 - %z 1930 Jun 21 11:00 Russia %z 1991 Mar 31 2:00s 10:00 Russia %z 1992 Jan 19 2:00s 11:00 Russia %z 2011 Mar 27 2:00s 12:00 - %z 2014 Oct 26 2:00s 11:00 - %z # From Tim Parenti (2014-07-03): # Asia/Ust-Nera covers parts of (14, RU-SA) Sakha (Yakutia) Republic: # 14-22 **** Oymyakonsky District # From Arthur David Olson (2022-03-21): # Oymyakonsky and the Kuril Islands switched from # Magadan time to Vladivostok time in 2011. # # From Tim Parenti (2014-07-06), per Alexander Krivenyshev (2014-07-02): # It's unlikely that any of the Kuril Islands were involved in such a switch, # as the South and Middle Kurils have been on UTC+11 (SAKT) with the rest of # Sakhalin Oblast since at least 2011-09, and the North Kurils have been on # UTC+12 since at least then, too. Zone Asia/Ust-Nera 9:32:54 - LMT 1919 Dec 15 8:00 - %z 1930 Jun 21 9:00 Russia %z 1981 Apr 1 11:00 Russia %z 1991 Mar 31 2:00s 10:00 Russia %z 1992 Jan 19 2:00s 11:00 Russia %z 2011 Mar 27 2:00s 12:00 - %z 2011 Sep 13 0:00s # Decree 725? 11:00 - %z 2014 Oct 26 2:00s 10:00 - %z # From Tim Parenti (2014-07-03), per Oscar van Vlijmen (2001-08-25): # Asia/Kamchatka covers... # 91 RU-KAM Kamchatka Krai # # Note: Effective 2007-07-01, (41) Kamchatka Oblast and (82) Koryak # Autonomous Okrug merged to form (91, RU-KAM) Kamchatka Krai. # The Zone name should be Asia/Petropavlovsk-Kamchatski or perhaps # Asia/Petropavlovsk-Kamchatsky, but these are too long. Zone Asia/Kamchatka 10:34:36 - LMT 1922 Nov 10 11:00 - %z 1930 Jun 21 12:00 Russia %z 1991 Mar 31 2:00s 11:00 Russia %z 1992 Jan 19 2:00s 12:00 Russia %z 2010 Mar 28 2:00s 11:00 Russia %z 2011 Mar 27 2:00s 12:00 - %z # From Tim Parenti (2014-07-03): # Asia/Anadyr covers... # 87 RU-CHU Chukotka Autonomous Okrug Zone Asia/Anadyr 11:49:56 - LMT 1924 May 2 12:00 - %z 1930 Jun 21 13:00 Russia %z 1982 Apr 1 0:00s 12:00 Russia %z 1991 Mar 31 2:00s 11:00 Russia %z 1992 Jan 19 2:00s 12:00 Russia %z 2010 Mar 28 2:00s 11:00 Russia %z 2011 Mar 27 2:00s 12:00 - %z # Bosnia & Herzegovina # Croatia # Kosovo # Montenegro # North Macedonia # Serbia # Slovenia # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone Europe/Belgrade 1:22:00 - LMT 1884 1:00 - CET 1941 Apr 18 23:00 1:00 C-Eur CE%sT 1945 1:00 - CET 1945 May 8 2:00s 1:00 1:00 CEST 1945 Sep 16 2:00s # Metod Koželj reports that the legal date of # transition to EU rules was 1982-11-27, for all of Yugoslavia at the time. # Shanks & Pottenger don't give as much detail, so go with Koželj. 1:00 - CET 1982 Nov 27 1:00 EU CE%sT # Spain # # From Paul Eggert (2016-12-14): # # The source for Europe/Madrid before 2013 is: # Planesas P. La hora oficial en España y sus cambios. # Anuario del Observatorio Astronómico de Madrid (2013, in Spanish). # http://astronomia.ign.es/rknowsys-theme/images/webAstro/paginas/documentos/Anuario/lahoraoficialenespana.pdf # As this source says that historical time in the Canaries is obscure, # and it does not discuss Ceuta, stick with Shanks for now for that data. # # In the 1918 and 1919 fallback transitions in Spain, the clock for # the hour-longer day officially kept going after midnight, so that # the repeated instances of that day's 00:00 hour were 24 hours apart, # with a fallback transition from the second occurrence of 00:59... to # the next day's 00:00. Our data format cannot represent this # directly, and instead repeats the first hour of the next day, with a # fallback transition from the next day's 00:59... to 00:00. # From Michael Deckers (2016-12-15): # The Royal Decree of 1900-07-26 quoted by Planesas, online at # https://www.boe.es/datos/pdfs/BOE//1900/209/A00383-00384.pdf # says in its article 5 (my translation): # These dispositions will enter into force beginning with the # instant at which, according to the time indicated in article 1, # the 1st day of January of 1901 will begin. # Rule NAME FROM TO - IN ON AT SAVE LETTER/S Rule Spain 1918 only - Apr 15 23:00 1:00 S Rule Spain 1918 1919 - Oct 6 24:00s 0 - Rule Spain 1919 only - Apr 6 23:00 1:00 S Rule Spain 1924 only - Apr 16 23:00 1:00 S Rule Spain 1924 only - Oct 4 24:00s 0 - Rule Spain 1926 only - Apr 17 23:00 1:00 S Rule Spain 1926 1929 - Oct Sat>=1 24:00s 0 - Rule Spain 1927 only - Apr 9 23:00 1:00 S Rule Spain 1928 only - Apr 15 0:00 1:00 S Rule Spain 1929 only - Apr 20 23:00 1:00 S # Republican Spain during the civil war; it controlled Madrid until 1939-03-28. Rule Spain 1937 only - Jun 16 23:00 1:00 S Rule Spain 1937 only - Oct 2 24:00s 0 - Rule Spain 1938 only - Apr 2 23:00 1:00 S Rule Spain 1938 only - Apr 30 23:00 2:00 M Rule Spain 1938 only - Oct 2 24:00 1:00 S # The following rules are for unified Spain again. # # Planesas does not say what happened in Madrid between its fall on # 1939-03-28 and the Nationalist spring-forward transition on # 1939-04-15. For lack of better info, assume Madrid's clocks did not # change during that period. # # The first rule is commented out, as it is redundant for Republican Spain. #Rule Spain 1939 only - Apr 15 23:00 1:00 S Rule Spain 1939 only - Oct 7 24:00s 0 - Rule Spain 1942 only - May 2 23:00 1:00 S Rule Spain 1942 only - Sep 1 1:00 0 - Rule Spain 1943 1946 - Apr Sat>=13 23:00 1:00 S Rule Spain 1943 1944 - Oct Sun>=1 1:00 0 - Rule Spain 1945 1946 - Sep lastSun 1:00 0 - Rule Spain 1949 only - Apr 30 23:00 1:00 S Rule Spain 1949 only - Oct 2 1:00 0 - Rule Spain 1974 1975 - Apr Sat>=12 23:00 1:00 S Rule Spain 1974 1975 - Oct Sun>=1 1:00 0 - Rule Spain 1976 only - Mar 27 23:00 1:00 S Rule Spain 1976 1977 - Sep lastSun 1:00 0 - Rule Spain 1977 only - Apr 2 23:00 1:00 S Rule Spain 1978 only - Apr 2 2:00s 1:00 S Rule Spain 1978 only - Oct 1 2:00s 0 - # Nationalist Spain during the civil war #Rule NatSpain 1937 only - May 22 23:00 1:00 S #Rule NatSpain 1937 1938 - Oct Sat>=1 24:00s 0 - #Rule NatSpain 1938 only - Mar 26 23:00 1:00 S # The following rules are copied from Morocco from 1967 through 1978, # except with "S" letters. Rule SpainAfrica 1967 only - Jun 3 12:00 1:00 S Rule SpainAfrica 1967 only - Oct 1 0:00 0 - Rule SpainAfrica 1974 only - Jun 24 0:00 1:00 S Rule SpainAfrica 1974 only - Sep 1 0:00 0 - Rule SpainAfrica 1976 1977 - May 1 0:00 1:00 S Rule SpainAfrica 1976 only - Aug 1 0:00 0 - Rule SpainAfrica 1977 only - Sep 28 0:00 0 - Rule SpainAfrica 1978 only - Jun 1 0:00 1:00 S Rule SpainAfrica 1978 only - Aug 4 0:00 0 - # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone Europe/Madrid -0:14:44 - LMT 1901 Jan 1 0:00u 0:00 Spain WE%sT 1940 Mar 16 23:00 1:00 Spain CE%sT 1979 1:00 EU CE%sT Zone Africa/Ceuta -0:21:16 - LMT 1901 Jan 1 0:00u 0:00 - WET 1918 May 6 23:00 0:00 1:00 WEST 1918 Oct 7 23:00 0:00 - WET 1924 0:00 Spain WE%sT 1929 0:00 - WET 1967 # Help zishrink.awk. 0:00 SpainAfrica WE%sT 1984 Mar 16 1:00 - CET 1986 1:00 EU CE%sT Zone Atlantic/Canary -1:01:36 - LMT 1922 Mar # Las Palmas de Gran C. -1:00 - %z 1946 Sep 30 1:00 0:00 - WET 1980 Apr 6 0:00s 0:00 1:00 WEST 1980 Sep 28 1:00u 0:00 EU WE%sT # IATA SSIM (1996-09) says the Canaries switch at 2:00u, not 1:00u. # Ignore this for now, as the Canaries are part of the EU. # Germany (Busingen enclave) # Liechtenstein # Switzerland # # From Howse: # By the end of the 18th century clocks and watches became commonplace # and their performance improved enormously. Communities began to keep # mean time in preference to apparent time - Geneva from 1780 .... # Rule NAME FROM TO - IN ON AT SAVE LETTER/S # From Whitman (who writes "Midnight?"): # Rule Swiss 1940 only - Nov 2 0:00 1:00 S # Rule Swiss 1940 only - Dec 31 0:00 0 - # From Shanks & Pottenger: # Rule Swiss 1941 1942 - May Sun>=1 2:00 1:00 S # Rule Swiss 1941 1942 - Oct Sun>=1 0:00 0 - # From Alois Treindl (2008-12-17): # I have researched the DST usage in Switzerland during the 1940ies. # # As I wrote in an earlier message, I suspected the current tzdata values # to be wrong. This is now verified. # # I have found copies of the original ruling by the Swiss Federal # government, in 'Eidgenössische Gesetzessammlung 1941 and 1942' (Swiss # federal law collection)... # # DST began on Monday 5 May 1941, 1:00 am by shifting the clocks to 2:00 am # DST ended on Monday 6 Oct 1941, 2:00 am by shifting the clocks to 1:00 am. # # DST began on Monday, 4 May 1942 at 01:00 am # DST ended on Monday, 5 Oct 1942 at 02:00 am # # There was no DST in 1940, I have checked the law collection carefully. # It is also indicated by the fact that the 1942 entry in the law # collection points back to 1941 as a reference, but no reference to any # other years are made. # # Newspaper articles I have read in the archives on 6 May 1941 reported # about the introduction of DST (Sommerzeit in German) during the previous # night as an absolute novelty, because this was the first time that such # a thing had happened in Switzerland. # # I have also checked 1916, because one book source (Gabriel, Traité de # l'heure dans le monde) claims that Switzerland had DST in 1916. This is # false, no official document could be found. Probably Gabriel got misled # by references to Germany, which introduced DST in 1916 for the first time. # # The tzdata rules for Switzerland must be changed to: # Rule Swiss 1941 1942 - May Mon>=1 1:00 1:00 S # Rule Swiss 1941 1942 - Oct Mon>=1 2:00 0 - # # The 1940 rules must be deleted. # # One further detail for Switzerland, which is probably out of scope for # most users of tzdata: The [Europe/Zurich zone] ... # describes all of Switzerland correctly, with the exception of # the Canton de Genève (Geneva, Genf). Between 1848 and 1894 Geneva did not # follow Bern Mean Time but kept its own local mean time. # To represent this, an extra zone would be needed. # # From Alois Treindl (2013-09-11): # The Federal regulations say # https://www.admin.ch/opc/de/classified-compilation/20071096/index.html # ... the meridian for Bern mean time ... is 7° 26' 22.50". # Expressed in time, it is 0h29m45.5s. # From Pierre-Yves Berger (2013-09-11): # the "Circulaire du conseil fédéral" (December 11 1893) # http://www.amtsdruckschriften.bar.admin.ch/viewOrigDoc.do?id=10071353 # clearly states that the [1894-06-01] change should be done at midnight # but if no one is present after 11 at night, could be postponed until one # hour before the beginning of service. # From Paul Eggert (2024-05-24): # Express BMT as 0:29:45.500, approximately the same precision 7° 26' 22.50". # # We can find no reliable source for Shanks's assertion that all of Switzerland # except Geneva switched to Bern Mean Time at 00:00 on 1848-09-12. This book: # # Jakob Messerli. Gleichmässig, pünktlich, schnell. Zeiteinteilung und # Zeitgebrauch in der Schweiz im 19. Jahrhundert. Chronos, Zurich 1995, # ISBN 3-905311-68-2, OCLC 717570797. # # suggests that the transition was more gradual, and that the Swiss did not # agree about civil time during the transition. The timekeeping it gives the # most detail for is postal and telegraph time: here, federal legislation (the # "Bundesgesetz über die Erstellung von elektrischen Telegraphen") passed on # 1851-11-23, and an official implementation notice was published 1853-07-16 # (Bundesblatt 1853, Bd. II, S. 859). On p 72 Messerli writes that in # practice since July 1853 Bernese time was used in "all postal and telegraph # offices in Switzerland from Geneva to St. Gallen and Basel to Chiasso" # (Google translation). For now, model this transition as occurring on # 1853-07-16, though it probably occurred at some other date in Zurich, and # legal civil time probably changed at still some other transition date. # From Tobias Conradi (2011-09-12): # Büsingen , surrounded by the Swiss canton # Schaffhausen, did not start observing DST in 1980 as the rest of DE # (West Germany at that time) and DD (East Germany at that time) did. # DD merged into DE, the area is currently covered by code DE in ISO 3166-1, # which in turn is covered by the zone Europe/Berlin. # # Source for the time in Büsingen 1980: # http://www.srf.ch/player/video?id=c012c029-03b7-4c2b-9164-aa5902cd58d3 # # From Arthur David Olson (2012-03-03): # Büsingen and Zurich have shared clocks since 1970. # Rule NAME FROM TO - IN ON AT SAVE LETTER/S Rule Swiss 1941 1942 - May Mon>=1 1:00 1:00 S Rule Swiss 1941 1942 - Oct Mon>=1 2:00 0 - # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone Europe/Zurich 0:34:08 - LMT 1853 Jul 16 # See above comment. #STDOFF 0:29:45.500 0:29:46 - BMT 1894 Jun # Bern Mean Time 1:00 Swiss CE%sT 1981 1:00 EU CE%sT # Turkey # From Alois Treindl (2019-08-12): # http://www.astrolojidergisi.com/yazsaati.htm has researched the time zone # history of Turkey, based on newspaper archives and official documents. # From Paul Eggert (2019-08-28): # That source (Oya Vulaş, "Türkiye'de Yaz Saati Uygulamaları") # is used for 1940/1972, where it seems more reliable than our other # sources. # From Kıvanç Yazan (2019-08-12): # http://www.resmigazete.gov.tr/arsiv/14539.pdf#page=24 # 1973-06-03 01:00 -> 02:00, 1973-11-04 02:00 -> 01:00 # # http://www.resmigazete.gov.tr/arsiv/14829.pdf#page=1 # 1974-03-31 02:00 -> 03:00, 1974-11-03 02:00 -> 01:00 # # http://www.resmigazete.gov.tr/arsiv/15161.pdf#page=1 # 1975-03-22 02:00 -> 03:00, 1975-11-02 02:00 -> 01:00 # # http://www.resmigazete.gov.tr/arsiv/15535_1.pdf#page=1 # 1976-03-21 02:00 -> 03:00, 1976-10-31 02:00 -> 01:00 # # http://www.resmigazete.gov.tr/arsiv/15778.pdf#page=5 # 1977-04-03 02:00 -> 03:00, 1977-10-16 02:00 -> 01:00, # 1978-04-02 02:00 -> 03:00 (not applied, see below) # 1978-10-15 02:00 -> 01:00 (not applied, see below) # 1979-04-01 02:00 -> 03:00 (not applied, see below) # 1979-10-14 02:00 -> 01:00 (not applied, see below) # # http://www.resmigazete.gov.tr/arsiv/16245.pdf#page=17 # This cancels the previous decision, and repeats it only for 1978. # 1978-04-02 02:00 -> 03:00, 1978-10-15 02:00 -> 01:00 # (not applied due to standard TZ change below) # # http://www.resmigazete.gov.tr/arsiv/16331.pdf#page=3 # This decision changes the default longitude for Turkish time zone from 30 # degrees East to 45 degrees East. This means a standard TZ change, from +2 # to +3. This is published & applied on 1978-06-29. At that time, Turkey was # already on summer time (already on 45E). Hence, this new law just meant an # "continuous summer time". Note that this was reversed in a few years. # # http://www.resmigazete.gov.tr/arsiv/18119_1.pdf#page=1 # 1983-07-31 02:00 -> 03:00 (note that this jumps TZ to +4) # 1983-10-02 02:00 -> 01:00 (back to +3) # # http://www.resmigazete.gov.tr/arsiv/18561.pdf (page 1 and 34) # At this time, Turkey is still on +3 with no spring-forward on early # 1984. This decision is published on 10/31/1984. Page 1 declares # the decision of reverting the "default longitude change". So the # standard time should go back to +3 (30E). And page 34 explains when # that will happen: 1984-11-01 02:00 -> 01:00. You can think of this # as "end of continuous summer time, change of standard time zone". # # http://www.resmigazete.gov.tr/arsiv/18713.pdf#page=1 # 1985-04-20 01:00 -> 02:00, 1985-09-28 02:00 -> 01:00 # From Kıvanç Yazan (2016-09-25): # 1) For 1986-2006, DST started at 01:00 local and ended at 02:00 local, with # no exceptions. # 2) 1994's lastSun was overridden with Mar 20 ... # Here are official papers: # http://www.resmigazete.gov.tr/arsiv/19032.pdf#page=2 for 1986 # http://www.resmigazete.gov.tr/arsiv/19400.pdf#page=4 for 1987 # http://www.resmigazete.gov.tr/arsiv/19752.pdf#page=15 for 1988 # http://www.resmigazete.gov.tr/arsiv/20102.pdf#page=6 for 1989 # http://www.resmigazete.gov.tr/arsiv/20464.pdf#page=1 for 1990 - 1992 # http://www.resmigazete.gov.tr/arsiv/21531.pdf#page=15 for 1993 - 1995 # http://www.resmigazete.gov.tr/arsiv/21879.pdf#page=1 for overriding 1994 # http://www.resmigazete.gov.tr/arsiv/22588.pdf#page=1 for 1996, 1997 # http://www.resmigazete.gov.tr/arsiv/23286.pdf#page=10 for 1998 - 2000 # http://www.resmigazete.gov.tr/eskiler/2001/03/20010324.htm#2 - for 2001 # http://www.resmigazete.gov.tr/eskiler/2002/03/20020316.htm#2 - for 2002-2006 # From Paul Eggert (2016-09-25): # Prefer the above sources to Shanks & Pottenger for timestamps after 1985. # From Steffen Thorsen (2007-03-09): # Starting 2007 though, it seems that they are adopting EU's 1:00 UTC # start/end time, according to the following page (2007-03-07): # http://www.ntvmsnbc.com/news/402029.asp # The official document is located here - it is in Turkish...: # http://rega.basbakanlik.gov.tr/eskiler/2007/03/20070307-7.htm # I was able to locate the following seemingly official document # (on a non-government server though) describing dates between 2002 and 2006: # http://www.alomaliye.com/bkk_2002_3769.htm # From Gökdeniz Karadağ (2011-03-10): # According to the articles linked below, Turkey will change into summer # time zone (GMT+3) on March 28, 2011 at 3:00 a.m. instead of March 27. # This change is due to a nationwide exam on 27th. # https://www.worldbulletin.net/?aType=haber&ArticleID=70872 # Turkish: # https://www.hurriyet.com.tr/yaz-saati-uygulamasi-bir-gun-ileri-alindi-17230464 # From Faruk Pasin (2014-02-14): # The DST for Turkey has been changed for this year because of the # Turkish Local election.... # http://www.sabah.com.tr/Ekonomi/2014/02/12/yaz-saatinde-onemli-degisiklik # ... so Turkey will move clocks forward one hour on March 31 at 3:00 a.m. # From Randal L. Schwartz (2014-04-15): # Having landed on a flight from the states to Istanbul (via AMS) on March 31, # I can tell you that NOBODY (even the airlines) respected this timezone DST # change delay. Maybe the word just didn't get out in time. # From Paul Eggert (2014-06-15): # The press reported massive confusion, as election officials obeyed the rule # change but cell phones (and airline baggage systems) did not. See: # Kostidis M. Eventful elections in Turkey. Balkan News Agency # http://www.balkaneu.com/eventful-elections-turkey/ 2014-03-30. # I guess the best we can do is document the official time. # From Fatih (2015-09-29): # It's officially announced now by the Ministry of Energy. # Turkey delays winter time to 8th of November 04:00 # http://www.aa.com.tr/tr/turkiye/yaz-saati-uygulamasi-8-kasimda-sona-erecek/362217 # # From BBC News (2015-10-25): # Confused Turks are asking "what's the time?" after automatic clocks defied a # government decision ... "For the next two weeks #Turkey is on EEST... Erdogan # Engineered Standard Time," said Twitter user @aysekarahasan. # http://www.bbc.com/news/world-europe-34631326 # From Burak AYDIN (2016-09-08): # Turkey will stay in Daylight Saving Time even in winter.... # http://www.resmigazete.gov.tr/eskiler/2016/09/20160908-2.pdf # # From Paul Eggert (2016-09-07): # The change is permanent, so this is the new standard time in Turkey. # It takes effect today, which is not much notice. # From Kıvanç Yazan (2017-10-28): # Turkey will go back to Daylight Saving Time starting 2018-10. # http://www.resmigazete.gov.tr/eskiler/2017/10/20171028-5.pdf # # From Even Scharning (2017-11-08): # ... today it was announced that the DST will become "continuous": # http://www.hurriyet.com.tr/son-dakika-yaz-saati-uygulamasi-surekli-hale-geldi-40637482 # From Paul Eggert (2017-11-08): # Although Google Translate misfires on that source, it looks like # Turkey reversed last month's decision, and so will stay at +03. # Rule NAME FROM TO - IN ON AT SAVE LETTER/S Rule Turkey 1916 only - May 1 0:00 1:00 S Rule Turkey 1916 only - Oct 1 0:00 0 - Rule Turkey 1920 only - Mar 28 0:00 1:00 S Rule Turkey 1920 only - Oct 25 0:00 0 - Rule Turkey 1921 only - Apr 3 0:00 1:00 S Rule Turkey 1921 only - Oct 3 0:00 0 - Rule Turkey 1922 only - Mar 26 0:00 1:00 S Rule Turkey 1922 only - Oct 8 0:00 0 - # Whitman gives 1923 Apr 28 - Sep 16 and no DST in 1924-1925; # go with Shanks & Pottenger. Rule Turkey 1924 only - May 13 0:00 1:00 S Rule Turkey 1924 1925 - Oct 1 0:00 0 - Rule Turkey 1925 only - May 1 0:00 1:00 S Rule Turkey 1940 only - Jul 1 0:00 1:00 S Rule Turkey 1940 only - Oct 6 0:00 0 - Rule Turkey 1940 only - Dec 1 0:00 1:00 S Rule Turkey 1941 only - Sep 21 0:00 0 - Rule Turkey 1942 only - Apr 1 0:00 1:00 S Rule Turkey 1945 only - Oct 8 0:00 0 - Rule Turkey 1946 only - Jun 1 0:00 1:00 S Rule Turkey 1946 only - Oct 1 0:00 0 - Rule Turkey 1947 1948 - Apr Sun>=16 0:00 1:00 S Rule Turkey 1947 1951 - Oct Sun>=2 0:00 0 - Rule Turkey 1949 only - Apr 10 0:00 1:00 S Rule Turkey 1950 only - Apr 16 0:00 1:00 S Rule Turkey 1951 only - Apr 22 0:00 1:00 S # DST for 15 months; unusual but we'll let it pass. Rule Turkey 1962 only - Jul 15 0:00 1:00 S Rule Turkey 1963 only - Oct 30 0:00 0 - Rule Turkey 1964 only - May 15 0:00 1:00 S Rule Turkey 1964 only - Oct 1 0:00 0 - Rule Turkey 1973 only - Jun 3 1:00 1:00 S Rule Turkey 1973 1976 - Oct Sun>=31 2:00 0 - Rule Turkey 1974 only - Mar 31 2:00 1:00 S Rule Turkey 1975 only - Mar 22 2:00 1:00 S Rule Turkey 1976 only - Mar 21 2:00 1:00 S Rule Turkey 1977 1978 - Apr Sun>=1 2:00 1:00 S Rule Turkey 1977 1978 - Oct Sun>=15 2:00 0 - Rule Turkey 1978 only - Jun 29 0:00 0 - Rule Turkey 1983 only - Jul 31 2:00 1:00 S Rule Turkey 1983 only - Oct 2 2:00 0 - Rule Turkey 1985 only - Apr 20 1:00s 1:00 S Rule Turkey 1985 only - Sep 28 1:00s 0 - Rule Turkey 1986 1993 - Mar lastSun 1:00s 1:00 S Rule Turkey 1986 1995 - Sep lastSun 1:00s 0 - Rule Turkey 1994 only - Mar 20 1:00s 1:00 S Rule Turkey 1995 2006 - Mar lastSun 1:00s 1:00 S Rule Turkey 1996 2006 - Oct lastSun 1:00s 0 - # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone Europe/Istanbul 1:55:52 - LMT 1880 1:56:56 - IMT 1910 Oct # Istanbul Mean Time? 2:00 Turkey EE%sT 1978 Jun 29 3:00 Turkey %z 1984 Nov 1 2:00 2:00 Turkey EE%sT 2007 2:00 EU EE%sT 2011 Mar 27 1:00u 2:00 - EET 2011 Mar 28 1:00u 2:00 EU EE%sT 2014 Mar 30 1:00u 2:00 - EET 2014 Mar 31 1:00u 2:00 EU EE%sT 2015 Oct 25 1:00u 2:00 1:00 EEST 2015 Nov 8 1:00u 2:00 EU EE%sT 2016 Sep 7 3:00 - %z # Ukraine # # From Alois Treindl (2014-03-01): # REGULATION A N O V A on March 20, 1992 N 139 ... means that from # 1992 on, Ukraine had DST with begin time at 02:00 am, on last Sunday # in March, and end time 03:00 am, last Sunday in September.... # CABINET OF MINISTERS OF UKRAINE RESOLUTION on May 13, 1996 N 509 # "On the order of computation time on the territory of Ukraine" .... # As this cabinet decision is from May 1996, it seems likely that the # transition in March 1996, which predates it, was still at 2:00 am # and not at 3:00 as would have been under EU rules. # This is why I have set the change to EU rules into May 1996, # so that the change in March is stil covered by the Ukraine rule. # The next change in October 1996 happened under EU rules. # # From Paul Eggert (2022-08-27): # For now, assume that Ukraine's zones all followed the same rules, # except that Crimea switched to Moscow time in 1994 as described elsewhere. # From Igor Karpov, who works for the Ukrainian Ministry of Justice, # via Garrett Wollman (2003-01-27): # BTW, I've found the official document on this matter. It's government # regulations No. 509, May 13, 1996. In my poor translation it says: # "Time in Ukraine is set to second timezone (Kiev time). Each last Sunday # of March at 3am the time is changing to 4am and each last Sunday of # October the time at 4am is changing to 3am" # From Alexander Krivenyshev (2011-09-20): # On September 20, 2011 the deputies of the Verkhovna Rada agreed to # abolish the transfer clock to winter time. # # Bill No. 8330 of MP from the Party of Regions Oleg Nadoshi got # approval from 266 deputies. # # Ukraine abolishes transfer back to the winter time (in Russian) # http://news.mail.ru/politics/6861560/ # # The Ukrainians will no longer change the clock (in Russian) # http://www.segodnya.ua/news/14290482.html # # Deputies cancelled the winter time (in Russian) # https://www.pravda.com.ua/rus/news/2011/09/20/6600616/ # # From Philip Pizzey (2011-10-18): # Today my Ukrainian colleagues have informed me that the # Ukrainian parliament have decided that they will go to winter # time this year after all. # # From Udo Schwedt (2011-10-18): # As far as I understand, the recent change to the Ukrainian time zone # (Europe/Kiev) to introduce permanent daylight saving time (similar # to Russia) was reverted today: # http://portal.rada.gov.ua/rada/control/en/publish/article/info_left?art_id=287324&cat_id=105995 # # Also reported by Alexander Bokovoy (2011-10-18) who also noted: # The law documents themselves are at # http://w1.c1.rada.gov.ua/pls/zweb_n/webproc4_1?id=&pf3511=41484 # From Vladimir in Moscow via Alois Treindl re Kyiv time 1991/2 (2014-02-28): # First in Ukraine they changed Time zone from UTC+3 to UTC+2 with DST: # 03 25 1990 02:00 -03.00 1 Time Zone 3 with DST # 07 01 1990 02:00 -02.00 1 Time Zone 2 with DST # * Ukrainian Government's Resolution of 18.06.1990, No. 134. # http://search.ligazakon.ua/l_doc2.nsf/link1/T001500.html # # They did not end DST in September, 1990 (according to the law, # "summer time" was still in action): # 09 30 1990 03:00 -02.00 1 Time Zone 2 with DST # * Ukrainian Government's Resolution of 21.09.1990, No. 272. # http://search.ligazakon.ua/l_doc2.nsf/link1/KP900272.html # # Again no change in March, 1991 ("summer time" in action): # 03 31 1991 02:00 -02.00 1 Time Zone 2 with DST # # DST ended in September 1991 ("summer time" ended): # 09 29 1991 03:00 -02.00 0 Time Zone 2, no DST # * Ukrainian Government's Resolution of 25.09.1991, No. 225. # http://www.uazakon.com/documents/date_21/pg_iwgdoc.htm # This is an answer. # # Since 1992 they had normal DST procedure: # 03 29 1992 02:00 -02.00 1 DST started # 09 27 1992 03:00 -02.00 0 DST ended # * Ukrainian Government's Resolution of 20.03.1992, No. 139. # http://www.uazakon.com/documents/date_8u/pg_grcasa.htm # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone Europe/Kyiv 2:02:04 - LMT 1880 2:02:04 - KMT 1924 May 2 # Kyiv Mean Time 2:00 - EET 1930 Jun 21 3:00 - MSK 1941 Sep 20 1:00 C-Eur CE%sT 1943 Nov 6 3:00 Russia MSK/MSD 1990 Jul 1 2:00 2:00 1:00 EEST 1991 Sep 29 3:00 2:00 C-Eur EE%sT 1996 May 13 2:00 EU EE%sT ############################################################################### # One source shows that Bulgaria, Cyprus, Finland, and Greece observe DST from # the last Sunday in March to the last Sunday in September in 1986. # The source shows Romania changing a day later than everybody else. # # According to Bernard Sieloff's source, Poland is in the MET time zone but # uses the WE DST rules. The Western USSR uses EET+1 and ME DST rules. # Bernard Sieloff's source claims Romania switches on the same day, but at # 00:00 standard time (i.e., 01:00 DST). It also claims that Turkey # switches on the same day, but switches on at 01:00 standard time # and off at 00:00 standard time (i.e., 01:00 DST) # ... # Date: Wed, 28 Jan 87 16:56:27 -0100 # From: Tom Hofmann # ... # # ...the European time rules are...standardized since 1981, when # most European countries started DST. Before that year, only # a few countries (UK, France, Italy) had DST, each according # to own national rules. In 1981, however, DST started on # 'Apr firstSun', and not on 'Mar lastSun' as in the following # years... # But also since 1981 there are some more national exceptions # than listed in 'europe': Switzerland, for example, joined DST # one year later, Denmark ended DST on 'Oct 1' instead of 'Sep # lastSun' in 1981 - I don't know how they handle now. # # Finally, DST ist always from 'Apr 1' to 'Oct 1' in the # Soviet Union (as far as I know). # # Tom Hofmann, Scientific Computer Center, CIBA-GEIGY AG, # 4002 Basle, Switzerland # ... # ... # Date: Wed, 4 Feb 87 22:35:22 +0100 # From: Dik T. Winter # ... # # The information from Tom Hofmann is (as far as I know) not entirely correct. # After a request from chongo at amdahl I tried to retrieve all information # about DST in Europe. I was able to find all from about 1969. # # ...standardization on DST in Europe started in about 1977 with switches on # first Sunday in April and last Sunday in September... # In 1981 UK joined Europe insofar that # the starting day for both shifted to last Sunday in March. And from 1982 # the whole of Europe used DST, with switch dates April 1 and October 1 in # the Sov[i]et Union. In 1985 the SU reverted to standard Europe[a]n switch # dates... # # It should also be remembered that time-zones are not constants; e.g. # Portugal switched in 1976 from MET (or CET) to WET with DST... # Note also that though there were rules for switch dates not # all countries abided to these dates, and many individual deviations # occurred, though not since 1982 I believe. Another note: it is always # assumed that DST is 1 hour ahead of normal time, this need not be the # case; at least in the Netherlands there have been times when DST was 2 hours # in advance of normal time. # # ... # dik t. winter, cwi, amsterdam, nederland # ... # From Bob Devine (1988-01-28): # ... # Greece: Last Sunday in April to last Sunday in September (iffy on dates). # Since 1978. Change at midnight. # ... # Monaco: has same DST as France. # ... pr0m1th3as-datatypes-9c9a8d3/inst/tzdata/factory000066400000000000000000000017351522766574100217210ustar00rootroot00000000000000# tzdb data for noncommittal factory settings # This file is in the public domain, so clarified as of # 2009-05-17 by Arthur David Olson. # For distributors who don't want to specify a timezone in their # installation procedures. Users who run 'date' will get the # time zone abbreviation "-00", indicating that the actual time zone # is unknown. # TZ="Factory" was added to TZDB in 1989, and in 2016 its abbreviation # was changed to "-00" from a longish English-language error message. # Around 2010, CLDR added "Etc/Unknown" for use with TZDB, to stand # for an unknown or invalid time zone. These two notions differ: # TZ="Factory" is a valid timezone, so tzalloc("Factory") succeeds, whereas # TZ="Etc/Unknown" is invalid and tzalloc("Etc/Unknown") fails. # Also, a downstream distributor could modify Factory to be a # default timezone suitable for the devices it manufactures, # whereas that cannot happen for Etc/Unknown. # Zone NAME STDOFF RULES FORMAT Zone Factory 0 - -00 pr0m1th3as-datatypes-9c9a8d3/inst/tzdata/iso3166.tab000066400000000000000000000113511522766574100221240ustar00rootroot00000000000000# ISO 3166 alpha-2 country codes # # This file is in the public domain, so clarified as of # 2009-05-17 by Arthur David Olson. # # From Paul Eggert (2025-07-01): # This file contains a table of two-letter country codes. Columns are # separated by a single tab. Lines beginning with ‘#’ are comments. # All text uses UTF-8 encoding. The columns of the table are as follows: # # 1. ISO 3166-1 alpha-2 country code, current as of # ISO/TC 46 N1127 (2024-02-29). See: ISO/TC 46 Documents # https://www.iso.org/committee/48750.html?view=documents # 2. The usual English name for the coded region. This sometimes # departs from ISO-listed names, sometimes so that sorted subsets # of names are useful (e.g., “Samoa (American)” and “Samoa # (western)” rather than “American Samoa” and “Samoa”), # sometimes to avoid confusion among non-experts (e.g., # “Czech Republic” and “Turkey” rather than “Czechia” and “Türkiye”), # and sometimes to omit needless detail or churn (e.g., “Netherlands” # rather than “Netherlands (the)” or “Netherlands (Kingdom of the)”). # # The table is sorted by country code. # # This table is intended as an aid for users, to help them select time # zone data appropriate for their practical needs. It is not intended # to take or endorse any position on legal or territorial claims. # #country- #code name of country, territory, area, or subdivision AD Andorra AE United Arab Emirates AF Afghanistan AG Antigua & Barbuda AI Anguilla AL Albania AM Armenia AO Angola AQ Antarctica AR Argentina AS Samoa (American) AT Austria AU Australia AW Aruba AX Åland Islands AZ Azerbaijan BA Bosnia & Herzegovina BB Barbados BD Bangladesh BE Belgium BF Burkina Faso BG Bulgaria BH Bahrain BI Burundi BJ Benin BL St Barthelemy BM Bermuda BN Brunei BO Bolivia BQ Caribbean NL BR Brazil BS Bahamas BT Bhutan BV Bouvet Island BW Botswana BY Belarus BZ Belize CA Canada CC Cocos (Keeling) Islands CD Congo (Dem. Rep.) CF Central African Rep. CG Congo (Rep.) CH Switzerland CI Côte d’Ivoire CK Cook Islands CL Chile CM Cameroon CN China CO Colombia CR Costa Rica CU Cuba CV Cape Verde CW Curaçao CX Christmas Island CY Cyprus CZ Czech Republic DE Germany DJ Djibouti DK Denmark DM Dominica DO Dominican Republic DZ Algeria EC Ecuador EE Estonia EG Egypt EH Western Sahara ER Eritrea ES Spain ET Ethiopia FI Finland FJ Fiji FK Falkland Islands FM Micronesia FO Faroe Islands FR France GA Gabon GB Britain (UK) GD Grenada GE Georgia GF French Guiana GG Guernsey GH Ghana GI Gibraltar GL Greenland GM Gambia GN Guinea GP Guadeloupe GQ Equatorial Guinea GR Greece GS South Georgia & the South Sandwich Islands GT Guatemala GU Guam GW Guinea-Bissau GY Guyana HK Hong Kong HM Heard Island & McDonald Islands HN Honduras HR Croatia HT Haiti HU Hungary ID Indonesia IE Ireland IL Israel IM Isle of Man IN India IO British Indian Ocean Territory IQ Iraq IR Iran IS Iceland IT Italy JE Jersey JM Jamaica JO Jordan JP Japan KE Kenya KG Kyrgyzstan KH Cambodia KI Kiribati KM Comoros KN St Kitts & Nevis KP Korea (North) KR Korea (South) KW Kuwait KY Cayman Islands KZ Kazakhstan LA Laos LB Lebanon LC St Lucia LI Liechtenstein LK Sri Lanka LR Liberia LS Lesotho LT Lithuania LU Luxembourg LV Latvia LY Libya MA Morocco MC Monaco MD Moldova ME Montenegro MF St Martin (French) MG Madagascar MH Marshall Islands MK North Macedonia ML Mali MM Myanmar (Burma) MN Mongolia MO Macau MP Northern Mariana Islands MQ Martinique MR Mauritania MS Montserrat MT Malta MU Mauritius MV Maldives MW Malawi MX Mexico MY Malaysia MZ Mozambique NA Namibia NC New Caledonia NE Niger NF Norfolk Island NG Nigeria NI Nicaragua NL Netherlands NO Norway NP Nepal NR Nauru NU Niue NZ New Zealand OM Oman PA Panama PE Peru PF French Polynesia PG Papua New Guinea PH Philippines PK Pakistan PL Poland PM St Pierre & Miquelon PN Pitcairn PR Puerto Rico PS Palestine PT Portugal PW Palau PY Paraguay QA Qatar RE Réunion RO Romania RS Serbia RU Russia RW Rwanda SA Saudi Arabia SB Solomon Islands SC Seychelles SD Sudan SE Sweden SG Singapore SH St Helena SI Slovenia SJ Svalbard & Jan Mayen SK Slovakia SL Sierra Leone SM San Marino SN Senegal SO Somalia SR Suriname SS South Sudan ST Sao Tome & Principe SV El Salvador SX St Maarten (Dutch) SY Syria SZ Eswatini (Swaziland) TC Turks & Caicos Is TD Chad TF French S. Terr. TG Togo TH Thailand TJ Tajikistan TK Tokelau TL East Timor TM Turkmenistan TN Tunisia TO Tonga TR Turkey TT Trinidad & Tobago TV Tuvalu TW Taiwan TZ Tanzania UA Ukraine UG Uganda UM US minor outlying islands US United States UY Uruguay UZ Uzbekistan VA Vatican City VC St Vincent VE Venezuela VG Virgin Islands (UK) VI Virgin Islands (US) VN Vietnam VU Vanuatu WF Wallis & Futuna WS Samoa (western) YE Yemen YT Mayotte ZA South Africa ZM Zambia ZW Zimbabwe pr0m1th3as-datatypes-9c9a8d3/inst/tzdata/leap-seconds.list000066400000000000000000000117111522766574100235740ustar00rootroot00000000000000# ATOMIC TIME # Coordinated Universal Time (UTC) is the reference time scale derived # from The "Temps Atomique International" (TAI) calculated by the Bureau # International des Poids et Mesures (BIPM) using a worldwide network of atomic # clocks. UTC differs from TAI by an integer number of seconds; it is the basis # of all activities in the world. # # # ASTRONOMICAL TIME (UT1) is the time scale based on the rate of rotation of the earth. # It is now mainly derived from Very Long Baseline Interferometry (VLBI). The various # irregular fluctuations progressively detected in the rotation rate of the Earth led # in 1972 to the replacement of UT1 by UTC as the reference time scale. # # # LEAP SECOND # Atomic clocks are more stable than the rate of the earth's rotation since the latter # undergoes a full range of geophysical perturbations at various time scales: lunisolar # and core-mantle torques, atmospheric and oceanic effects, etc. # Leap seconds are needed to keep the two time scales in agreement, i.e. UT1-UTC smaller # than 0.9 seconds. Therefore, when necessary a "leap second" is applied to UTC. # Since the adoption of this system in 1972 it has been necessary to add a number of seconds to UTC, # firstly due to the initial choice of the value of the second (1/86400 mean solar day of # the year 1820) and secondly to the general slowing down of the Earth's rotation. It is # theoretically possible to have a negative leap second (a second removed from UTC), but so far, # all leap seconds have been positive (a second has been added to UTC). Based on what we know about # the earth's rotation, it is unlikely that we will ever have a negative leap second. # # # HISTORY # The first leap second was added on June 30, 1972. Until the year 2000, it was necessary in average to add a # leap second at a rate of 1 to 2 years. Since the year 2000 leap seconds are introduced with an # average interval of 3 to 4 years due to the acceleration of the Earth's rotation speed. # # # RESPONSIBILITY OF THE DECISION TO INTRODUCE A LEAP SECOND IN UTC # The decision to introduce a leap second in UTC is the responsibility of the Earth Orientation Center of # the International Earth Rotation and reference System Service (IERS). This center is located at Paris # Observatory. According to international agreements, leap seconds should be scheduled only for certain dates: # first preference is given to the end of December and June, and second preference at the end of March # and September. Since the introduction of leap seconds in 1972, only dates in June and December were used. # # Questions or comments to: # Christian Bizouard: christian.bizouard@obspm.fr # Earth orientation Center of the IERS # Paris Observatory, France # # # # COPYRIGHT STATUS OF THIS FILE # This file is in the public domain. # # # VALIDITY OF THE FILE # It is important to express the validity of the file. These next two dates are # given in units of seconds since 1900.0. # # 1) Last update of the file. # # Updated through IERS Bulletin C (https://hpiers.obspm.fr/iers/bul/bulc/bulletinc.dat) # # The following line shows the last update of this file in NTP timestamp: # #$ 3992312697 # # 2) Expiration date of the file given on a semi-annual basis: last June or last December # # File expires on 28 June 2027 # # Expire date in NTP timestamp: # #@ 4023129600 # # # LIST OF LEAP SECONDS # NTP timestamp (X parameter) is the number of seconds since 1900.0 # # MJD: The Modified Julian Day number. MJD = X/86400 + 15020 # # DTAI: The difference DTAI= TAI-UTC in units of seconds # It is the quantity to add to UTC to get the time in TAI # # Day Month Year : epoch in clear # #NTP Time DTAI Day Month Year # 2272060800 10 # 1 Jan 1972 2287785600 11 # 1 Jul 1972 2303683200 12 # 1 Jan 1973 2335219200 13 # 1 Jan 1974 2366755200 14 # 1 Jan 1975 2398291200 15 # 1 Jan 1976 2429913600 16 # 1 Jan 1977 2461449600 17 # 1 Jan 1978 2492985600 18 # 1 Jan 1979 2524521600 19 # 1 Jan 1980 2571782400 20 # 1 Jul 1981 2603318400 21 # 1 Jul 1982 2634854400 22 # 1 Jul 1983 2698012800 23 # 1 Jul 1985 2776982400 24 # 1 Jan 1988 2840140800 25 # 1 Jan 1990 2871676800 26 # 1 Jan 1991 2918937600 27 # 1 Jul 1992 2950473600 28 # 1 Jul 1993 2982009600 29 # 1 Jul 1994 3029443200 30 # 1 Jan 1996 3076704000 31 # 1 Jul 1997 3124137600 32 # 1 Jan 1999 3345062400 33 # 1 Jan 2006 3439756800 34 # 1 Jan 2009 3550089600 35 # 1 Jul 2012 3644697600 36 # 1 Jul 2015 3692217600 37 # 1 Jan 2017 # # A hash code has been generated to be able to verify the integrity # of this file. For more information about using this hash code, # please see the readme file in the 'source' directory : # https://hpiers.obspm.fr/iers/bul/bulc/ntp/sources/README # #h a9bad145 84c31c70 758402aa b37bfd54 5923836a pr0m1th3as-datatypes-9c9a8d3/inst/tzdata/leapseconds000066400000000000000000000071561522766574100225550ustar00rootroot00000000000000# Allowance for leap seconds added to each time zone file. # This file is in the public domain. # This file is generated automatically from the data in the public-domain # NIST/IERS format leap-seconds.list file, which can be copied from # # or via a less-secure protocol and with different comments and # less volatile last-modified and expiration timestamps, from # . # For more about leap-seconds.list, please see # The NTP Timescale and Leap Seconds # . # The rules for leap seconds are specified in Annex 1 (Time scales) of: # Standard-frequency and time-signal emissions. # International Telecommunication Union - Radiocommunication Sector # (ITU-R) Recommendation TF.460-6 (02/2002) # . # The International Earth Rotation and Reference Systems Service (IERS) # periodically uses leap seconds to keep UTC to within 0.9 s of UT1 # (a proxy for Earth's angle in space as measured by astronomers) # and publishes leap second data in a copyrighted file # . # See: Levine J. Coordinated Universal Time and the leap second. # URSI Radio Sci Bull. 2016;89(4):30-6. doi:10.23919/URSIRSB.2016.7909995 # . # There were no leap seconds before 1972, as no official mechanism # accounted for the discrepancy between atomic time (TAI) and the earth's # rotation. The first ("1 Jan 1972") data line in leap-seconds.list # does not denote a leap second; it denotes the start of the current definition # of UTC. # All leap-seconds are Stationary (S) at the given UTC time. # The correction (+ or -) is made at the given time, so in the unlikely # event of a negative leap second, a line would look like this: # Leap YEAR MON DAY 23:59:59 - S # Typical lines look like this: # Leap YEAR MON DAY 23:59:60 + S Leap 1972 Jun 30 23:59:60 + S Leap 1972 Dec 31 23:59:60 + S Leap 1973 Dec 31 23:59:60 + S Leap 1974 Dec 31 23:59:60 + S Leap 1975 Dec 31 23:59:60 + S Leap 1976 Dec 31 23:59:60 + S Leap 1977 Dec 31 23:59:60 + S Leap 1978 Dec 31 23:59:60 + S Leap 1979 Dec 31 23:59:60 + S Leap 1981 Jun 30 23:59:60 + S Leap 1982 Jun 30 23:59:60 + S Leap 1983 Jun 30 23:59:60 + S Leap 1985 Jun 30 23:59:60 + S Leap 1987 Dec 31 23:59:60 + S Leap 1989 Dec 31 23:59:60 + S Leap 1990 Dec 31 23:59:60 + S Leap 1992 Jun 30 23:59:60 + S Leap 1993 Jun 30 23:59:60 + S Leap 1994 Jun 30 23:59:60 + S Leap 1995 Dec 31 23:59:60 + S Leap 1997 Jun 30 23:59:60 + S Leap 1998 Dec 31 23:59:60 + S Leap 2005 Dec 31 23:59:60 + S Leap 2008 Dec 31 23:59:60 + S Leap 2012 Jun 30 23:59:60 + S Leap 2015 Jun 30 23:59:60 + S Leap 2016 Dec 31 23:59:60 + S # UTC timestamp when this leap second list expires. # Any additional leap seconds will come after this. # This Expires line is commented out for now, # so that pre-2020a zic implementations do not reject this file. #Expires 2027 Jun 28 00:00:00 # Here are POSIX timestamps for the data in this file. # "#updated" gives the last time the leap seconds data changed # or, if this file was derived from the IERS leap-seconds.list, # the last time that file changed in any way. # "#expires" gives the first time this file might be wrong; # if this file was derived from the IERS leap-seconds.list, # this is typically a bit less than one year after "updated". #updated 1783323897 (2026-07-06 07:44:57 UTC) #expires 1814140800 (2027-06-28 00:00:00 UTC) # Updated through IERS Bulletin C (https://hpiers.obspm.fr/iers/bul/bulc/bulletinc.dat) # File expires on 28 June 2027 pr0m1th3as-datatypes-9c9a8d3/inst/tzdata/northamerica000066400000000000000000005316751522766574100227410ustar00rootroot00000000000000# tzdb data for North and Central America and environs # This file is in the public domain, so clarified as of # 2009-05-17 by Arthur David Olson. # also includes Central America and the Caribbean # This file is by no means authoritative; if you think you know better, # go ahead and edit the file, and please send any changes to # the public mailing list tz@iana.org for general use in the future. # For more, please see the file CONTRIBUTING in the tz distribution. # From Paul Eggert (1999-03-22): # A reliable and entertaining source about time zones is # Derek Howse, Greenwich time and longitude, Philip Wilson Publishers (1997). ############################################################################### # United States # From Paul Eggert (1999-03-31): # Howse writes (pp 121-125) that time zones were invented by # Professor Charles Ferdinand Dowd (1825-1904), # Principal of Temple Grove Ladies' Seminary (Saratoga Springs, NY). # His pamphlet "A System of National Time for Railroads" (1870) # was the result of his proposals at the Convention of Railroad Trunk Lines # in New York City (1869-10). His 1870 proposal was based on Washington, DC, # but in 1872-05 he moved the proposed origin to Greenwich. # From Paul Eggert (2024-11-18): # Dowd's proposal left many details unresolved, such as where to draw # lines between time zones. Sandford Fleming of the Canadian Pacific Railway # argued for Dowd's proposal in 1876, and Cleveland Abbe of the American # Meteorology Society published a report in 1879 recommending four US time # zones based on GMT. However, the key individual who made time zones # work in the US was William Frederick Allen - railway engineer, # managing editor of the Travelers' Guide, and secretary of the # General Time Convention, a railway standardization group. Allen # spent months in dialogs with scientific and railway leaders, # developed a workable plan to institute time zones, and presented it # to the General Time Convention on 1883-04-11, saying that his plan # meant "local time would be practically abolished" - a plus for # railway scheduling. By the next convention on 1883-10-11 nearly all # railroads had agreed and it took effect on 1883-11-18. That Sunday # was called the "day of two noons", as some locations observed noon # twice. Allen witnessed the transition in New York City, writing: # # I heard the bells of St. Paul's strike on the old time. Four # minutes later, obedient to the electrical signal from the Naval # Observatory ... the time-ball made its rapid descent, the chimes # of old Trinity rang twelve measured strokes, and local time was # abandoned, probably forever. # # Most of the US soon followed suit. See: # Bartky IR. The adoption of standard time. Technol Cult 1989 Jan;30(1):25-56. # https://dx.doi.org/10.2307/3105430 # From Paul Eggert (2005-04-16): # That 1883 transition occurred at 12:00 new time, not at 12:00 old time. # See p 46 of David Prerau, Seize the daylight, Thunder's Mouth Press (2005). # From Paul Eggert (2006-03-22): # A good source for time zone historical data in the US is # Thomas G. Shanks, The American Atlas (5th edition), # San Diego: ACS Publications, Inc. (1991). # Make sure you have the errata sheet; the book is somewhat useless without it. # It is the source for most of the pre-1991 US entries below. # From Paul Eggert (2001-03-06): # Daylight Saving Time was first suggested as a joke by Benjamin Franklin # in his whimsical essay "An Economical Project for Diminishing the Cost # of Light" published in the Journal de Paris (1784-04-26). # Not everyone is happy with the results: # # I don't really care how time is reckoned so long as there is some # agreement about it, but I object to being told that I am saving # daylight when my reason tells me that I am doing nothing of the kind. # I even object to the implication that I am wasting something # valuable if I stay in bed after the sun has risen. As an admirer # of moonlight I resent the bossy insistence of those who want to # reduce my time for enjoying it. At the back of the Daylight Saving # scheme I detect the bony, blue-fingered hand of Puritanism, eager # to push people into bed earlier, and get them up earlier, to make # them healthy, wealthy and wise in spite of themselves. # # -- Robertson Davies, The diary of Samuel Marchbanks, # Clarke, Irwin (1947), XIX, Sunday # # For more about the first ten years of DST in the United States, see # Robert Garland, Ten years of daylight saving from the Pittsburgh standpoint # (Carnegie Library of Pittsburgh, 1927). # https://web.archive.org/web/20160517155308/http://www.clpgh.org/exhibit/dst.html # # Shanks says that DST was called "War Time" in the US in 1918 and 1919. # However, DST was imposed by the Standard Time Act of 1918, which # was the first nationwide legal time standard, and apparently # time was just called "Standard Time" or "Daylight Saving Time". # From Paul Eggert (2019-06-04): # Here is the legal basis for the US federal rules. # * Public Law 65-106 (1918-03-19) implemented standard and daylight saving # time for the first time across the US, springing forward on March's last # Sunday and falling back on October's last Sunday. # https://www.loc.gov/law/help/statutes-at-large/65th-congress/session-2/c65s2ch24.pdf # * Public Law 66-40 (1919-08-20) repealed DST on October 1919's last Sunday. # https://www.loc.gov/law/help/statutes-at-large/66th-congress/session-1/c66s1ch51.pdf # * Public Law 77-403 (1942-01-20) started wartime DST on 1942-02-09. # https://www.loc.gov/law/help/statutes-at-large/77th-congress/session-2/c77s2ch7.pdf # * Public Law 79-187 (1945-09-25) ended wartime DST on 1945-09-30. # https://www.loc.gov/law/help/statutes-at-large/79th-congress/session-1/c79s1ch388.pdf # * Public Law 89-387 (1966-04-13) reinstituted a national standard for DST, # from April's last Sunday to October's last Sunday, effective 1967. # https://www.govinfo.gov/content/pkg/STATUTE-80/pdf/STATUTE-80-Pg107.pdf # * Public Law 93-182 (1973-12-15) moved the 1974 spring-forward to 01-06. # https://www.govinfo.gov/content/pkg/STATUTE-87/pdf/STATUTE-87-Pg707.pdf # * Public Law 93-434 (1974-10-05) moved the 1975 spring-forward to # February's last Sunday. # https://www.govinfo.gov/content/pkg/STATUTE-88/pdf/STATUTE-88-Pg1209.pdf # * Public Law 99-359 (1986-07-08) moved the spring-forward to April's first # Sunday. # https://www.govinfo.gov/content/pkg/STATUTE-100/pdf/STATUTE-100-Pg764.pdf # * Public Law 109-58 (2005-08-08), effective 2007, moved the spring-forward # to March's second Sunday and the fall-back to November's first Sunday. # https://www.govinfo.gov/content/pkg/PLAW-109publ58/pdf/PLAW-109publ58.pdf # All transitions are at 02:00 local time. # From Arthur David Olson: # Before the Uniform Time Act of 1966 took effect in 1967, observance of # Daylight Saving Time in the US was by local option, except during wartime. # From Arthur David Olson (2000-09-25): # Last night I heard part of a rebroadcast of a 1945 Arch Oboler radio drama. # In the introduction, Oboler spoke of "Eastern Peace Time." # An AltaVista search turned up: # https://web.archive.org/web/20000926032210/http://rowayton.org/rhs/hstaug45.html # "When the time is announced over the radio now, it is 'Eastern Peace # Time' instead of the old familiar 'Eastern War Time.' Peace is wonderful." # (August 1945) by way of confirmation. # # From Paul Eggert (2017-09-23): # This was the V-J Day issue of the Clamdigger, a Rowayton, CT newsletter. # From Joseph Gallant citing # George H. Douglas, _The Early Days of Radio Broadcasting_ (1987): # At 7 P.M. (Eastern War Time) [on 1945-08-14], the networks were set # to switch to London for Attlee's address, but the American people # never got to hear his speech live. According to one press account, # CBS' Bob Trout was first to announce the word of Japan's surrender, # but a few seconds later, NBC, ABC and Mutual also flashed the word # of surrender, all of whom interrupting the bells of Big Ben in # London which were to precede Mr. Attlee's speech. # From Paul Eggert (2003-02-09): It was Robert St John, not Bob Trout. From # Myrna Oliver's obituary of St John on page B16 of today's Los Angeles Times: # # ... a war-weary U.S. clung to radios, awaiting word of Japan's surrender. # Any announcement from Asia would reach St. John's New York newsroom on a # wire service teletype machine, which had prescribed signals for major news. # Associated Press, for example, would ring five bells before spewing out # typed copy of an important story, and 10 bells for news "of transcendental # importance." # # On Aug. 14, stalling while talking steadily into the NBC networks' open # microphone, St. John heard five bells and waited only to hear a sixth bell, # before announcing confidently: "Ladies and gentlemen, World War II is over. # The Japanese have agreed to our surrender terms." # # He had scored a 20-second scoop on other broadcasters. # From Arthur David Olson (2005-08-22): # Paul has been careful to use the "US" rules only in those locations # that are part of the United States; this reflects the real scope of # U.S. government action. So even though the "US" rules have changed # in the latest release, other countries won't be affected. # Rule NAME FROM TO - IN ON AT SAVE LETTER/S Rule US 1918 1919 - Mar lastSun 2:00 1:00 D Rule US 1918 1919 - Oct lastSun 2:00 0 S Rule US 1942 only - Feb 9 2:00 1:00 W # War Rule US 1945 only - Aug 14 23:00u 1:00 P # Peace Rule US 1945 only - Sep 30 2:00 0 S Rule US 1967 2006 - Oct lastSun 2:00 0 S Rule US 1967 1973 - Apr lastSun 2:00 1:00 D Rule US 1974 only - Jan 6 2:00 1:00 D Rule US 1975 only - Feb lastSun 2:00 1:00 D Rule US 1976 1986 - Apr lastSun 2:00 1:00 D Rule US 1987 2006 - Apr Sun>=1 2:00 1:00 D Rule US 2007 max - Mar Sun>=8 2:00 1:00 D Rule US 2007 max - Nov Sun>=1 2:00 0 S # From U. S. Naval Observatory (1989-01-19): # USA EASTERN 5 H BEHIND UTC NEW YORK, WASHINGTON # USA EASTERN 4 H BEHIND UTC APR 3 - OCT 30 # USA CENTRAL 6 H BEHIND UTC CHICAGO, HOUSTON # USA CENTRAL 5 H BEHIND UTC APR 3 - OCT 30 # USA MOUNTAIN 7 H BEHIND UTC DENVER # USA MOUNTAIN 6 H BEHIND UTC APR 3 - OCT 30 # USA PACIFIC 8 H BEHIND UTC L.A., SAN FRANCISCO # USA PACIFIC 7 H BEHIND UTC APR 3 - OCT 30 # USA ALASKA STD 9 H BEHIND UTC MOST OF ALASKA (AKST) # USA ALASKA STD 8 H BEHIND UTC APR 3 - OCT 30 (AKDT) # USA ALEUTIAN 10 H BEHIND UTC ISLANDS WEST OF 170W # USA " 9 H BEHIND UTC APR 3 - OCT 30 # USA HAWAII 10 H BEHIND UTC # USA BERING 11 H BEHIND UTC SAMOA, MIDWAY # From Arthur David Olson (1989-01-21): # The above dates are for 1988. # Note the "AKST" and "AKDT" abbreviations, the claim that there's # no DST in Samoa, and the claim that there is DST in Alaska and the # Aleutians. # From Arthur David Olson (1988-02-13): # Legal standard time zone names, from United States Code (1982 Edition and # Supplement III), Title 15, Chapter 6, Section 260 and forward. First, names # up to 1967-04-01 (when most provisions of the Uniform Time Act of 1966 # took effect), as explained in sections 263 and 261: # (none) # United States standard eastern time # United States standard mountain time # United States standard central time # United States standard Pacific time # (none) # United States standard Alaska time # (none) # Next, names from 1967-04-01 until 1983-11-30 (the date for # public law 98-181): # Atlantic standard time # eastern standard time # central standard time # mountain standard time # Pacific standard time # Yukon standard time # Alaska-Hawaii standard time # Bering standard time # And after 1983-11-30: # Atlantic standard time # eastern standard time # central standard time # mountain standard time # Pacific standard time # Alaska standard time # Hawaii-Aleutian standard time # Samoa standard time # The law doesn't give abbreviations. # # From Paul Eggert (2016-12-19): # Here are URLs for the 1918 and 1966 legislation: # http://uscode.house.gov/statviewer.htm?volume=40&page=451 # http://uscode.house.gov/statviewer.htm?volume=80&page=108 # Although the 1918 names were officially "United States Standard # Eastern Time" and similarly for "Central", "Mountain", "Pacific", # and "Alaska", in practice "Standard" was placed just before "Time", # as codified in 1966. In practice, Alaska time was abbreviated "AST" # before 1968. Summarizing the 1967 name changes: # 1918 names 1967 names # -08 Standard Pacific Time (PST) Pacific standard time (PST) # -09 (unofficial) Yukon (YST) Yukon standard time (YST) # -10 Standard Alaska Time (AST) Alaska-Hawaii standard time (AHST) # -11 (unofficial) Nome (NST) Bering standard time (BST) # # From Paul Eggert (2023-01-23), from a 2001-01-08 heads-up from Rives McDow: # Public law 106-564 (2000-12-23) introduced "Chamorro standard time" # for time in Guam and the Northern Marianas. See the file "australasia". # Also see 15 U.S.C. §263 . # # From Paul Eggert (2015-04-17): # HST and HDT are standardized abbreviations for Hawaii-Aleutian # standard and daylight times. See section 9.47 (p 234) of the # U.S. Government Printing Office Style Manual (2008) # https://www.gpo.gov/fdsys/pkg/GPO-STYLEMANUAL-2008/pdf/GPO-STYLEMANUAL-2008.pdf # From Arthur David Olson, 2005-08-09 # The following was signed into law on 2005-08-08. # # H.R. 6, Energy Policy Act of 2005, SEC. 110. DAYLIGHT SAVINGS. # (a) Amendment.--Section 3(a) of the Uniform Time Act of 1966 (15 # U.S.C. 260a(a)) is amended-- # (1) by striking "first Sunday of April" and inserting "second # Sunday of March"; and # (2) by striking "last Sunday of October" and inserting "first # Sunday of November'. # (b) Effective Date.--Subsection (a) shall take effect 1 year after the # date of enactment of this Act or March 1, 2007, whichever is later. # (c) Report to Congress.--Not later than 9 months after the effective # date stated in subsection (b), the Secretary shall report to Congress # on the impact of this section on energy consumption in the United # States. # (d) Right to Revert.--Congress retains the right to revert the # Daylight Saving Time back to the 2005 time schedules once the # Department study is complete. # US eastern time, represented by New York # Connecticut, Delaware, District of Columbia, most of Florida, # Georgia, southeast Indiana (Dearborn and Ohio counties), eastern Kentucky # (except America/Kentucky/Louisville below), Maine, Maryland, Massachusetts, # New Hampshire, New Jersey, New York, North Carolina, Ohio, # Pennsylvania, Rhode Island, South Carolina, eastern Tennessee, # Vermont, Virginia, West Virginia # From Dave Cantor (2004-11-02): # Early this summer I had the occasion to visit the Mount Washington # Observatory weather station atop (of course!) Mount Washington [, NH].... # One of the staff members said that the station was on Eastern Standard Time # and didn't change their clocks for Daylight Saving ... so that their # reports will always have times which are 5 hours behind UTC. # From Paul Eggert (2005-08-26): # According to today's Huntsville Times # http://www.al.com/news/huntsvilletimes/index.ssf?/base/news/1125047783228320.xml&coll=1 # a few towns on Alabama's "eastern border with Georgia, such as Phenix City # in Russell County, Lanett in Chambers County and some towns in Lee County, # set their watches and clocks on Eastern time." It quotes H.H. "Bubba" # Roberts, city administrator in Phenix City. as saying "We are in the Central # time zone, but we do go by the Eastern time zone because so many people work # in Columbus." # # From Paul Eggert (2017-02-22): # Four cities are involved. The two not mentioned above are Smiths Station # and Valley. Barbara Brooks, Valley's assistant treasurer, heard it started # because West Point Pepperell textile mills were in Alabama while the # corporate office was in Georgia, and residents voted to keep Eastern # time even after the mills closed. See: Kazek K. Did you know which # Alabama towns are in a different time zone? al.com 2017-02-06. # http://www.al.com/living/index.ssf/2017/02/do_you_know_which_alabama_town.html # From Paul Eggert (2014-09-06): # Monthly Notices of the Royal Astronomical Society 44, 4 (1884-02-08), 208 # says that New York City Hall time was 3 minutes 58.4 seconds fast of # Eastern time (i.e., -4:56:01.6) just before the 1883 switch. # Rule NAME FROM TO - IN ON AT SAVE LETTER Rule NYC 1920 only - Mar lastSun 2:00 1:00 D Rule NYC 1920 only - Oct lastSun 2:00 0 S Rule NYC 1921 1966 - Apr lastSun 2:00 1:00 D Rule NYC 1921 1954 - Sep lastSun 2:00 0 S Rule NYC 1955 1966 - Oct lastSun 2:00 0 S # Zone NAME STDOFF RULES FORMAT [UNTIL] #STDOFF -4:56:01.6 Zone America/New_York -4:56:02 - LMT 1883 Nov 18 17:00u -5:00 US E%sT 1920 -5:00 NYC E%sT 1942 -5:00 US E%sT 1946 -5:00 NYC E%sT 1967 -5:00 US E%sT # US central time, represented by Chicago # Alabama, Arkansas, Florida panhandle (Bay, Calhoun, Escambia, # Gulf, Holmes, Jackson, Okaloosa, Santa Rosa, Walton, and # Washington counties), Illinois, western Indiana # (Gibson, Jasper, Lake, LaPorte, Newton, Porter, Posey, Spencer, # Vanderburgh, and Warrick counties), Iowa, most of Kansas, western # Kentucky, Louisiana, Minnesota, Mississippi, Missouri, eastern # Nebraska, eastern North Dakota, Oklahoma, eastern South Dakota, # western Tennessee, most of Texas, Wisconsin # From Paul Eggert (2018-01-07): # In 1869 the Chicago Astronomical Society contracted with the city to keep # time. Though delayed by the Great Fire, by 1880 a wire ran from the # Dearborn Observatory (on the University of Chicago campus) to City Hall, # which then sent signals to police and fire stations. However, railroads got # their time signals from the Allegheny Observatory, the Madison Observatory, # the Ann Arbor Observatory, etc., so their clocks did not agree with each # other or with the city's official time. The confusion took some years to # clear up. See: # Moser M. How Chicago gave America its time zones. Chicago. 2018-01-04. # http://www.chicagomag.com/city-life/January-2018/How-Chicago-Gave-America-Its-Time-Zones/ # From Larry M. Smith (2006-04-26) re Wisconsin: # https://docs.legis.wisconsin.gov/statutes/statutes/175.pdf # is currently enforced at the 01:00 time of change. Because the local # "bar time" in the state corresponds to 02:00, a number of citations # are issued for the "sale of class 'B' alcohol after prohibited # hours" within the deviated hour of this change every year.... # # From Douglas R. Bomberg (2007-03-12): # Wisconsin has enacted (nearly eleventh-hour) legislation to get WI # Statue 175 closer in synch with the US Congress' intent.... # https://docs.legis.wisconsin.gov/2007/related/acts/3 # From an email administrator of the City of Fort Pierre, SD (2015-12-21): # Fort Pierre is technically located in the Mountain time zone as is # the rest of Stanley County. Most of Stanley County and Fort Pierre # uses the Central time zone due to doing most of their business in # Pierre so it simplifies schedules. I have lived in Stanley County # all my life and it has been that way since I can remember. (43 years!) # # From Paul Eggert (2015-12-25): # Assume this practice predates 1970, so Fort Pierre can use America/Chicago. # From Paul Eggert (2015-04-06): # In 1950s Nashville a public clock had dueling faces, one for conservatives # and the other for liberals; the two sides didn't agree about the time of day. # I haven't found a photo of this clock, nor have I tracked down the TIME # magazine report cited below, but here's the story as told by the late # American journalist John Seigenthaler, who was there: # # "The two [newspaper] owners held strongly contrasting political and # ideological views. Evans was a New South liberal, Stahlman an Old South # conservative, and their two papers frequently clashed editorially, often on # the same day.... In the 1950s as the state legislature was grappling with # the question of whether to approve daylight saving time for the entire state, # TIME magazine reported: # # "'The Nashville Banner and The Nashville Tennessean rarely agree on anything # but the time of day - and last week they couldn't agree on that.' # # "It was all too true. The clock on the front of the building had two faces - # The Tennessean side of the building facing west, the other, east. When it # was high noon Banner time, it was 11 a.m. Tennessean time." # # Seigenthaler J. For 100 years, Tennessean had it covered. # The Tennessean 2007-05-11, republished 2015-04-06. # https://www.tennessean.com/story/insider/extras/2015/04/06/archives-seigenthaler-for-100-years-the-tennessean-had-it-covered/25348545/ # Rule NAME FROM TO - IN ON AT SAVE LETTER Rule Chicago 1920 only - Jun 13 2:00 1:00 D Rule Chicago 1920 1921 - Oct lastSun 2:00 0 S Rule Chicago 1921 only - Mar lastSun 2:00 1:00 D Rule Chicago 1922 1966 - Apr lastSun 2:00 1:00 D Rule Chicago 1922 1954 - Sep lastSun 2:00 0 S Rule Chicago 1955 1966 - Oct lastSun 2:00 0 S # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone America/Chicago -5:50:36 - LMT 1883 Nov 18 18:00u -6:00 US C%sT 1920 -6:00 Chicago C%sT 1936 Mar 1 2:00 -5:00 - EST 1936 Nov 15 2:00 -6:00 Chicago C%sT 1942 -6:00 US C%sT 1946 -6:00 Chicago C%sT 1967 -6:00 US C%sT # Oliver County, ND switched from mountain to central time on 1992-10-25. Zone America/North_Dakota/Center -6:45:12 - LMT 1883 Nov 18 19:00u -7:00 US M%sT 1992 Oct 25 2:00 -6:00 US C%sT # Morton County, ND, switched from mountain to central time on # 2003-10-26, except for the area around Mandan which was already central time. # See . # Officially this switch also included part of Sioux County, and # Jones, Mellette, and Todd Counties in South Dakota; # but in practice these other counties were already observing central time. # See . Zone America/North_Dakota/New_Salem -6:45:39 - LMT 1883 Nov 18 19:00u -7:00 US M%sT 2003 Oct 26 2:00 -6:00 US C%sT # From Josh Findley (2011-01-21): # ...it appears that Mercer County, North Dakota, changed from the # mountain time zone to the central time zone at the last transition from # daylight-saving to standard time (on Nov. 7, 2010): # https://www.gpo.gov/fdsys/pkg/FR-2010-09-29/html/2010-24376.htm # http://www.bismarcktribune.com/news/local/article_1eb1b588-c758-11df-b472-001cc4c03286.html # From Andy Lipscomb (2011-01-24): # ...according to the Census Bureau, the largest city is Beulah (although # it's commonly referred to as Beulah-Hazen, with Hazen being the next # largest city in Mercer County). Google Maps places Beulah's city hall # at 47° 15' 51" N, 101° 46' 40" W, which yields an offset of 6h47'07". Zone America/North_Dakota/Beulah -6:47:07 - LMT 1883 Nov 18 19:00u -7:00 US M%sT 2010 Nov 7 2:00 -6:00 US C%sT # US mountain time, represented by Denver # # Colorado, far western Kansas, Montana, western # Nebraska, Nevada border (Jackpot, Owyhee, and Mountain City), # New Mexico, southwestern North Dakota, # western South Dakota, far western Texas (El Paso County, Hudspeth County, # and Pine Springs and Nickel Creek in Culberson County), Utah, Wyoming # # From Paul Eggert (2018-10-25): # On 1921-03-04 federal law placed all of Texas into the central time zone. # However, El Paso ignored the law for decades and continued to observe # mountain time, on the grounds that that's what they had always done # and they weren't about to let the federal government tell them what to do. # Eventually the federal government gave in and changed the law on # 1970-04-10 to match what El Paso was actually doing. Although # that's slightly after our 1970 cutoff, there is no need to create a # separate zone for El Paso since they were ignoring the law anyway. See: # Long T. El Pasoans were time rebels, fought to stay in Mountain zone. # El Paso Times. 2018-10-24 06:40 -06. # https://www.elpasotimes.com/story/news/local/el-paso/2018/10/24/el-pasoans-were-time-rebels-fought-stay-mountain-zone/1744509002/ # # Rule NAME FROM TO - IN ON AT SAVE LETTER Rule Denver 1920 1921 - Mar lastSun 2:00 1:00 D Rule Denver 1920 only - Oct lastSun 2:00 0 S Rule Denver 1921 only - May 22 2:00 0 S Rule Denver 1965 1966 - Apr lastSun 2:00 1:00 D Rule Denver 1965 1966 - Oct lastSun 2:00 0 S # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone America/Denver -6:59:56 - LMT 1883 Nov 18 19:00u -7:00 US M%sT 1920 -7:00 Denver M%sT 1942 -7:00 US M%sT 1946 -7:00 Denver M%sT 1967 -7:00 US M%sT # US Pacific time, represented by Los Angeles # # California, northern Idaho (Benewah, Bonner, Boundary, Clearwater, # Kootenai, Latah, Lewis, Nez Perce, and Shoshone counties, Idaho county # north of the Salmon River, and the towns of Burgdorf and Warren), # Nevada (except West Wendover), Oregon (except the northern ¾ of # Malheur county), and Washington # From Paul Eggert (2016-08-20): # In early February 1948, in response to California's electricity shortage, # PG&E changed power frequency from 60 to 59.5 Hz during daylight hours, # causing electric clocks to lose six minutes per day. (This did not change # legal time, and is not part of the data here.) See: # Ross SA. An energy crisis from the past: Northern California in 1948. # Working Paper No. 8, Institute of Governmental Studies, UC Berkeley, # 1973-11. https://escholarship.org/uc/item/8x22k30c # # In another measure to save electricity, DST was instituted from 1948-03-14 # at 02:01 to 1949-01-16 at 02:00, with the governor having the option to move # the fallback transition earlier. See pages 3-4 of: # http://clerk.assembly.ca.gov/sites/clerk.assembly.ca.gov/files/archive/Statutes/1948/48Vol1_Chapters.pdf # # In response: # # Governor Warren received a torrent of objecting mail, and it is not too much # to speculate that the objections to Daylight Saving Time were one important # factor in the defeat of the Dewey-Warren Presidential ticket in California. # -- Ross, p 25 # # On December 8 the governor exercised the option, setting the date to January 1 # (LA Times 1948-12-09). The transition time was 02:00 (LA Times 1949-01-01). # # Despite the controversy, in 1949 California voters approved Proposition 12, # which established DST from April's last Sunday at 01:00 until September's # last Sunday at 02:00. This was amended by 1962's Proposition 6, which changed # the fall-back date to October's last Sunday. See: # https://repository.uchastings.edu/cgi/viewcontent.cgi?article=1501&context=ca_ballot_props # https://repository.uchastings.edu/cgi/viewcontent.cgi?article=1636&context=ca_ballot_props # # Rule NAME FROM TO - IN ON AT SAVE LETTER Rule CA 1948 only - Mar 14 2:01 1:00 D Rule CA 1949 only - Jan 1 2:00 0 S Rule CA 1950 1966 - Apr lastSun 1:00 1:00 D Rule CA 1950 1961 - Sep lastSun 2:00 0 S Rule CA 1962 1966 - Oct lastSun 2:00 0 S # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone America/Los_Angeles -7:52:58 - LMT 1883 Nov 18 20:00u -8:00 US P%sT 1946 -8:00 CA P%sT 1967 -8:00 US P%sT # Alaska # AK%sT is the modern abbreviation for -09 per USNO. # # From Paul Eggert (2017-06-15): # Howse writes that Alaska switched from the Julian to the Gregorian calendar, # and from east-of-GMT to west-of-GMT days, when the US bought it from Russia. # On Friday, 1867-10-18 (Gregorian), at precisely 15:30 local time, the # Russian forts and fleet at Sitka fired salutes to mark the ceremony of # formal transfer. See the Sacramento Daily Union (1867-11-14), p 3, col 2. # https://cdnc.ucr.edu/cgi-bin/cdnc?a=d&d=SDU18671114.2.12.1 # Sitka workers did not change their calendars until Sunday, 1867-10-20, # and so celebrated two Sundays that week. See: Ahllund T (tr Hallamaa P). # From the memoirs of a Finnish workman. Alaska History. 2006 Fall;21(2):1-25. # http://alaskahistoricalsociety.org/wp-content/uploads/2016/12/Ahllund-2006-Memoirs-of-a-Finnish-Workman.pdf # Include only the time zone part of this transition, ignoring the switch # from Julian to Gregorian, since we can't represent the Julian calendar. # # As far as we know, of the locations mentioned below only Sitka was # permanently inhabited in 1867 by anyone using either calendar. # (Yakutat was colonized by the Russians in 1799, but the settlement was # destroyed in 1805 by a Yakutat-kon war party.) Many of Alaska's inhabitants # were unaware of the US acquisition of Alaska, much less of any calendar or # time change. However, the Russian-influenced part of Alaska did observe # Russian time, and it is more accurate to model this than to ignore it. # The database format requires an exact transition time; use the Russian # salute as a somewhat-arbitrary time for the formal transfer of control for # all of Alaska. Sitka's UTC offset is -9:01:13; adjust its 15:30 to the # local times of other Alaskan locations so that they change simultaneously. # From Paul Eggert (2014-07-18): # One opinion of the early 1980s turmoil in Alaska over time zones and # daylight saving time appeared as graffiti on a Juneau airport wall: # "Welcome to Juneau. Please turn your watch back to the 19th century." # See: Turner W. Alaska's four time zones now two. NY Times 1983-11-01. # http://www.nytimes.com/1983/11/01/us/alaska-s-four-time-zones-now-two.html # # Steve Ferguson (2011-01-31) referred to the following source: # Norris F. Keeping time in Alaska: national directives, local response. # Alaska History 2001;16(1-2). # http://alaskahistoricalsociety.org/discover-alaska/glimpses-of-the-past/keeping-time-in-alaska/ # From Arthur David Olson (2011-02-01): # Here's database-relevant material from the 2001 "Alaska History" article: # # On September 20 [1979]...DOT...officials decreed that on April 27, # 1980, Juneau and other nearby communities would move to Yukon Time. # Sitka, Petersburg, Wrangell, and Ketchikan, however, would remain on # Pacific Time. # # ...on September 22, 1980, DOT Secretary Neil E. Goldschmidt rescinded the # Department's September 1979 decision. Juneau and other communities in # northern Southeast reverted to Pacific Time on October 26. # # On October 28 [1983]...the Metlakatla Indian Community Council voted # unanimously to keep the reservation on Pacific Time. # # According to DOT official Joanne Petrie, Indian reservations are not # bound to follow time zones imposed by neighboring jurisdictions. # # (The last is consistent with how the database now handles the Navajo # Nation.) # From Arthur David Olson (2011-02-09): # I just spoke by phone with a staff member at the Metlakatla Indian # Community office (using contact information available at # http://www.commerce.state.ak.us/dca/commdb/CIS.cfm?Comm_Boro_name=Metlakatla # It's shortly after 1:00 here on the east coast of the United States; # the staffer said it was shortly after 10:00 there. When I asked whether # that meant they were on Pacific time, they said no - they were on their # own time. I asked about daylight saving; they said it wasn't used. I # did not inquire about practices in the past. # From Arthur David Olson (2011-08-17): # For lack of better information, assume that Metlakatla's # abandonment of use of daylight saving resulted from the 1983 vote. # From Steffen Thorsen (2015-11-09): # It seems Metlakatla did go off PST on Sunday, November 1, changing # their time to AKST and are going to follow Alaska's DST, switching # between AKST and AKDT from now on.... # https://www.krbd.org/2015/10/30/annette-island-times-they-are-a-changing/ # From Ryan Stanley (2018-11-06): # The Metlakatla community in Alaska has decided not to change its # clock back an hour starting on November 4th, 2018 (day before yesterday). # They will be gmtoff=-28800 year-round. # https://www.facebook.com/141055983004923/photos/pb.141055983004923.-2207520000.1541465673./569081370202380/ # From Paul Eggert (2018-12-16): # In a 2018-12-11 special election, Metlakatla voted to go back to # Alaska time (including daylight saving time) starting next year. # https://www.krbd.org/2018/12/12/metlakatla-to-follow-alaska-standard-time-allow-liquor-sales/ # # From Ryan Stanley (2019-01-11): # The community will be changing back on the 20th of this month... # From Tim Parenti (2019-01-11): # Per an announcement on the Metlakatla community's official Facebook page, the # "fall back" will be on Sunday 2019-01-20 at 02:00: # https://www.facebook.com/141055983004923/photos/607150969728753/ # So they won't be waiting for Alaska to join them on 2019-03-10, but will # rather change their clocks twice in seven weeks. # From Paul Eggert (2023-01-23): # America/Adak is for the Aleutian Islands that are part of Alaska # and are west of 169.5° W. # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone America/Juneau 15:02:19 - LMT 1867 Oct 19 15:33:32 -8:57:41 - LMT 1900 Aug 20 12:00 -8:00 - PST 1942 -8:00 US P%sT 1946 -8:00 - PST 1969 -8:00 US P%sT 1980 Apr 27 2:00 -9:00 US Y%sT 1980 Oct 26 2:00 -8:00 US P%sT 1983 Oct 30 2:00 -9:00 US Y%sT 1983 Nov 30 -9:00 US AK%sT Zone America/Sitka 14:58:47 - LMT 1867 Oct 19 15:30 -9:01:13 - LMT 1900 Aug 20 12:00 -8:00 - PST 1942 -8:00 US P%sT 1946 -8:00 - PST 1969 -8:00 US P%sT 1983 Oct 30 2:00 -9:00 US Y%sT 1983 Nov 30 -9:00 US AK%sT Zone America/Metlakatla 15:13:42 - LMT 1867 Oct 19 15:44:55 -8:46:18 - LMT 1900 Aug 20 12:00 -8:00 - PST 1942 -8:00 US P%sT 1946 -8:00 - PST 1969 -8:00 US P%sT 1983 Oct 30 2:00 -8:00 - PST 2015 Nov 1 2:00 -9:00 US AK%sT 2018 Nov 4 2:00 -8:00 - PST 2019 Jan 20 2:00 -9:00 US AK%sT Zone America/Yakutat 14:41:05 - LMT 1867 Oct 19 15:12:18 -9:18:55 - LMT 1900 Aug 20 12:00 -9:00 - YST 1942 -9:00 US Y%sT 1946 -9:00 - YST 1969 -9:00 US Y%sT 1983 Nov 30 -9:00 US AK%sT Zone America/Anchorage 14:00:24 - LMT 1867 Oct 19 14:31:37 -9:59:36 - LMT 1900 Aug 20 12:00 -10:00 - AST 1942 -10:00 US A%sT 1967 Apr -10:00 - AHST 1969 -10:00 US AH%sT 1983 Oct 30 2:00 -9:00 US Y%sT 1983 Nov 30 -9:00 US AK%sT Zone America/Nome 12:58:22 - LMT 1867 Oct 19 13:29:35 -11:01:38 - LMT 1900 Aug 20 12:00 -11:00 - NST 1942 -11:00 US N%sT 1946 -11:00 - NST 1967 Apr -11:00 - BST 1969 -11:00 US B%sT 1983 Oct 30 2:00 -9:00 US Y%sT 1983 Nov 30 -9:00 US AK%sT Zone America/Adak 12:13:22 - LMT 1867 Oct 19 12:44:35 -11:46:38 - LMT 1900 Aug 20 12:00 -11:00 - NST 1942 -11:00 US N%sT 1946 -11:00 - NST 1967 Apr -11:00 - BST 1969 -11:00 US B%sT 1983 Oct 30 2:00 -10:00 US AH%sT 1983 Nov 30 -10:00 US H%sT # The following switches don't make our 1970 cutoff. # # Kiska observed Tokyo date and time during Japanese occupation from # 1942-06-06 to 1943-07-29, and similarly for Attu from 1942-06-07 to # 1943-05-29 (all dates American). Both islands are now uninhabited. # # Shanks writes that part of southwest Alaska (e.g. Aniak) # switched from -11:00 to -10:00 on 1968-09-22 at 02:00, # and another part (e.g. Akiak) made the same switch five weeks later. # # From David Flater (2004-11-09): # In e-mail, 2004-11-02, Ray Hudson, historian/liaison to the Unalaska # Historic Preservation Commission, provided this information, which # suggests that Unalaska deviated from statutory time from early 1967 # possibly until 1983: # # Minutes of the Unalaska City Council Meeting, January 10, 1967: # "Except for St. Paul and Akutan, Unalaska is the only important # location not on Alaska Standard Time. The following resolution was # made by William Robinson and seconded by Henry Swanson: Be it # resolved that the City of Unalaska hereby goes to Alaska Standard # Time as of midnight Friday, January 13, 1967 (1 A.M. Saturday, # January 14, Alaska Standard Time.) This resolution was passed with # three votes for and one against." # Hawaii # From Arthur David Olson (2010-12-09): # "Hawaiian Time" by Robert C. Schmitt and Doak C. Cox appears on pages 207-225 # of volume 26 of The Hawaiian Journal of History (1992). As of 2010-12-09, # the article is available at # https://evols.library.manoa.hawaii.edu/bitstream/10524/239/2/JL26215.pdf # and indicates that standard time was adopted effective noon, January # 13, 1896 (page 218), that in "1933, the Legislature decreed daylight # saving for the period between the last Sunday of each April and the # last Sunday of each September, but less than a month later repealed the # act," (page 220), that year-round daylight saving time was in effect # from 1942-02-09 to 1945-09-30 (page 221, with no time of day given for # when clocks changed) and that clocks were changed by 30 minutes # effective the second Sunday of June, 1947 (page 219, with no time of # day given for when clocks changed). A footnote for the 1933 changes # cites Session Laws of Hawaii 1933, "Act. 90 (approved 26 Apr. 1933) # and Act 163 (approved 21 May 1933)." # From Arthur David Olson (2011-01-19): # The following is from "Laws of the Territory of Hawaii Passed by the # Seventeenth Legislature: Regular Session 1933," available (as of # 2011-01-19) at American University's Pence Law Library. Page 85: "Act # 90...At 2 o'clock ante meridian of the last Sunday in April of each # year, the standard time of this Territory shall be advanced one # hour...This Act shall take effect upon its approval. Approved this 26th # day of April, A. D. 1933. LAWRENCE M JUDD, Governor of the Territory of # Hawaii." Page 172: "Act 163...Act 90 of the Session Laws of 1933 is # hereby repealed...This Act shall take effect upon its approval, upon # which date the standard time of this Territory shall be restored to # that existing immediately prior to the taking effect of said Act 90. # Approved this 21st day of May, A. D. 1933. LAWRENCE M. JUDD, Governor # of the Territory of Hawaii." # # Note that 1933-05-21 was a Sunday. # We're left to guess the time of day when Act 163 was approved; guess noon. # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone Pacific/Honolulu -10:31:26 - LMT 1896 Jan 13 12:00 -10:30 - HST 1933 Apr 30 2:00 -10:30 1:00 HDT 1933 May 21 12:00 -10:30 US H%sT 1947 Jun 8 2:00 -10:00 - HST # Now we turn to US areas that have diverged from the consensus since 1970. # Arizona mostly uses MST. # From Paul Eggert (2002-10-20): # # The information in the rest of this paragraph is derived from the # Daylight Saving Time web page # (2002-01-23) # maintained by the Arizona State Library, Archives and Public Records. # Between 1944-01-01 and 1944-04-01 the State of Arizona used standard # time, but by federal law railroads, airlines, bus lines, military # personnel, and some engaged in interstate commerce continued to # observe war (i.e., daylight saving) time. The 1944-03-17 Phoenix # Gazette says that was the date the law changed, and that 04-01 was # the date the state's clocks would change. In 1945 the State of # Arizona used standard time all year, again with exceptions only as # mandated by federal law. Arizona observed DST in 1967, but Arizona # Laws 1968, ch. 183 (effective 1968-03-21) repealed DST. # # Shanks says the 1944 experiment came to an end on 1944-03-17. # Go with the Arizona State Library instead. # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone America/Phoenix -7:28:18 - LMT 1883 Nov 18 19:00u -7:00 US M%sT 1944 Jan 1 0:01 -7:00 - MST 1944 Apr 1 0:01 -7:00 US M%sT 1944 Oct 1 0:01 -7:00 - MST 1967 -7:00 US M%sT 1968 Mar 21 -7:00 - MST # From Arthur David Olson (1988-02-13): # A writer from the Inter Tribal Council of Arizona, Inc., # notes in private correspondence dated 1987-12-28 that "Presently, only the # Navajo Nation participates in the Daylight Saving Time policy, due to its # large size and location in three states." (The "only" means that other # tribal nations don't use DST.) # # From Paul Eggert (2013-08-26): # See America/Denver for a zone appropriate for the Navajo Nation. # Southern Idaho (Ada, Adams, Bannock, Bear Lake, Bingham, Blaine, # Boise, Bonneville, Butte, Camas, Canyon, Caribou, Cassia, Clark, # Custer, Elmore, Franklin, Fremont, Gem, Gooding, Jefferson, Jerome, # Lemhi, Lincoln, Madison, Minidoka, Oneida, Owyhee, Payette, Power, # Teton, Twin Falls, Valley, Washington counties, and the southern # quarter of Idaho county) and eastern Oregon (most of Malheur County) # switched four weeks late in 1974. # # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone America/Boise -7:44:49 - LMT 1883 Nov 18 20:00u -8:00 US P%sT 1923 May 13 2:00 -7:00 US M%sT 1974 -7:00 - MST 1974 Feb 3 2:00 -7:00 US M%sT # Indiana # # For a map of Indiana's time zone regions, see: # https://en.wikipedia.org/wiki/Time_in_Indiana # # From Paul Eggert (2018-11-30): # A brief but entertaining history of time in Indiana describes a 1949 debate # in the Indiana House where city legislators (who favored "fast time") # tussled with farm legislators (who didn't) over a bill to outlaw DST: # "Lacking enough votes, the city faction tries to filibuster until time runs # out on the session at midnight, but rural champion Rep. Herbert Copeland, # R-Madison, leans over the gallery railing and forces the official clock # back to 9 p.m., breaking it in the process. The clock sticks on 9 as the # debate rages on into the night. The filibuster finally dies out and the # bill passes, while outside the chamber, clocks read 3:30 a.m. In the end, # it doesn't matter which side won. The law has no enforcement powers and # is simply ignored by fast-time communities." # How Indiana went from 'God's time' to split zones and daylight-saving. # Indianapolis Star. 2018-11-27 14:58 -05. # https://www.indystar.com/story/news/politics/2018/11/27/indianapolis-indiana-time-zone-history-central-eastern-daylight-savings-time/2126300002/ # # From Paul Eggert (2007-08-17): # Since 1970, most of Indiana has been like America/Indiana/Indianapolis, # with the following exceptions: # # - Gibson, Jasper, Lake, LaPorte, Newton, Porter, Posey, Spencer, # Vanderburgh, and Warrick counties have been like America/Chicago. # # - Dearborn and Ohio counties have been like America/New_York. # # - Clark, Floyd, and Harrison counties have been like # America/Kentucky/Louisville. # # - Crawford, Daviess, Dubois, Knox, Martin, Perry, Pike, Pulaski, Starke, # and Switzerland counties have their own time zone histories as noted below. # # Shanks partitioned Indiana into 345 regions, each with its own time history, # and wrote "Even newspaper reports present contradictory information." # Those Hoosiers! Such a flighty and changeable people! # Fortunately, most of the complexity occurred before our cutoff date of 1970. # # Other than Indianapolis, the Indiana place names are so nondescript # that they would be ambiguous if we left them at the 'America' level. # So we reluctantly put them all in a subdirectory 'America/Indiana'. # From Paul Eggert (2014-06-26): # https://www.federalregister.gov/articles/2006/01/20/06-563/standard-time-zone-boundary-in-the-state-of-indiana # says "DOT is relocating the time zone boundary in Indiana to move Starke, # Pulaski, Knox, Daviess, Martin, Pike, Dubois, and Perry Counties from the # Eastern Time Zone to the Central Time Zone.... The effective date of # this rule is 2 a.m. EST Sunday, April 2, 2006, which is the # changeover date from standard time to Daylight Saving Time." # Strictly speaking, this meant the affected counties changed their # clocks twice that night, but this obviously was in error. The intent # was that 01:59:59 EST be followed by 02:00:00 CDT. # From Gwillim Law (2007-02-10): # The Associated Press has been reporting that Pulaski County, Indiana is # going to switch from Central to Eastern Time on March 11, 2007.... # http://www.indystar.com/apps/pbcs.dll/article?AID=/20070207/LOCAL190108/702070524/0/LOCAL # Rule NAME FROM TO - IN ON AT SAVE LETTER Rule Indianapolis 1941 only - Jun 22 2:00 1:00 D Rule Indianapolis 1941 1954 - Sep lastSun 2:00 0 S Rule Indianapolis 1946 1954 - Apr lastSun 2:00 1:00 D # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone America/Indiana/Indianapolis -5:44:38 - LMT 1883 Nov 18 18:00u -6:00 US C%sT 1920 -6:00 Indianapolis C%sT 1942 -6:00 US C%sT 1946 -6:00 Indianapolis C%sT 1955 Apr 24 2:00 -5:00 - EST 1957 Sep 29 2:00 -6:00 - CST 1958 Apr 27 2:00 -5:00 - EST 1969 -5:00 US E%sT 1971 -5:00 - EST 2006 -5:00 US E%sT # # Eastern Crawford County, Indiana, left its clocks alone in 1974, # as well as from 1976 through 2005. # Rule NAME FROM TO - IN ON AT SAVE LETTER Rule Marengo 1951 only - Apr lastSun 2:00 1:00 D Rule Marengo 1951 only - Sep lastSun 2:00 0 S Rule Marengo 1954 1960 - Apr lastSun 2:00 1:00 D Rule Marengo 1954 1960 - Sep lastSun 2:00 0 S # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone America/Indiana/Marengo -5:45:23 - LMT 1883 Nov 18 18:00u -6:00 US C%sT 1951 -6:00 Marengo C%sT 1961 Apr 30 2:00 -5:00 - EST 1969 -5:00 US E%sT 1974 Jan 6 2:00 -6:00 1:00 CDT 1974 Oct 27 2:00 -5:00 US E%sT 1976 -5:00 - EST 2006 -5:00 US E%sT # # Daviess, Dubois, Knox, and Martin Counties, Indiana, # switched from eastern to central time in April 2006, then switched back # in November 2007. # Rule NAME FROM TO - IN ON AT SAVE LETTER Rule Vincennes 1946 only - Apr lastSun 2:00 1:00 D Rule Vincennes 1946 only - Sep lastSun 2:00 0 S Rule Vincennes 1953 1954 - Apr lastSun 2:00 1:00 D Rule Vincennes 1953 1959 - Sep lastSun 2:00 0 S Rule Vincennes 1955 only - May 1 0:00 1:00 D Rule Vincennes 1956 1963 - Apr lastSun 2:00 1:00 D Rule Vincennes 1960 only - Oct lastSun 2:00 0 S Rule Vincennes 1961 only - Sep lastSun 2:00 0 S Rule Vincennes 1962 1963 - Oct lastSun 2:00 0 S # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone America/Indiana/Vincennes -5:50:07 - LMT 1883 Nov 18 18:00u -6:00 US C%sT 1946 -6:00 Vincennes C%sT 1964 Apr 26 2:00 -5:00 - EST 1969 -5:00 US E%sT 1971 -5:00 - EST 2006 Apr 2 2:00 -6:00 US C%sT 2007 Nov 4 2:00 -5:00 US E%sT # # Perry County, Indiana, switched from eastern to central time in April 2006. # From Alois Treindl (2019-07-09): # The Indianapolis News, Friday 27 October 1967 states that Perry County # returned to CST. It went again to EST on 27 April 1969, as documented by the # Indianapolis star of Saturday 26 April. # Rule NAME FROM TO - IN ON AT SAVE LETTER Rule Perry 1955 only - May 1 0:00 1:00 D Rule Perry 1955 1960 - Sep lastSun 2:00 0 S Rule Perry 1956 1963 - Apr lastSun 2:00 1:00 D Rule Perry 1961 1963 - Oct lastSun 2:00 0 S # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone America/Indiana/Tell_City -5:47:03 - LMT 1883 Nov 18 18:00u -6:00 US C%sT 1946 -6:00 Perry C%sT 1964 Apr 26 2:00 -5:00 - EST 1967 Oct 29 2:00 -6:00 US C%sT 1969 Apr 27 2:00 -5:00 US E%sT 1971 -5:00 - EST 2006 Apr 2 2:00 -6:00 US C%sT # # Pike County, Indiana moved from central to eastern time in 1977, # then switched back in 2006, then switched back again in 2007. # Rule NAME FROM TO - IN ON AT SAVE LETTER Rule Pike 1955 only - May 1 0:00 1:00 D Rule Pike 1955 1960 - Sep lastSun 2:00 0 S Rule Pike 1956 1964 - Apr lastSun 2:00 1:00 D Rule Pike 1961 1964 - Oct lastSun 2:00 0 S # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone America/Indiana/Petersburg -5:49:07 - LMT 1883 Nov 18 18:00u -6:00 US C%sT 1955 -6:00 Pike C%sT 1965 Apr 25 2:00 -5:00 - EST 1966 Oct 30 2:00 -6:00 US C%sT 1977 Oct 30 2:00 -5:00 - EST 2006 Apr 2 2:00 -6:00 US C%sT 2007 Nov 4 2:00 -5:00 US E%sT # # Starke County, Indiana moved from central to eastern time in 1991, # then switched back in 2006. # From Arthur David Olson (1991-10-28): # An article on page A3 of the Sunday, 1991-10-27 Washington Post # notes that Starke County switched from Central time to Eastern time as of # 1991-10-27. # Rule NAME FROM TO - IN ON AT SAVE LETTER Rule Starke 1947 1961 - Apr lastSun 2:00 1:00 D Rule Starke 1947 1954 - Sep lastSun 2:00 0 S Rule Starke 1955 1956 - Oct lastSun 2:00 0 S Rule Starke 1957 1958 - Sep lastSun 2:00 0 S Rule Starke 1959 1961 - Oct lastSun 2:00 0 S # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone America/Indiana/Knox -5:46:30 - LMT 1883 Nov 18 18:00u -6:00 US C%sT 1947 -6:00 Starke C%sT 1962 Apr 29 2:00 -5:00 - EST 1963 Oct 27 2:00 -6:00 US C%sT 1991 Oct 27 2:00 -5:00 - EST 2006 Apr 2 2:00 -6:00 US C%sT # # Pulaski County, Indiana, switched from eastern to central time in # April 2006 and then switched back in March 2007. # Rule NAME FROM TO - IN ON AT SAVE LETTER Rule Pulaski 1946 1960 - Apr lastSun 2:00 1:00 D Rule Pulaski 1946 1954 - Sep lastSun 2:00 0 S Rule Pulaski 1955 1956 - Oct lastSun 2:00 0 S Rule Pulaski 1957 1960 - Sep lastSun 2:00 0 S # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone America/Indiana/Winamac -5:46:25 - LMT 1883 Nov 18 18:00u -6:00 US C%sT 1946 -6:00 Pulaski C%sT 1961 Apr 30 2:00 -5:00 - EST 1969 -5:00 US E%sT 1971 -5:00 - EST 2006 Apr 2 2:00 -6:00 US C%sT 2007 Mar 11 2:00 -5:00 US E%sT # # Switzerland County, Indiana, did not observe DST from 1973 through 2005. # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone America/Indiana/Vevay -5:40:16 - LMT 1883 Nov 18 18:00u -6:00 US C%sT 1954 Apr 25 2:00 -5:00 - EST 1969 -5:00 US E%sT 1973 -5:00 - EST 2006 -5:00 US E%sT # From Paul Eggert (2018-03-20): # The Louisville & Nashville Railroad's 1883-11-18 change occurred at # 10:00 old local time; train were supposed to come to a standstill # for precisely 18 minutes. See Bartky Fig. 1 (page 50). It is not # clear how this matched civil time in Louisville, so for now continue # to assume Louisville switched at noon new local time, like New York. # # From Michael Deckers (2019-08-06): # From the contemporary source given by Alois Treindl, # the switch in Louisville on 1946-04-28 was on 00:01 # From Paul Eggert (2019-08-26): # That source was the Louisville Courier-Journal, 1946-04-27, p 4. # Shanks gives 02:00 for all 20th-century transition times in Louisville. # Evidently this is wrong for spring 1946. Although also likely wrong # for other dates, we have no data. # # Part of Kentucky left its clocks alone in 1974. # This also includes Clark, Floyd, and Harrison counties in Indiana. # Rule NAME FROM TO - IN ON AT SAVE LETTER Rule Louisville 1921 only - May 1 2:00 1:00 D Rule Louisville 1921 only - Sep 1 2:00 0 S Rule Louisville 1941 only - Apr lastSun 2:00 1:00 D Rule Louisville 1941 only - Sep lastSun 2:00 0 S Rule Louisville 1946 only - Apr lastSun 0:01 1:00 D Rule Louisville 1946 only - Jun 2 2:00 0 S Rule Louisville 1950 1961 - Apr lastSun 2:00 1:00 D Rule Louisville 1950 1955 - Sep lastSun 2:00 0 S Rule Louisville 1956 1961 - Oct lastSun 2:00 0 S # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone America/Kentucky/Louisville -5:43:02 - LMT 1883 Nov 18 18:00u -6:00 US C%sT 1921 -6:00 Louisville C%sT 1942 -6:00 US C%sT 1946 -6:00 Louisville C%sT 1961 Jul 23 2:00 -5:00 - EST 1968 -5:00 US E%sT 1974 Jan 6 2:00 -6:00 1:00 CDT 1974 Oct 27 2:00 -5:00 US E%sT # # Wayne County, Kentucky # # From Lake Cumberland LIFE # http://www.lake-cumberland.com/life/archive/news990129time.shtml # (1999-01-29) via WKYM-101.7: # Clinton County has joined Wayne County in asking the DoT to change from # the Central to the Eastern time zone.... The Wayne County government made # the same request in December. And while Russell County officials have not # taken action, the majority of respondents to a poll conducted there in # August indicated they would like to change to "fast time" also. # The three Lake Cumberland counties are the farthest east of any U.S. # location in the Central time zone. # # From Rich Wales (2000-08-29): # After prolonged debate, and despite continuing deep differences of opinion, # Wayne County (central Kentucky) is switching from Central (-0600) to Eastern # (-0500) time. They won't "fall back" this year. See Sara Shipley, # The difference an hour makes, Nando Times (2000-08-29 15:33 -0400). # # From Paul Eggert (2001-07-16): # The final rule was published in the # Federal Register 65, 160 (2000-08-17), pp 50154-50158. # https://www.gpo.gov/fdsys/pkg/FR-2000-08-17/html/00-20854.htm # Zone America/Kentucky/Monticello -5:39:24 - LMT 1883 Nov 18 18:00u -6:00 US C%sT 1946 -6:00 - CST 1968 -6:00 US C%sT 2000 Oct 29 2:00 -5:00 US E%sT # From Rives McDow (2000-08-30): # Here ... are all the changes in the US since 1985. # Kearny County, KS (put all of county on central; # previously split between MST and CST) ... 1990-10 # Starke County, IN (from CST to EST) ... 1991-10 # Oliver County, ND (from MST to CST) ... 1992-10 # West Wendover, NV (from PST TO MST) ... 1999-10 # Wayne County, KY (from CST to EST) ... 2000-10 # # From Paul Eggert (2001-07-17): # We don't know where the line used to be within Kearny County, KS, # so omit that change for now. # See America/Indiana/Knox for the Starke County, IN change. # See America/North_Dakota/Center for the Oliver County, ND change. # West Wendover, NV officially switched from Pacific to mountain time on # 1999-10-31. See the # Federal Register 64, 203 (1999-10-21), pp 56705-56707. # https://www.gpo.gov/fdsys/pkg/FR-1999-10-21/html/99-27240.htm # However, the Federal Register says that West Wendover already operated # on mountain time, and the rule merely made this official; # hence a separate tz entry is not needed. # Michigan # # From Bob Devine (1988-01-28): # Michigan didn't observe DST from 1968 to 1973. # # From Paul Eggert (1999-03-31): # Shanks writes that Michigan started using standard time on 1885-09-18, # but Howse writes (pp 124-125, referring to Popular Astronomy, 1901-01) # that Detroit kept # # local time until 1900 when the City Council decreed that clocks should # be put back twenty-eight minutes to Central Standard Time. Half the # city obeyed, half refused. After considerable debate, the decision # was rescinded and the city reverted to Sun time. A derisive offer to # erect a sundial in front of the city hall was referred to the # Committee on Sewers. Then, in 1905, Central time was adopted # by city vote. # # This story is too entertaining to be false, so go with Howse over Shanks. # # From Paul Eggert (2001-03-06): # Garland (1927) writes "Cleveland and Detroit advanced their clocks # one hour in 1914." This change is not in Shanks. We have no more # info, so omit this for now. # # From Paul Eggert (2019-07-06): # Due to a complicated set of legal maneuvers, in 1967 Michigan did # not start daylight saving time when the rest of the US did. # Instead, it began DST on Jun 14 at 00:01. This was big news: # the Detroit Free Press reported it at the top of Page 1 on # 1967-06-14, in an article "State Adjusting to Switch to Fast Time" # by Gary Blonston, above an article about Thurgood Marshall's # confirmation to the US Supreme Court. Although Shanks says Detroit # observed DST until 1967-10-29 00:01, that time of day seems to be # incorrect, as the Free Press later said DST ended in Michigan at the # same time as the rest of the US. Also, although Shanks reports no DST in # Detroit in 1968, it did observe DST that year; in the November 1968 # election Michigan voters narrowly repealed DST, effective 1969. # # Most of Michigan observed DST from 1973 on, but was a bit late in 1975. # Rule NAME FROM TO - IN ON AT SAVE LETTER Rule Detroit 1948 only - Apr lastSun 2:00 1:00 D Rule Detroit 1948 only - Sep lastSun 2:00 0 S # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone America/Detroit -5:32:11 - LMT 1905 -6:00 - CST 1915 May 15 2:00 -5:00 - EST 1942 -5:00 US E%sT 1946 -5:00 Detroit E%sT 1967 Jun 14 0:01 -5:00 US E%sT 1969 -5:00 - EST 1973 -5:00 US E%sT 1975 -5:00 - EST 1975 Apr 27 2:00 -5:00 US E%sT # # Dickinson, Gogebic, Iron, and Menominee Counties, Michigan, # switched from EST to CST/CDT in 1973. # Rule NAME FROM TO - IN ON AT SAVE LETTER Rule Menominee 1946 only - Apr lastSun 2:00 1:00 D Rule Menominee 1946 only - Sep lastSun 2:00 0 S Rule Menominee 1966 only - Apr lastSun 2:00 1:00 D Rule Menominee 1966 only - Oct lastSun 2:00 0 S # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone America/Menominee -5:50:27 - LMT 1885 Sep 18 12:00 -6:00 US C%sT 1946 -6:00 Menominee C%sT 1969 Apr 27 2:00 -5:00 - EST 1973 Apr 29 2:00 -6:00 US C%sT # Navassa # administered by the US Fish and Wildlife Service # claimed by US under the provisions of the 1856 Guano Islands Act # also claimed by Haiti # occupied 1857/1900 by the Navassa Phosphate Co # US lighthouse 1917/1996-09 # currently uninhabited # see Mark Fineman, "An Isle Rich in Guano and Discord", # _Los Angeles Times_ (1998-11-10), A1, A10; it cites # Jimmy Skaggs, _The Great Guano Rush_ (1994). # From Rob van Gent (2025-07-23): # Another useful source for historical time zone information appears to be # a series of circulars with the title "Standard Time Throughout the World" # issued between 1925 and 1950 by the U.S. Bureau of Standards. # I found the following issues online: # https://nvlpubs.nist.gov/nistpubs/Legacy/circ/nbscircular280.pdf (1925) # https://nvlpubs.nist.gov/nistpubs/Legacy/circ/nbscircular399.pdf (1932) # https://nvlpubs.nist.gov/nistpubs/Legacy/circ/nbscircular406.pdf (1935) # https://nvlpubs.nist.gov/nistpubs/Legacy/circ/nbscircular496.pdf (1950) ################################################################################ # From Paul Eggert (2017-02-10): # # Unless otherwise specified, the source for data through 1990 is: # Thomas G. Shanks and Rique Pottenger, The International Atlas (6th edition), # San Diego: ACS Publications, Inc. (2003). # Unfortunately this book contains many errors and cites no sources. # # Many years ago Gwillim Law wrote that a good source # for time zone data was the International Air Transport # Association's Standard Schedules Information Manual (IATA SSIM), # published semiannually. Law sent in several helpful summaries # of the IATA's data after 1990. Except where otherwise noted, # IATA SSIM is the source for entries after 1990. # # Other sources occasionally used include: # # Milne J. Civil time. Geogr J. 1899 Feb;13(2):173-94 # . # # Pearce C. The Great Daylight Saving Time Controversy. # Australian Ebook Publisher. 2017. ISBN 978-1-925516-96-8. # # Edward W. Whitman, World Time Differences, # Whitman Publishing Co, 2 Niagara Av, Ealing, London (undated), # which I found in the UCLA library. # # William Willett, The Waste of Daylight, 19th edition # # [PDF] (1914-03) # # For the 1911/1912 establishment of standard time in French possessions, see: # Société Française de Physique, Recueil de constantes physiques (1913), # page 752, 18b. # # See the 'europe' file for Greenland. # Canada # From Alain LaBonté (1994-11-14): # I post here the time zone abbreviations standardized in Canada # for both English and French in the CAN/CSA-Z234.4-89 standard.... # # UTC Standard time Daylight saving time # offset French English French English # -2:30 - - HAT NDT # -3 - - HAA ADT # -3:30 HNT NST - - # -4 HNA AST HAE EDT # -5 HNE EST HAC CDT # -6 HNC CST HAR MDT # -7 HNR MST HAP PDT # -8 HNP PST HAY YDT # -9 HNY YST - - # # HN: Heure Normale ST: Standard Time # HA: Heure Avancée DT: Daylight saving Time # # A: de l'Atlantique Atlantic # C: du Centre Central # E: de l'Est Eastern # M: Mountain # N: Newfoundland # P: du Pacifique Pacific # R: des Rocheuses # T: de Terre-Neuve # Y: du Yukon Yukon # # From Paul Eggert (1994-11-22): # Alas, this sort of thing must be handled by localization software. # Unless otherwise specified, the data entries for Canada are all from Shanks # & Pottenger. # From Chris Walton (2006-04-01, 2006-04-25, 2006-06-26, 2007-01-31, # 2007-03-01): # The British Columbia government announced yesterday that it will # adjust daylight savings next year to align with changes in the # U.S. and the rest of Canada.... # https://archive.news.gov.bc.ca/releases/news_releases_2005-2009/2006AG0014-000330.htm # ... # Nova Scotia # Daylight saving time will be extended by four weeks starting in 2007.... # https://www.novascotia.ca/just/regulations/rg2/2006/ma1206.pdf # # [For New Brunswick] the new legislation dictates that the time change is to # be done at 02:00 instead of 00:01. # https://www.gnb.ca/0062/acts/BBA-2006/Chap-19.pdf # ... # Manitoba has traditionally changed the clock every fall at 03:00. # As of 2006, the transition is to take place one hour earlier at 02:00. # https://web2.gov.mb.ca/laws/statutes/ccsm/o030e.php # ... # [Alberta, Ontario, Quebec] will follow US rules. # http://www.qp.gov.ab.ca/documents/spring/CH03_06.CFM # http://www.e-laws.gov.on.ca/DBLaws/Source/Regs/English/2006/R06111_e.htm # http://www2.publicationsduquebec.gouv.qc.ca/dynamicSearch/telecharge.php?type=5&file=2006C39A.PDF # ... # P.E.I. will follow US rules.... # http://www.assembly.pe.ca/bills/pdf_chapter/62/3/chapter-41.pdf # ... # Province of Newfoundland and Labrador.... # http://www.hoa.gov.nl.ca/hoa/bills/Bill0634.htm # ... # Yukon # https://www.gov.yk.ca/legislation/regs/oic2006_127.pdf # ... # N.W.T. will follow US rules. Whoever maintains the government web site # does not seem to believe in bookmarks. To see the news release, click the # following link and search for "Daylight Savings Time Change". Press the # "Daylight Savings Time Change" link; it will fire off a popup using # JavaScript. # http://www.exec.gov.nt.ca/currentnews/currentPR.asp?mode=archive # ... # Nunavut # An amendment to the Interpretation Act was registered on February 19/2007.... # http://action.attavik.ca/home/justice-gn/attach/2007/gaz02part2.pdf # From Paul Eggert (2014-10-18): # H. David Matthews and Mary Vincent's map # "It's about TIME", _Canadian Geographic_ (September-October 1998) # https://web.archive.org/web/19990827055050/https://canadiangeographic.ca/SO98/geomap.htm # contains detailed boundaries for regions observing nonstandard # time and daylight saving time arrangements in Canada circa 1998. # # National Research Council Canada maintains info about time zones and DST. # https://www.nrc-cnrc.gc.ca/eng/services/time/time_zones.html # https://www.nrc-cnrc.gc.ca/eng/services/time/faq/index.html#Q5 # Its unofficial information is often taken from Matthews and Vincent. # From Paul Eggert (2006-06-27): # For now, assume all of DST-observing Canada will fall into line with the # new US DST rules, # From Chris Walton (2011-12-01) # In the first of Tammy Hardwick's articles # http://www.ilovecreston.com/?p=articles&t=spec&ar=260 # she quotes the Friday November 1/1918 edition of the Creston Review. # The quote includes these two statements: # 'Sunday the CPR went back to the old system of time...' # '... The daylight saving scheme was dropped all over Canada at the same time,' # These statements refer to a transition from daylight time to standard time # that occurred nationally on Sunday October 27/1918. This transition was # also documented in the Saturday October 26/1918 edition of the Toronto Star. # In light of that evidence, we alter the date from the earlier believed # Oct 31, to Oct 27, 1918 (and Sunday is a more likely transition day # than Thursday) in all Canadian rulesets. # Rule NAME FROM TO - IN ON AT SAVE LETTER/S Rule Canada 1918 only - Apr 14 2:00 1:00 D Rule Canada 1918 only - Oct 27 2:00 0 S Rule Canada 1942 only - Feb 9 2:00 1:00 W # War Rule Canada 1945 only - Aug 14 23:00u 1:00 P # Peace Rule Canada 1945 only - Sep 30 2:00 0 S Rule Canada 1974 1986 - Apr lastSun 2:00 1:00 D Rule Canada 1974 2006 - Oct lastSun 2:00 0 S Rule Canada 1987 2006 - Apr Sun>=1 2:00 1:00 D Rule Canada 2007 max - Mar Sun>=8 2:00 1:00 D Rule Canada 2007 max - Nov Sun>=1 2:00 0 S # Newfoundland and Labrador # From Paul Eggert (2017-10-14): # Legally Labrador should observe Newfoundland time; see: # McLeod J. Labrador time - legal or not? St. John's Telegram, 2017-10-07 # http://www.thetelegram.com/news/local/labrador-time--legal-or-not-154860/ # Matthews and Vincent (1998) write that the only part of Labrador # that follows the rules is the southeast corner, including Port Hope # Simpson and Mary's Harbour, but excluding, say, Black Tickle. # Rule NAME FROM TO - IN ON AT SAVE LETTER/S Rule StJohns 1917 only - Apr 8 2:00 1:00 D Rule StJohns 1917 only - Sep 17 2:00 0 S # Whitman gives 1919 Apr 5 and 1920 Apr 5; go with Shanks & Pottenger. Rule StJohns 1919 only - May 5 23:00 1:00 D Rule StJohns 1919 only - Aug 12 23:00 0 S # For 1931-1935 Whitman gives Apr same date; go with Shanks & Pottenger. Rule StJohns 1920 1935 - May Sun>=1 23:00 1:00 D Rule StJohns 1920 1935 - Oct lastSun 23:00 0 S # For 1936-1941 Whitman gives May Sun>=8 and Oct Sun>=1; go with Shanks & # Pottenger. Rule StJohns 1936 1941 - May Mon>=9 0:00 1:00 D Rule StJohns 1936 1941 - Oct Mon>=2 0:00 0 S # Whitman gives the following transitions: # 1942 03-01/12-31, 1943 05-30/09-05, 1944 07-10/09-02, 1945 01-01/10-07 # but go with Shanks & Pottenger and assume they used Canadian rules. # For 1946-9 Whitman gives May 5,4,9,1 - Oct 1,5,3,2, and for 1950 he gives # Apr 30 - Sep 24; go with Shanks & Pottenger. Rule StJohns 1946 1950 - May Sun>=8 2:00 1:00 D Rule StJohns 1946 1950 - Oct Sun>=2 2:00 0 S Rule StJohns 1951 1986 - Apr lastSun 2:00 1:00 D Rule StJohns 1951 1959 - Sep lastSun 2:00 0 S Rule StJohns 1960 1986 - Oct lastSun 2:00 0 S # From Paul Eggert (2000-10-02): # INMS (2000-09-12) says that, since 1988 at least, Newfoundland switches # at 00:01 local time. For now, assume it started in 1987. # From Michael Pelley (2011-09-12): # We received today, Monday, September 12, 2011, notification that the # changes to the Newfoundland Standard Time Act have been proclaimed. # The change in the Act stipulates that the change from Daylight Savings # Time to Standard Time and from Standard Time to Daylight Savings Time # now occurs at 2:00AM. # ... # http://www.assembly.nl.ca/legislation/sr/annualstatutes/2011/1106.chp.htm # ... # MICHAEL PELLEY | Manager of Enterprise Architecture - Solution Delivery # Office of the Chief Information Officer # Executive Council # Government of Newfoundland & Labrador Rule StJohns 1987 only - Apr Sun>=1 0:01 1:00 D Rule StJohns 1987 2006 - Oct lastSun 0:01 0 S Rule StJohns 1988 only - Apr Sun>=1 0:01 2:00 DD Rule StJohns 1989 2006 - Apr Sun>=1 0:01 1:00 D Rule StJohns 2007 2011 - Mar Sun>=8 0:01 1:00 D Rule StJohns 2007 2010 - Nov Sun>=1 0:01 0 S # # St John's has an apostrophe, but POSIX file names can't have apostrophes. # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone America/St_Johns -3:30:52 - LMT 1884 -3:30:52 StJohns N%sT 1918 -3:30:52 Canada N%sT 1919 -3:30:52 StJohns N%sT 1935 Mar 30 -3:30 StJohns N%sT 1942 May 11 -3:30 Canada N%sT 1946 -3:30 StJohns N%sT 2011 Nov -3:30 Canada N%sT # most of east Labrador # The name 'Happy Valley-Goose Bay' is too long; use 'Goose Bay'. # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone America/Goose_Bay -4:01:40 - LMT 1884 # Happy Valley-Goose Bay -3:30:52 - NST 1918 -3:30:52 Canada N%sT 1919 -3:30:52 - NST 1935 Mar 30 -3:30 - NST 1936 -3:30 StJohns N%sT 1942 May 11 -3:30 Canada N%sT 1946 -3:30 StJohns N%sT 1966 Mar 15 2:00 -4:00 StJohns A%sT 2011 Nov -4:00 Canada A%sT # west Labrador, Nova Scotia, Prince Edward I, # Îles-de-la-Madeleine, Listuguj reserve # From Brian Inglis (2015-07-20): # From the historical weather station records available at: # https://weatherspark.com/history/28351/1971/Sydney-Nova-Scotia-Canada # Sydney shares the same time history as Glace Bay, so was # likely to be the same across the island.... # Sydney, as the capital and most populous location, or Cape Breton, would # have been better names for the zone had we known this in 1996. # From Paul Eggert (2015-07-20): # Shanks & Pottenger write that since 1970 most of this region has been like # Halifax. Many locales did not observe peacetime DST until 1972; # the Cape Breton area, represented by Glace Bay, is the largest we know of # (Glace Bay was perhaps not the best name choice but no point changing now). # Shanks & Pottenger also write that Liverpool, NS was the only town # in Canada to observe DST in 1971 but not 1970; for now we'll assume # this is a typo. # From Jeffery Nichols (2020-01-09): # America/Halifax ... also applies to Îles-de-la-Madeleine and the Listuguj # reserve in Quebec. Officially, this came into effect on January 1, 2007 # (Legal Time Act, CQLR c T-5.1), but the legislative debates surrounding that # bill say that it is "accommodating the customs and practices" of those # regions, which suggests that they have always been in-line with Halifax. # Rule NAME FROM TO - IN ON AT SAVE LETTER/S Rule Halifax 1916 only - Apr 1 0:00 1:00 D Rule Halifax 1916 only - Oct 1 0:00 0 S Rule Halifax 1920 only - May 9 0:00 1:00 D Rule Halifax 1920 only - Aug 29 0:00 0 S Rule Halifax 1921 only - May 6 0:00 1:00 D Rule Halifax 1921 1922 - Sep 5 0:00 0 S Rule Halifax 1922 only - Apr 30 0:00 1:00 D Rule Halifax 1923 1925 - May Sun>=1 0:00 1:00 D Rule Halifax 1923 only - Sep 4 0:00 0 S Rule Halifax 1924 only - Sep 15 0:00 0 S Rule Halifax 1925 only - Sep 28 0:00 0 S Rule Halifax 1926 only - May 16 0:00 1:00 D Rule Halifax 1926 only - Sep 13 0:00 0 S Rule Halifax 1927 only - May 1 0:00 1:00 D Rule Halifax 1927 only - Sep 26 0:00 0 S Rule Halifax 1928 1931 - May Sun>=8 0:00 1:00 D Rule Halifax 1928 only - Sep 9 0:00 0 S Rule Halifax 1929 only - Sep 3 0:00 0 S Rule Halifax 1930 only - Sep 15 0:00 0 S Rule Halifax 1931 1932 - Sep Mon>=24 0:00 0 S Rule Halifax 1932 only - May 1 0:00 1:00 D Rule Halifax 1933 only - Apr 30 0:00 1:00 D Rule Halifax 1933 only - Oct 2 0:00 0 S Rule Halifax 1934 only - May 20 0:00 1:00 D Rule Halifax 1934 only - Sep 16 0:00 0 S Rule Halifax 1935 only - Jun 2 0:00 1:00 D Rule Halifax 1935 only - Sep 30 0:00 0 S Rule Halifax 1936 only - Jun 1 0:00 1:00 D Rule Halifax 1936 only - Sep 14 0:00 0 S Rule Halifax 1937 1938 - May Sun>=1 0:00 1:00 D Rule Halifax 1937 1941 - Sep Mon>=24 0:00 0 S Rule Halifax 1939 only - May 28 0:00 1:00 D Rule Halifax 1940 1941 - May Sun>=1 0:00 1:00 D Rule Halifax 1946 1949 - Apr lastSun 2:00 1:00 D Rule Halifax 1946 1949 - Sep lastSun 2:00 0 S Rule Halifax 1951 1954 - Apr lastSun 2:00 1:00 D Rule Halifax 1951 1954 - Sep lastSun 2:00 0 S Rule Halifax 1956 1959 - Apr lastSun 2:00 1:00 D Rule Halifax 1956 1959 - Sep lastSun 2:00 0 S Rule Halifax 1962 1973 - Apr lastSun 2:00 1:00 D Rule Halifax 1962 1973 - Oct lastSun 2:00 0 S # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone America/Halifax -4:14:24 - LMT 1902 Jun 15 -4:00 Halifax A%sT 1918 -4:00 Canada A%sT 1919 -4:00 Halifax A%sT 1942 Feb 9 2:00s -4:00 Canada A%sT 1946 -4:00 Halifax A%sT 1974 -4:00 Canada A%sT Zone America/Glace_Bay -3:59:48 - LMT 1902 Jun 15 -4:00 Canada A%sT 1953 -4:00 Halifax A%sT 1954 -4:00 - AST 1972 -4:00 Halifax A%sT 1974 -4:00 Canada A%sT # New Brunswick # From Paul Eggert (2007-01-31): # The Time Definition Act # says they changed at 00:01 through 2006, and # makes it # clear that this was the case since at least 1993. # For now, assume it started in 1993. # Rule NAME FROM TO - IN ON AT SAVE LETTER/S Rule Moncton 1933 1935 - Jun Sun>=8 1:00 1:00 D Rule Moncton 1933 1935 - Sep Sun>=8 1:00 0 S Rule Moncton 1936 1938 - Jun Sun>=1 1:00 1:00 D Rule Moncton 1936 1938 - Sep Sun>=1 1:00 0 S Rule Moncton 1939 only - May 27 1:00 1:00 D Rule Moncton 1939 1941 - Sep Sat>=21 1:00 0 S Rule Moncton 1940 only - May 19 1:00 1:00 D Rule Moncton 1941 only - May 4 1:00 1:00 D Rule Moncton 1946 1972 - Apr lastSun 2:00 1:00 D Rule Moncton 1946 1956 - Sep lastSun 2:00 0 S Rule Moncton 1957 1972 - Oct lastSun 2:00 0 S Rule Moncton 1993 2006 - Apr Sun>=1 0:01 1:00 D Rule Moncton 1993 2006 - Oct lastSun 0:01 0 S # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone America/Moncton -4:19:08 - LMT 1883 Dec 9 -5:00 - EST 1902 Jun 15 -4:00 Canada A%sT 1933 -4:00 Moncton A%sT 1942 -4:00 Canada A%sT 1946 -4:00 Moncton A%sT 1973 -4:00 Canada A%sT 1993 -4:00 Moncton A%sT 2007 -4:00 Canada A%sT # Quebec # From Paul Eggert (2020-01-10): # See America/Toronto for most of Quebec, including Montreal. # See America/Halifax for the Îles de la Madeleine and the Listuguj reserve. # See America/Puerto_Rico for east of Natashquan. # Ontario # From Mark Brader (2003-07-26): # [According to the Toronto Star] Orillia, Ontario, adopted DST # effective Saturday, 1912-06-22, 22:00; the article mentions that # Port Arthur (now part of Thunder Bay, Ontario) as well as Moose Jaw # have already done so. In Orillia DST was to run until Saturday, # 1912-08-31 (no time mentioned), but it was met with considerable # hostility from certain segments of the public, and was revoked after # only two weeks - I copied it as Saturday, 1912-07-07, 22:00, but # presumably that should be -07-06. (1912-06-19, -07-12; also letters # earlier in June). # # Kenora, Ontario, was to abandon DST on 1914-06-01 (-05-21). # # From Paul Eggert (2017-07-08): # For more on Orillia, see: Daubs K. Bold attempt at daylight saving # time became a comic failure in Orillia. Toronto Star 2017-07-08. # https://www.thestar.com/news/insight/2017/07/08/bold-attempt-at-daylight-saving-time-became-a-comic-failure-in-orillia.html # From Paul Eggert (2025-03-20): # Also see the 1912-06-17 front page of The Evening Sunbeam, # reproduced in: Richardson M. "Daylight saving was a confusing # time in Orillia" in the 2025-03-15 Orillia Matters. Richardson writes, # "The first Sunday after the switch was made, [DST proponent and # Orillia mayor William Sword] Frost walked into church an hour late. # This became a symbol of the downfall of daylight saving in Orillia." # The mayor became known as "Daylight Bill". # https://www.orilliamatters.com/local-news/column-daylight-saving-was-a-confusing-time-in-orillia-10377529 # From Mark Brader (2010-03-06): # # In the (Toronto) Globe and Mail for Saturday, 1955-09-24, in the bottom # right corner of page 1, it says that Toronto will return to standard # time at 2 am Sunday morning (which agrees with the database), and that: # # The one-hour setback will go into effect throughout most of Ontario, # except in areas like Windsor which remains on standard time all year. # # ... I don't know if Windsor began observing DST when Detroit did, # or in 1974, or on some other date. # # By the way, the article continues by noting that: # # Some cities in the United States have pushed the deadline back # three weeks and will change over from daylight saving in October. # From Chris Walton (2024-01-09): # The [Toronto] changes in 1947, 1948, and 1949 took place at 2:00 a.m. local # time instead of midnight.... Toronto Daily Star - ... # April 2, 1947 - Page 39 ... April 7, 1948 - Page 13 ... # April 2, 1949 - Page 1 ... April 7, 1949 - Page 24 ... # November 25, 1949 - Page 52 ... April 21, 1950 - Page 14 ... # September 19, 1950 - Page 46 ... September 20, 1950 - Page 3 ... # November 24, 1950 - Page 21 # From Arthur David Olson (2010-07-17): # # "Standard Time and Time Zones in Canada" appeared in # The Journal of The Royal Astronomical Society of Canada, # volume 26, number 2 (February 1932) and, as of 2010-07-17, # was available at # http://adsabs.harvard.edu/full/1932JRASC..26...49S # # It includes the text below (starting on page 57): # # A list of the places in Canada using daylight saving time would # require yearly revision. From information kindly furnished by # the provincial governments and by the postmasters in many cities # and towns, it is found that the following places used daylight sav- # ing in 1930. The information for the province of Quebec is definite, # for the other provinces only approximate: # # Province Daylight saving time used # Prince Edward Island Not used. # Nova Scotia In Halifax only. # New Brunswick In St. John only. # Quebec In the following places: # Montreal Lachine # Quebec Mont-Royal # Lévis Iberville # St. Lambert Cap de la Madelèine # Verdun Loretteville # Westmount Richmond # Outremont St. Jérôme # Longueuil Greenfield Park # Arvida Waterloo # Chambly-Canton Beaulieu # Melbourne La Tuque # St. Théophile Buckingham # Ontario Used generally in the cities and towns along # the southerly part of the province. Not # used in the northwesterly part. # Manitoba Not used. # Saskatchewan In Regina only. # Alberta Not used. # British Columbia Not used. # # With some exceptions, the use of daylight saving may be said to be limited # to those cities and towns lying between Quebec city and Windsor, Ont. # Rule NAME FROM TO - IN ON AT SAVE LETTER/S Rule Toronto 1919 only - Mar 30 23:30 1:00 D Rule Toronto 1919 only - Oct 26 0:00 0 S Rule Toronto 1920 only - May 2 2:00 1:00 D Rule Toronto 1920 only - Sep 26 0:00 0 S Rule Toronto 1921 only - May 15 2:00 1:00 D Rule Toronto 1921 only - Sep 15 2:00 0 S Rule Toronto 1922 1923 - May Sun>=8 2:00 1:00 D # Shanks & Pottenger say 1923-09-19; assume it's a typo and that "-16" # was meant. Rule Toronto 1922 1926 - Sep Sun>=15 2:00 0 S Rule Toronto 1924 1927 - May Sun>=1 2:00 1:00 D Rule Toronto 1927 1937 - Sep Sun>=25 2:00 0 S Rule Toronto 1928 1937 - Apr Sun>=25 2:00 1:00 D Rule Toronto 1938 1940 - Apr lastSun 2:00 1:00 D Rule Toronto 1938 1939 - Sep lastSun 2:00 0 S Rule Toronto 1945 1948 - Sep lastSun 2:00 0 S Rule Toronto 1946 1973 - Apr lastSun 2:00 1:00 D Rule Toronto 1949 1950 - Nov lastSun 2:00 0 S Rule Toronto 1951 1956 - Sep lastSun 2:00 0 S # Shanks & Pottenger say Toronto ended DST a week early in 1971, # namely on 1971-10-24, but Mark Brader wrote (2003-05-31) that this # is wrong, and that he had confirmed it by checking the 1971-10-30 # Toronto Star, which said that DST was ending 1971-10-31 as usual. Rule Toronto 1957 1973 - Oct lastSun 2:00 0 S # The Bahamas match Toronto since 1970. # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone America/Toronto -5:17:32 - LMT 1895 -5:00 Canada E%sT 1919 -5:00 Toronto E%sT 1942 Feb 9 2:00s -5:00 Canada E%sT 1946 -5:00 Toronto E%sT 1974 -5:00 Canada E%sT # For Atikokan see America/Panama. # Manitoba # From Paul Eggert (2026-05-08): # For 1916 timestamps America/Winnipeg covers only a small region. See: # Cassidy C. Winnipeg’s 110-year history with daylight time. # Winnipeg Free Press. 2026-05-06. # https://www.winnipegfreepress.com/our-communities/correspondents/2026/05/06/winnipegs-110-year-history-with-daylight-time # Of the 1916 experiment, Cassidy writes: “As rural areas and nearby # urban centres such as Selkirk and Brandon did not adopt DST, the # City of Winnipeg essentially had its own time zone.” Cassidy also # writes that province-wide DST came into effect on 1963-05-12. # # Shanks & Pottenger write that Winnipeg did not observe DST in 1964 and 1965. # Although dubious in the light of Cassidy’s article, we lack a better source. # Perhaps S&P’s data are for the train stations, not for the city? # Also, S&P say Manitoba switched at 02:00 (not 02:00s) starting in 1966. # Since 02:00s is clearly correct for 1967 on, assume 02:00s in 1966 too. # From Rob Douglas (2006-04-06): # the old Manitoba Time Act - as amended by Bill 2, assented to # March 27, 1987 ... said ... # "between two o'clock Central Standard Time in the morning of # the first Sunday of April of each year and two o'clock Central # Standard Time in the morning of the last Sunday of October next # following, one hour in advance of Central Standard Time."... # I believe that the English legislation [of the old time act] had # been assented to (March 22, 1967).... # Also, as far as I can tell, there was no order-in-council varying # the time of Daylight Saving Time for 2005 and so the provisions of # the 1987 version would apply - the changeover was at 2:00 Central # Standard Time (i.e. not until 3:00 Central Daylight Time). # Rule NAME FROM TO - IN ON AT SAVE LETTER/S Rule Winn 1916 only - Apr 23 0:00 1:00 D Rule Winn 1916 only - Sep 17 0:00 0 S Rule Winn 1918 only - Apr 14 2:00 1:00 D Rule Winn 1918 only - Oct 27 2:00 0 S Rule Winn 1937 only - May 16 2:00 1:00 D Rule Winn 1937 only - Sep 26 2:00 0 S Rule Winn 1942 only - Feb 9 2:00 1:00 W # War Rule Winn 1945 only - Aug 14 23:00u 1:00 P # Peace Rule Winn 1945 only - Sep lastSun 2:00 0 S Rule Winn 1946 only - May 12 2:00 1:00 D Rule Winn 1946 only - Oct 13 2:00 0 S Rule Winn 1947 1949 - Apr lastSun 2:00 1:00 D Rule Winn 1947 1949 - Sep lastSun 2:00 0 S Rule Winn 1950 only - May 1 2:00 1:00 D Rule Winn 1950 only - Sep 30 2:00 0 S Rule Winn 1951 1960 - Apr lastSun 2:00 1:00 D Rule Winn 1951 1958 - Sep lastSun 2:00 0 S Rule Winn 1959 only - Oct lastSun 2:00 0 S Rule Winn 1960 only - Sep lastSun 2:00 0 S Rule Winn 1963 only - Apr lastSun 2:00 1:00 D Rule Winn 1963 only - Sep 22 2:00 0 S Rule Winn 1966 1986 - Apr lastSun 2:00s 1:00 D Rule Winn 1966 2005 - Oct lastSun 2:00s 0 S Rule Winn 1987 2005 - Apr Sun>=1 2:00s 1:00 D # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone America/Winnipeg -6:28:36 - LMT 1887 Jul 16 -6:00 Winn C%sT 2006 -6:00 Canada C%sT # Saskatchewan # From Mark Brader (2003-07-26): # The first actual adoption of DST in Canada was at the municipal # level. As the [Toronto] Star put it (1912-06-07), "While people # elsewhere have long been talking of legislation to save daylight, # the city of Moose Jaw [Saskatchewan] has acted on its own hook." # DST in Moose Jaw began on Saturday, 1912-06-01 (no time mentioned: # presumably late evening, as below), and would run until "the end of # the summer". The discrepancy between municipal time and railroad # time was noted. # From Paul Eggert (2003-07-27): # Willett (1914-03) notes that DST "has been in operation ... in the # City of Moose Jaw, Saskatchewan, for one year." # From Paul Eggert (2019-07-25): # Pearce's book says Regina observed DST in 1914-1917. No dates and times, # unfortunately. It also says that in 1914 Saskatoon observed DST # from 1 June to 6 July, and that DST was also tried out in Davidson, # Melfort, and Prince Albert. # From Paul Eggert (2006-03-22): # Shanks & Pottenger say that since 1970 this region has mostly been as Regina. # Some western towns (e.g. Swift Current) switched from MST/MDT to CST in 1972. # Other western towns (e.g. Lloydminster) are like Edmonton. # Matthews and Vincent (1998) write that Denare Beach and Creighton # are like Winnipeg, in violation of Saskatchewan law. # From W. Jones (1992-11-06): # The. . .below is based on information I got from our law library, the # provincial archives, and the provincial Community Services department. # A precise history would require digging through newspaper archives, and # since you didn't say what you wanted, I didn't bother. # # Saskatchewan is split by a time zone meridian (105W) and over the years # the boundary became pretty ragged as communities near it reevaluated # their affiliations in one direction or the other. In 1965 a provincial # referendum favoured legislating common time practices. # # On 15 April 1966 the Time Act (c. T-14, Revised Statutes of # Saskatchewan 1978) was proclaimed, and established that the eastern # part of Saskatchewan would use CST year round, that districts in # northwest Saskatchewan would by default follow CST but could opt to # follow Mountain Time rules (thus 1 hour difference in the winter and # zero in the summer), and that districts in southwest Saskatchewan would # by default follow MT but could opt to follow CST. # # It took a few years for the dust to settle (I know one story of a town # on one time zone having its school in another, such that a mom had to # serve her family lunch in two shifts), but presently it seems that only # a few towns on the border with Alberta (e.g. Lloydminster) follow MT # rules any more; all other districts appear to have used CST year round # since sometime in the 1960s. # From Chris Walton (2006-06-26): # The Saskatchewan time act which was last updated in 1996 is about 30 pages # long and rather painful to read. # http://www.qp.gov.sk.ca/documents/English/Statutes/Statutes/T14.pdf # From Heitor David Pinto (2026-05-14): # In Saskatchewan, a bill was passed to replace the Time Act. It sets UTC-6 all # year in the whole province, including Lloydminster, but allows the government # to issue regulations specifying a different time in localities that request # so: # https://docs.legassembly.sk.ca/legdocs/Bills/30L2S/Bill30-58.pdf # The bill ... received royal assent today. # From Tim Parenti (2026-05-14): # In light of Alberta joining Saskatchewan on year-round -06, this simplifies # the prior Act's framework for the many local exceptions to year-round -06 in # border areas, by extending province-wide the notion of "time option areas" # which can be prescribed by regulation "if it is in the provincial interest" # for those areas to observe either "UTC-5, UTC-6 or UTC-7 for all or part of # the year" on at least 30 days' notice. # The new Act comes into force by order of the Lieutenant Governor in Council. # Rule NAME FROM TO - IN ON AT SAVE LETTER/S Rule Regina 1918 only - Apr 14 2:00 1:00 D Rule Regina 1918 only - Oct 27 2:00 0 S Rule Regina 1930 1934 - May Sun>=1 0:00 1:00 D Rule Regina 1930 1934 - Oct Sun>=1 0:00 0 S Rule Regina 1937 1941 - Apr Sun>=8 0:00 1:00 D Rule Regina 1937 only - Oct Sun>=8 0:00 0 S Rule Regina 1938 only - Oct Sun>=1 0:00 0 S Rule Regina 1939 1941 - Oct Sun>=8 0:00 0 S Rule Regina 1942 only - Feb 9 2:00 1:00 W # War Rule Regina 1945 only - Aug 14 23:00u 1:00 P # Peace Rule Regina 1945 only - Sep lastSun 2:00 0 S Rule Regina 1946 only - Apr Sun>=8 2:00 1:00 D Rule Regina 1946 only - Oct Sun>=8 2:00 0 S Rule Regina 1947 1957 - Apr lastSun 2:00 1:00 D Rule Regina 1947 1957 - Sep lastSun 2:00 0 S Rule Regina 1959 only - Apr lastSun 2:00 1:00 D Rule Regina 1959 only - Oct lastSun 2:00 0 S # Rule Swift 1957 only - Apr lastSun 2:00 1:00 D Rule Swift 1957 only - Oct lastSun 2:00 0 S Rule Swift 1959 1961 - Apr lastSun 2:00 1:00 D Rule Swift 1959 only - Oct lastSun 2:00 0 S Rule Swift 1960 1961 - Sep lastSun 2:00 0 S # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone America/Regina -6:58:36 - LMT 1905 Sep -7:00 Regina M%sT 1960 Apr lastSun 2:00 -6:00 - CST Zone America/Swift_Current -7:11:20 - LMT 1905 Sep -7:00 Canada M%sT 1946 Apr lastSun 2:00 -7:00 Regina M%sT 1950 -7:00 Swift M%sT 1972 Apr lastSun 2:00 -6:00 - CST # Alberta # From Alois Treindl (2019-07-19): # There was no DST in Alberta in 1967... Calgary Herald, 29 April 1967. # 1969, no DST, from Edmonton Journal 18 April 1969 # # From Paul Eggert (2019-07-25): # Pearce's book says that Alberta's 1948 Daylight Saving Act required # Mountain Standard Time without DST, and that "anyone who broke that law # could be fined up to $25 and costs". There seems to be no record of # anybody paying the fine. The law was not changed until an August 1971 # plebiscite reinstituted DST in 1972. This story is also mentioned in: # Boyer JP. Forcing Choice: The Risky Reward of Referendums. Dundum. 2017. # ISBN 978-1459739123. # From Roozbeh Pournader (2026-04-20): # https://calgaryherald.com/opinion/columnists/bell-alberta-daylight-time-year-round-premier-danielle-smith # # From Tim Parenti (2026-04-23): # Section 3 of Bill 31, the Red Tape Reduction Statutes Amendment Act, 2026 # https://docs.assembly.ab.ca/LADDAR_files/docs/bills/bill/legislature_31/session_2/20251023_bill-031.pdf # would repeal the Daylight Saving Time Act in the Revised Statutes of Alberta # 2000 Chapter D-5: # https://kings-printer.alberta.ca/documents/Acts/D05.pdf # ...and substitutes a new chapter with language that closely parallels the # original. The new title is the Official Time Act and will be numbered # Chapter O-5.7. The Act establishes a standard time of UTC−6 without # replacing the language previously used to effectuate DST. # # From Tim Parenti (2026-06-19): # After receiving Royal Assent on 2026-05-14, Order in Council 204/2026 was # issued on 2026-06-18 proclaiming the relevant section of the bill in force on # the same date. Order in Council 206/2026, issued the same day, uses the # regulatory authority within the Act to prescribe the official term "Alberta # Time"; we use the traditional abbreviation CST for consistency. # https://kings-printer.alberta.ca/Documents/Orders/Orders_in_Council/2026/2026_204.pdf # https://kings-printer.alberta.ca/Documents/Orders/Orders_in_Council/2026/2026_206.pdf # # Since wall clock times do not diverge from past practice until 2026-11-01, # use that transition date for now to work around potential CLDR limitations in # the meantime; see British Columbia, below. # # From Paul Eggert (2026-07-02): # The temporary hack for Alberta is needed for CLDR 48.2 (2026-03-17) # and earlier, not the CLDR 48.1-and-earlier which drives BC’s temporary hack. # Only a few platforms track minor CLDR releases, though, so the two # temporary hacks have roughly the same effect in practice. # # The term “Alberta Time” is legally prescribed from yesterday until # 2031-06-30, when the regulation in OiC 206/2026 expires to ensure that the # term is reviewed by then for relevancy and need. This plan for possible # obsolescence affects neither timekeeping nor TZDB’s data, which do # not contain the string “Alberta Time”. # Rule NAME FROM TO - IN ON AT SAVE LETTER/S Rule Edm 1918 1919 - Apr Sun>=8 2:00 1:00 D Rule Edm 1918 only - Oct 27 2:00 0 S Rule Edm 1919 only - May 27 2:00 0 S Rule Edm 1920 1923 - Apr lastSun 2:00 1:00 D Rule Edm 1920 only - Oct lastSun 2:00 0 S Rule Edm 1921 1923 - Sep lastSun 2:00 0 S Rule Edm 1942 only - Feb 9 2:00 1:00 W # War Rule Edm 1945 only - Aug 14 23:00u 1:00 P # Peace Rule Edm 1945 only - Sep lastSun 2:00 0 S Rule Edm 1947 only - Apr lastSun 2:00 1:00 D Rule Edm 1947 only - Sep lastSun 2:00 0 S Rule Edm 1972 1986 - Apr lastSun 2:00 1:00 D Rule Edm 1972 2006 - Oct lastSun 2:00 0 S # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone America/Edmonton -7:33:52 - LMT 1906 Sep -7:00 Edm M%sT 1987 -7:00 Canada M%sT 2026 Jun 18 # Temporary hack; see above. -7:00 1:00 MDT 2026 Nov 1 2:00 # End of temporary hack. -6:00 - CST # British Columbia # From Paul Eggert (2006-03-22): # Shanks & Pottenger write that since 1970 most of this region has # been like Vancouver. # Dawson Creek uses MST. Much of east BC is like Edmonton. # From Matt Johnson (2015-09-21): # Fort Nelson, BC, Canada will cancel DST this year. So while previously they # were aligned with America/Vancouver, they're now aligned with # America/Dawson_Creek. # http://www.northernrockies.ca/EN/meta/news/archives/2015/northern-rockies-time-change.html # # From Tim Parenti (2015-09-23): # This requires a new zone for the Northern Rockies Regional Municipality, # America/Fort_Nelson. The resolution of 2014-12-08 was reached following a # 2014-11-15 poll with nearly 75% support. Effectively, the municipality has # been on MST (-0700) like Dawson Creek since it advanced its clocks on # 2015-03-08. # # From Paul Eggert (2019-07-25): # Shanks says Fort Nelson did not observe DST in 1946, unlike Vancouver. # Alois Treindl confirmed this on 07-22, citing the 1946-04-27 Vancouver Daily # Province. He also cited the 1946-09-28 Victoria Daily Times, which said # that Vancouver, Victoria, etc. "change at midnight Saturday"; for now, # guess they meant 02:00 Sunday since 02:00 was common practice in Vancouver. # # Early Vancouver, Volume Four, by Major J.S. Matthews, V.D., 2011 edition # says that a 1922 plebiscite adopted DST, but a 1923 plebiscite rejected it. # http://former.vancouver.ca/ctyclerk/archives/digitized/EarlyVan/SearchEarlyVan/Vol4pdf/MatthewsEarlyVancouverVol4_DaylightSavings.pdf # A catalog entry for a newspaper clipping seems to indicate that Vancouver # observed DST in 1941 from 07-07 through 09-27; see # https://searcharchives.vancouver.ca/daylight-saving-1918-starts-again-july-7-1941-start-d-s-sept-27-end-of-d-s-1941 # We have no further details, so omit them for now. # From Arthur David Olson (2026-03-02): # B. C. Gov News: “Adopting permanent daylight saving time: ‘Spring forward’ # on March 8 will be the last time change, ending twice-yearly clock changes.” # https://news.gov.bc.ca/releases/2026AG0013-000209 # # From Paul Eggert (2026-07-02): # The law says that 21 hours after the usual 2026-03-08 02:00 switch from # PST to PDT, the next day inaugurates the new standard time Pacific Time, # i.e., just one clock change but two name changes separated by 21 hours. # PT, the obvious abbreviation for Pacific Time, is one letter too short # to conform to TZDB’s (and POSIX’s) [-+[:alnum:]]{3,6} requirements. # I asked the BC government for advice, with no response. For now, do this: # 1. As a temporary hack, pretend that the BC law takes effect # not on 2026-03-09 at 00:00, but on 2026-11-01 at 02:00. # This pretense works around a limitation in CLDR 48.1 (2026-01-08), # which would otherwise say the interval uses “Pacific Standard Time”. # (Below, this temporary hack is marked “Temporary hack; see above.”) # Strictly speaking this hack is incorrect since the interval uses # standard time, but it does have the right UT offset and it # works around the CLDR limitation. We should be able to remove # the temporary hack by November when there would be little point # to keeping it anyway. # 2. After the BC law takes effect, model the time as MST sans DST. # We can change this later if another conforming non-numeric abbreviation # for Pacific Time becomes more popular. Possibilities include: # MST - the most compatible with existing software and practice, # and already used in parts of BC and in Yukon # PDT - almost as software-friendly, but confusing because it implies # it is DST and is paired with PST, whereas PT is standard time # PST - straightforward but even more confusing, # and will likely break much software that assumes PST is -08 # -07 - accurate and clear in itself, but makes BC look odd vs neighbors # PacT - straightforward but novel abbreviation for Pacific Time # From Chris Walton (2026-03-15): # The Regional District of East Kootenay is planning to move to year-round # Mountain Standard Time (MST) on November 1, 2026.... # https://www.rdek.bc.ca/news/entry/rdek_board_moves_to_transition_to_year_round_mountain_standard_time # (2026-03-17): # The final decision East Kootenay made a few days ago may turn out not to # be final after all. They are going to reopen the debate next month! # https://www.cbc.ca/news/canada/british-columbia/what-time-is-it-in-the-east-kootenay-debate-9.7132624 # From Paul Eggert (2026-03-17): # Mayor Steve Fairbairn of Elkford asked the question be called a second time, # saying, “Pardon the pun, but this is not a time-sensitive issue.” # For now, merely mention the potential change in these comments. # If it happens it would likely affect clocks starting 2027-03-14 at 02:00. # From Tim Parenti (2026-05-14): # RDEK has historically been aligned with neighboring Alberta. With the latter # now opting to stay on -06 year-round, if RDEK does not follow, it would # require a new zone for the Cranbrook area. # Rule NAME FROM TO - IN ON AT SAVE LETTER/S Rule Vanc 1918 only - Apr 14 2:00 1:00 D Rule Vanc 1918 only - Oct 27 2:00 0 S Rule Vanc 1942 only - Feb 9 2:00 1:00 W # War Rule Vanc 1945 only - Aug 14 23:00u 1:00 P # Peace Rule Vanc 1945 only - Sep 30 2:00 0 S Rule Vanc 1946 1986 - Apr lastSun 2:00 1:00 D Rule Vanc 1946 only - Sep 29 2:00 0 S Rule Vanc 1947 1961 - Sep lastSun 2:00 0 S Rule Vanc 1962 2006 - Oct lastSun 2:00 0 S # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone America/Vancouver -8:12:28 - LMT 1884 -8:00 Vanc P%sT 1987 -8:00 Canada P%sT 2026 Mar 9 # Temporary hack; see above. -8:00 1:00 PDT 2026 Nov 1 2:00 # End of temporary hack. -7:00 - MST Zone America/Dawson_Creek -8:00:56 - LMT 1884 -8:00 Canada P%sT 1947 -8:00 Vanc P%sT 1972 Aug 30 2:00 -7:00 - MST Zone America/Fort_Nelson -8:10:47 - LMT 1884 -8:00 Vanc P%sT 1946 -8:00 - PST 1947 -8:00 Vanc P%sT 1987 -8:00 Canada P%sT 2015 Mar 8 2:00 -7:00 - MST # For Creston see America/Phoenix. # Northwest Territories, Nunavut, Yukon # From Chris Walton (2022-11-06): # Whitehorse Star - Thursday April 22, 1965 - page 1 # title: DST Starts Monday ... # https://www.newspapers.com/image/578587481/ # The title of this first article is wrong and/or misleading. # Also, the start time shown in the article is vague; it simply says "after # midnight" when it probably should have stated 2:00a.m.... # # Whitehorse Star - Monday October 25, 1965 - page 15 ... # https://www.newspapers.com/image/578589147/ # The 1965 Yukon Council minutes can be found here: # http://assets.yukonarchives.ca/PER_YG_06_1965_C20_S02_v1.pdf # ... I do not currently believe that NWT touched any of its clocks in 1965.... # # Whitehorse Star - Thursday Feb 24,1966 - page 2 # title: It's Time for YDT ... # https://www.newspapers.com/image/578575979/ ... # America/Whitehorse as a permanent change from UTC-9(YST) to # UTC-8(PST) at 00:00 on Sunday February 27, 1966.... # # Whitehorse Star - Friday April 28,1972 - page 6 # title: Daylight Saving Time for N.W.T.... # https://www.newspapers.com/image/578701610/ ... # Nunavut and NWT zones ... DST starting in 1972.... Start and End ... # should be the same as the rest of Canada # # # From Paul Eggert (2022-11-06): # For now, assume Yukon's 1965-04-22 spring forward was 00:00 -> 02:00, as this # seems likely than 02:00 -> 04:00 and matches "after midnight". # From Paul Eggert (2006-03-22): # Dawson switched to PST in 1973. Inuvik switched to MST in 1979. # Mathew Englander (1996-10-07) gives the following refs: # * 1967. Paragraph 28(34)(g) of the Interpretation Act, S.C. 1967-68, # c. 7 defines Yukon standard time as UTC-9.... # see Interpretation Act, R.S.C. 1985, c. I-21, s. 35(1). # [https://www.canlii.org/en/ca/laws/stat/rsc-1985-c-i-21/latest/rsc-1985-c-i-21.html] # * C.O. 1973/214 switched Yukon to PST on 1973-10-28 00:00. # * O.I.C. 1980/02 established DST. # * O.I.C. 1987/056 changed DST to Apr firstSun 2:00 to Oct lastSun 2:00. # From Brian Inglis (2015-04-14): # # I tried to trace the history of Yukon time and found the following # regulations, giving the reference title and URL if found, regulation name, # and relevant quote if available. Each regulation specifically revokes its # predecessor. The final reference is to the current Interpretation Act # authorizing and resulting from these regulatory changes. # # Only recent regulations were retrievable via Yukon government site search or # index, and only some via Canadian legal sources. Other sources used include # articles titled "Standard Time and Time Zones in Canada" from JRASC via ADS # Abstracts, cited by ADO for 1932 ..., and updated versions from 1958 and # 1970 quoted below; each article includes current extracts from provincial # and territorial ST and DST regulations at the end, summaries and details of # standard times and daylight saving time at many locations across Canada, # with time zone maps, tables and calculations for Canadian Sunrise, Sunset, # and LMST; they also cover many countries and global locations, with a chart # and table showing current Universal Time offsets, and may be useful as # another source of information for 1970 and earlier. # # * Standard Time and Time Zones in Canada; Smith, C.C.; JRASC, Vol. 26, # pp.49-77; February 1932; SAO/NASA Astrophysics Data System (ADS) # http://adsabs.harvard.edu/abs/1932JRASC..26...49S from p.75: # Yukon Interpretation Ordinance # Yukon standard time is the local mean time at the one hundred and # thirty-fifth meridian. # # * Standard Time and Time Zones in Canada; Smith, C.C.; Thomson, Malcolm M.; # JRASC, Vol. 52, pp.193-223; October 1958; SAO/NASA Astrophysics Data System # (ADS) http://adsabs.harvard.edu/abs/1958JRASC..52..193S from pp.220-1: # Yukon Interpretation Ordinance, 1955, Chap. 16. # # (1) Subject to this section, standard time shall be reckoned as nine # hours behind Greenwich Time and called Yukon Standard Time. # # (2) Notwithstanding subsection (1), the Commissioner may make regulations # varying the manner of reckoning standard time. # # * Yukon Territory Commissioner's Order 1966-20 Interpretation Ordinance # [no online source found] # # * Standard Time and Time Zones in Canada; Thomson, Malcolm M.; JRASC, # Vol. 64, pp.129-162; June 1970; SAO/NASA Astrophysics Data System (ADS) # http://adsabs.harvard.edu/abs/1970JRASC..64..129T from p.156: Yukon # Territory Commissioner's Order 1967-59 Interpretation Ordinance ... # # 1. Commissioner's Order 1966-20 dated at Whitehorse in the Yukon # Territory on 27th January, 1966, is hereby revoked. # # 2. Yukon (East) Standard Time as defined by section 36 of the # Interpretation Ordinance from and after mid-night on the 28th day of May, # 1967 shall be reckoned in the same manner as Pacific Standard Time, that # is to say, eight hours behind Greenwich Time in the area of the Yukon # Territory lying east of the 138th degree longitude west. # # 3. In the remainder of the Territory, lying west of the 138th degree # longitude west, Yukon (West) Standard Time shall be reckoned as nine # hours behind Greenwich Time. # # * Yukon Standard Time defined as Pacific Standard Time, YCO 1973/214 # https://www.canlii.org/en/yk/laws/regu/yco-1973-214/latest/yco-1973-214.html # C.O. 1973/214 INTERPRETATION ACT ... # # 1. Effective October 28, 1973 Commissioner's Order 1967/59 is hereby # revoked. # # 2. Yukon Standard Time as defined by section 36 of the Interpretation # Act from and after midnight on the twenty-eighth day of October, 1973 # shall be reckoned in the same manner as Pacific Standard Time, that is # to say eight hours behind Greenwich Time. # # * O.I.C. 1980/02 INTERPRETATION ACT # https://mm.icann.org/pipermail/tz/attachments/20201125/d5adc93b/CAYTOIC1980-02DST1980-01-04-0001.pdf # # * Yukon Daylight Saving Time, YOIC 1987/56 # https://www.canlii.org/en/yk/laws/regu/yoic-1987-56/latest/yoic-1987-56.html # O.I.C. 1987/056 INTERPRETATION ACT ... # # In every year between # (a) two o'clock in the morning in the first Sunday in April, and # (b) two o'clock in the morning in the last Sunday in October, # Standard Time shall be reckoned as seven hours behind Greenwich Time and # called Yukon Daylight Saving Time. # ... # Dated ... 9th day of March, A.D., 1987. # # * Yukon Daylight Saving Time 2006, YOIC 2006/127 # https://www.canlii.org/en/yk/laws/regu/yoic-2006-127/latest/yoic-2006-127.html # O.I.C. 2006/127 INTERPRETATION ACT ... # # 1. In Yukon each year the time for general purposes shall be 7 hours # behind Greenwich mean time during the period commencing at two o'clock # in the forenoon on the second Sunday of March and ending at two o'clock # in the forenoon on the first Sunday of November and shall be called # Yukon Daylight Saving Time. # # 2. Order-in-Council 1987/56 is revoked. # # 3. This order comes into force January 1, 2007. # # * Interpretation Act, RSY 2002, c 125 # https://www.canlii.org/en/yk/laws/stat/rsy-2002-c-125/latest/rsy-2002-c-125.html # From Chris Walton (2022-11-06): # The 5th edition of the Atlas of Canada contains a time zone map that # shows both legislated and observed time zone boundaries. # All communities on Baffin Island are shown to be observing Eastern time. # The date on the map is 1984. # https://ftp.maps.canada.ca/pub/nrcan_rncan/raster/atlas_5_ed/eng/other/referencemaps/mcr4056.pdf # From Rives McDow (1999-09-04): # Nunavut ... moved ... to incorporate the whole territory into one time zone. # Nunavut moves to single time zone Oct. 31 # http://www.nunatsiaq.com/nunavut/nvt90903_13.html # From Paul Eggert (1999-09-20): # Basic Facts: The New Territory # http://www.nunavut.com/basicfacts/english/basicfacts_1territory.html # (1999) reports that ... Coral Harbour does not observe DST. # From Paul Eggert (2000-10-02): # Matthews and Vincent (1998) say the following, but we lack histories # for these potential new Zones. # # The Canadian Forces station at Alert uses Eastern Time while the # handful of residents at the Eureka weather station [in the Central # zone] skip daylight savings. Baffin Island, which is crossed by the # Central, Eastern and Atlantic Time zones only uses Eastern Time. # Gjoa Haven, Taloyoak and Pelly Bay all use Mountain instead of # Central Time and Southampton Island [in the Central zone] is not # required to use daylight savings. # From # Nunavut now has two time zones (2000-11-10): # The Nunavut government would allow its employees in Kugluktuk and # Cambridge Bay to operate on central time year-round, putting them # one hour behind the rest of Nunavut for six months during the winter. # At the end of October the two communities had rebelled against # Nunavut's unified time zone, refusing to shift to eastern time with # the rest of the territory for the winter. Cambridge Bay remained on # central time, while Kugluktuk, even farther west, reverted to # mountain time, which they had used before the advent of Nunavut's # unified time zone in 1999. # # From Rives McDow (2001-01-20), quoting the Nunavut government: # The preceding decision came into effect at midnight, Saturday Nov 4, 2000. # From Paul Eggert (2000-12-04): # Let's just keep track of the official times for now. # From Rives McDow (2001-03-07): # The premier of Nunavut has issued a ministerial statement advising # that effective 2001-04-01, the territory of Nunavut will revert # back to three time zones (mountain, central, and eastern). Of the # cities in Nunavut, Coral Harbor is the only one that I know of that # has said it will not observe dst, staying on EST year round. I'm # checking for more info, and will get back to you if I come up with # more. # [Also see (2001-03-09).] # From Gwillim Law (2005-05-21): # According to ... # http://www.canadiangeographic.ca/Magazine/SO98/geomap.asp # (from a 1998 Canadian Geographic article), the de facto and de jure time # for Southampton Island (at the north end of Hudson Bay) is UTC-5 all year # round. Using Google, it's easy to find other websites that confirm this. # I wasn't able to find how far back this time regimen goes, but since it # predates the creation of Nunavut, it probably goes back many years.... # The Inuktitut name of Coral Harbour is Sallit, but it's rarely used. # # From Paul Eggert (2014-10-17): # For lack of better information, assume that Southampton Island observed # daylight saving only during wartime. Gwillim Law's email also # mentioned maps now maintained by National Research Council Canada; # see above for an up-to-date link. # From Chris Walton (2007-03-01): # ... the community of Resolute (located on Cornwallis Island in # Nunavut) moved from Central Time to Eastern Time last November. # Basically the community did not change its clocks at the end of # daylight saving.... # http://www.nnsl.com/frames/newspapers/2006-11/nov13_06none.html # From Chris Walton (2011-03-21): # Back in 2007 I initiated the creation of a new "zone file" for Resolute # Bay. Resolute Bay is a small community located about 900km north of # the Arctic Circle. The zone file was required because Resolute Bay had # decided to use UTC-5 instead of UTC-6 for the winter of 2006-2007. # # According to new information which I received last week, Resolute Bay # went back to using UTC-6 in the winter of 2007-2008... # # On March 11/2007 most of Canada went onto daylight saving. On March # 14/2007 I phoned the Resolute Bay hamlet office to do a "time check." I # talked to somebody that was both knowledgeable and helpful. I was able # to confirm that Resolute Bay was still operating on UTC-5. It was # explained to me that Resolute Bay had been on the Eastern Time zone # (EST) in the winter, and was now back on the Central Time zone (CDT). # i.e. the time zone had changed twice in the last year but the clocks # had not moved. The residents had to know which time zone they were in # so they could follow the correct TV schedule... # # On Nov 02/2008 most of Canada went onto standard time. On Nov 03/2008 I # phoned the Resolute Bay hamlet office...[D]ue to the challenging nature # of the phone call, I decided to seek out an alternate source of # information. I found an e-mail address for somebody by the name of # Stephanie Adams whose job was listed as "Inns North Support Officer for # Arctic Co-operatives." I was under the impression that Stephanie lived # and worked in Resolute Bay... # # On March 14/2011 I phoned the hamlet office again. I was told that # Resolute Bay had been using Central Standard Time over the winter of # 2010-2011 and that the clocks had therefore been moved one hour ahead # on March 13/2011. The person I talked to was aware that Resolute Bay # had previously experimented with Eastern Standard Time but he could not # tell me when the practice had stopped. # # On March 17/2011 I searched the Web to find an e-mail address of # somebody that might be able to tell me exactly when Resolute Bay went # off Eastern Standard Time. I stumbled on the name "Aziz Kheraj." Aziz # used to be the mayor of Resolute Bay and he apparently owns half the # businesses including "South Camp Inn." This website has some info on # Aziz: # http://www.uphere.ca/node/493 # # I sent Aziz an e-mail asking when Resolute Bay had stopped using # Eastern Standard Time. # # Aziz responded quickly with this: "hi, The time was not changed for the # 1 year only, the following year, the community went back to the old way # of "spring ahead-fall behind" currently we are zulu plus 5 hrs and in # the winter Zulu plus 6 hrs" # # This of course conflicted with everything I had ascertained in November 2008. # # I sent Aziz a copy of my 2008 e-mail exchange with Stephanie. Aziz # responded with this: "Hi, Stephanie lives in Winnipeg. I live here, You # may want to check with the weather office in Resolute Bay or do a # search on the weather through Env. Canada. web site" # # If I had realized the Stephanie did not live in Resolute Bay I would # never have contacted her. I now believe that all the information I # obtained in November 2008 should be ignored... # I apologize for reporting incorrect information in 2008. # From Tim Parenti (2020-03-05): # The government of Yukon announced [yesterday] the cessation of seasonal time # changes. "After clocks are pushed ahead one hour on March 8, the territory # will remain on [UTC-07]. ... [The government] found 93 per cent of # respondents wanted to end seasonal time changes and, of that group, 70 per # cent wanted 'permanent Pacific Daylight Saving Time.'" # https://www.cbc.ca/news/canada/north/yukon-end-daylight-saving-time-1.5486358 # # Although the government press release prefers PDT, we prefer MST for # consistency with nearby Dawson Creek, Creston, and Fort Nelson. # https://yukon.ca/en/news/yukon-end-seasonal-time-change # From Andrew G. Smith (2020-09-24): # Yukon has completed its regulatory change to be on UTC -7 year-round.... # http://www.gov.yk.ca/legislation/regs/oic2020_125.pdf # What we have done is re-defined Yukon Standard Time, as we are # authorized to do under section 33 of our Interpretation Act: # http://www.gov.yk.ca/legislation/acts/interpretation_c.pdf # # From Paul Eggert (2020-09-24): # tzdb uses the obsolete YST abbreviation for standard time in Yukon through # about 1970, and uses PST for standard time in Yukon since then. Consistent # with that, use MST for -07, the new standard time in Yukon effective Nov. 1. # From Tim Parenti (2026-04-21): # "[Northwest Territories] Premier R.J. Simpson announced Monday that the # territory will move to end seasonal time changes and will adopt a year-round # time standard instead. ... Simpson has previously said the territory wouldn't # end seasonal time changes until Alberta does." # https://www.cbc.ca/news/canada/north/nwt-ends-daylight-saving-9.7170964 # # From Tim Parenti (2026-06-19), per James Bellaire (2026-06-02): # Much of NWT has, to date, been represented by America/Edmonton, which alias # America/Yellowknife links to. While Bill 13 (assented to 2021-03-31) would # enable NWT's proposed change mirroring Alberta's, at time of writing it has # not yet been formally enacted; if it doesn't move forward as expected, # America/Yellowknife would need to become its own zone as Alberta has stopped # changing its clocks. # If it does go ahead, draft changes to America/Inuvik, which represents the # remainder of NWT, are commented below. # Rule NAME FROM TO - IN ON AT SAVE LETTER/S Rule NT_YK 1918 only - Apr 14 2:00 1:00 D Rule NT_YK 1918 only - Oct 27 2:00 0 S Rule NT_YK 1919 only - May 25 2:00 1:00 D Rule NT_YK 1919 only - Nov 1 0:00 0 S Rule NT_YK 1942 only - Feb 9 2:00 1:00 W # War Rule NT_YK 1945 only - Aug 14 23:00u 1:00 P # Peace Rule NT_YK 1945 only - Sep 30 2:00 0 S Rule NT_YK 1972 1986 - Apr lastSun 2:00 1:00 D Rule NT_YK 1972 2006 - Oct lastSun 2:00 0 S Rule NT_YK 1987 2006 - Apr Sun>=1 2:00 1:00 D Rule Yukon 1965 only - Apr lastSun 0:00 2:00 DD Rule Yukon 1965 only - Oct lastSun 2:00 0 S # Zone NAME STDOFF RULES FORMAT [UNTIL] # formerly Frobisher Bay Zone America/Iqaluit 0 - -00 1942 Aug # Frobisher Bay est. -5:00 NT_YK E%sT 1999 Oct 31 2:00 -6:00 Canada C%sT 2000 Oct 29 2:00 -5:00 Canada E%sT # aka Qausuittuq Zone America/Resolute 0 - -00 1947 Aug 31 # Resolute founded -6:00 NT_YK C%sT 2000 Oct 29 2:00 -5:00 - EST 2001 Apr 1 3:00 -6:00 Canada C%sT 2006 Oct 29 2:00 -5:00 - EST 2007 Mar 11 3:00 -6:00 Canada C%sT # aka Kangiqiniq Zone America/Rankin_Inlet 0 - -00 1957 # Rankin Inlet founded -6:00 NT_YK C%sT 2000 Oct 29 2:00 -5:00 - EST 2001 Apr 1 3:00 -6:00 Canada C%sT # aka Iqaluktuuttiaq Zone America/Cambridge_Bay 0 - -00 1920 # trading post est.? -7:00 NT_YK M%sT 1999 Oct 31 2:00 -6:00 Canada C%sT 2000 Oct 29 2:00 -5:00 - EST 2000 Nov 5 0:00 -6:00 - CST 2001 Apr 1 3:00 -7:00 Canada M%sT Zone America/Inuvik 0 - -00 1953 # Inuvik founded -8:00 NT_YK P%sT 1979 Apr lastSun 2:00 -7:00 NT_YK M%sT 1980 -7:00 Canada M%sT # Assuming Northwest Territories follows Alberta in abolishing seasonal time # changes, replace the above line with something like: # -7:00 Canada M%sT 2026 Nov 1 2:00 # -6:00 - CST Zone America/Whitehorse -9:00:12 - LMT 1900 Aug 20 -9:00 NT_YK Y%sT 1965 -9:00 Yukon Y%sT 1966 Feb 27 0:00 -8:00 - PST 1980 -8:00 Canada P%sT 2020 Nov 1 -7:00 - MST Zone America/Dawson -9:17:40 - LMT 1900 Aug 20 -9:00 NT_YK Y%sT 1965 -9:00 Yukon Y%sT 1973 Oct 28 0:00 -8:00 - PST 1980 -8:00 Canada P%sT 2020 Nov 1 -7:00 - MST ############################################################################### # Mexico # From Paul Eggert (2014-12-07): # The Investigation and Analysis Service of the # Mexican Library of Congress (MLoC) has published a # history of Mexican local time (in Spanish) # http://www.diputados.gob.mx/bibliot/publica/inveyana/polisoc/horver/index.htm # # Here are the discrepancies between Shanks & Pottenger (S&P) and the MLoC. # (In all cases we go with the MLoC.) # S&P report that Baja was at -8:00 in 1922/1923. # S&P say the 1930 transition in Baja was 1930-11-16. # S&P report no DST during summer 1931. # S&P report a transition at 1932-03-30 23:00, not 1932-04-01. # From Gwillim Law (2001-02-20): # There are some other discrepancies between the Decrees page and the # tz database. I think they can best be explained by supposing that # the researchers who prepared the Decrees page failed to find some of # the relevant documents. # From Heitor David Pinto (2024-08-04): # In 1931, the decree implementing DST specified that it would take # effect on 30 April.... # https://www.dof.gob.mx/nota_to_imagen_fs.php?cod_diario=192270&pagina=2&seccion=1 # # In 1981, the decree changing Campeche, Yucatán and Quintana Roo to UTC-5 # specified that it would enter into force on 26 December 1981 at 2:00.... # https://www.dof.gob.mx/nota_to_imagen_fs.php?codnota=4705667&fecha=23/12/1981&cod_diario=202796 # # In 1982, the decree returning Campeche and Yucatán to UTC-6 specified that # it would enter into force on 2 November 1982 at 2:00.... # https://www.dof.gob.mx/nota_to_imagen_fs.php?cod_diario=205689&pagina=3&seccion=0 # # Quintana Roo changed to UTC-6 on 4 January 1983 at 0:00, and again # to UTC-5 on 26 October 1997 at 2:00.... # https://www.dof.gob.mx/nota_to_imagen_fs.php?codnota=4787355&fecha=28/12/1982&cod_diario=206112 # https://www.dof.gob.mx/nota_to_imagen_fs.php?cod_diario=209559&pagina=15&seccion=0 # # Durango, Coahuila, Nuevo León and Tamaulipas were set to UTC-7 on 1 January # 1922, and changed to UTC-6 on 10 June 1927. Then Durango, Coahuila and # Nuevo León (but not Tamaulipas) returned to UTC-7 on 15 November 1930, # observed DST in 1931, and changed again to UTC-6 on 1 April 1932.... # https://www.dof.gob.mx/nota_to_imagen_fs.php?codnota=4441846&fecha=29/12/1921&cod_diario=187468 # https://www.dof.gob.mx/nota_to_imagen_fs.php?codnota=4541520&fecha=09/06/1927&cod_diario=193920 # https://www.dof.gob.mx/nota_to_imagen_fs.php?codnota=4491963&fecha=15/11/1930&cod_diario=190835 # https://www.dof.gob.mx/nota_to_imagen_fs.php?codnota=4418437&fecha=21/01/1932&cod_diario=185588 # # ... the ... 10 June 1927 ... decree only said 10 June 1927, without # specifying a time, so I suppose that it should be considered at 0:00. # https://www.dof.gob.mx/nota_to_imagen_fs.php?codnota=4541520&fecha=09/06/1927&cod_diario=193920 # # In 1942, the decree changing Baja California, Baja California Sur, Sonora, # Sinaloa and Nayarit to UTC-7 was published on 24 April, but it said that it # would apply from 1 April, so it's unclear when the change actually # occurred. The database currently shows 24 April 1942. # https://www.dof.gob.mx/nota_to_imagen_fs.php?cod_diario=192203&pagina=2&seccion=1 # # Baja California Sur, Sonora, Sinaloa and Nayarit never used UTC-8. The ... # 14 January 1949 ... change [to UTC-8] only occurred in Baja California. # https://www.dof.gob.mx/nota_to_imagen_fs.php?codnota=4515613&fecha=13/01/1949&cod_diario=192309 # # In 1945, the decree changing Baja California to UTC-8 specified that it # would take effect on the third day from its publication. # It was published on 12 November, so it would take effect on 15 November.... # https://www.dof.gob.mx/nota_to_imagen_fs.php?codnota=4555049&fecha=12/11/1945&cod_diario=194763 # # In 1948, the decree changing Baja California to UTC-7 specified that it # would take effect on "this date". The decree was made on 13 March, # but published on 5 April, so it's unclear when the change actually occurred. # The database currently shows 5 April 1948. # https://www.dof.gob.mx/nota_to_imagen_fs.php?cod_diario=188624&pagina=2&seccion=0 # # In 1949, the decree changing Baja California to UTC-8 was published on 13 # January, but it said that it would apply from 1 January, so it's unclear when # the change actually occurred. The database currently shows 14 January 1949. # https://www.dof.gob.mx/nota_to_imagen_fs.php?codnota=4515613&fecha=13/01/1949&cod_diario=192309 # # Baja California also observed UTC-7 from 1 May to 24 September 1950, # from 29 April to 30 September 1951 at 2:00, # and from 27 April to 28 September 1952 at 2:00.... # https://www.dof.gob.mx/nota_to_imagen_fs.php?codnota=4600403&fecha=29/04/1950&cod_diario=197505 # https://www.dof.gob.mx/nota_to_imagen_fs.php?codnota=4623553&fecha=23/09/1950&cod_diario=198805 # https://www.dof.gob.mx/nota_to_imagen_fs.php?codnota=4469444&fecha=27/04/1951&cod_diario=189317 # https://www.dof.gob.mx/nota_to_imagen_fs.php?codnota=4533868&fecha=10/03/1952&cod_diario=193465 # # All changes in Baja California from 1948 to 1952 match those in California, # on the same dates or with a difference of one day. # So it may be easier to implement these changes as DST with rule CA # during this whole period. # From Alois Treindl (2025-07-29): # I did a quick newspaper archive research on https://hndm.iib.unam.mx/ # and found that Periódico Oficial del Estado de Baja California Norte # (1973-04-20) states clearly that DST was observed from last Sunday # in April to last Sunday in October.... I have a few more data from the # official bulletin for DST begin or end in Baja California 1964 1967 1969 # 1972 1973 (already sent) 1974 1975 1976 I do not know whether it is safe to # assume that it also applied in the years where I did not yet find proof. # The 1974 end of DST contains a reference to an Acuerdo of 1973-dec-20 which # I could not find.... One might assume that Baja California, which followed # US-CA in all these other yours, did the same. # # From Paul Eggert (2025-08-04): # Assume that Tijuana agreed with San Diego from 1953 through 1996, # as this agrees with Alois Treindl's data and with Shanks. # For now, keep the slightly-different 1948/1952 history for Baja California, # as we have no information on whether 1948/1952 clocks in Tijuana followed # the decrees or followed San Diego. # From Mark Schapiro, writing in The Nation (2002-10-28): # https://www.thenation.com/article/archive/sowing-disaster/ # When Mexican clocks were turned back for daylight saving time in the spring, # the Zapotecs refused to make the adjustment, insisting that they live in # "God's time," not in what they derisively call "Fox time," referring to # President Vicente Fox in far-off Mexico City. # From Paul Eggert (2025-08-04): # Unfortunately we have no data to track this informal practice. # From Alan Perry (1996-02-15): # A guy from our Mexico subsidiary finally found the Presidential Decree # outlining the timezone changes in Mexico. # # ------------- Begin Forwarded Message ------------- # # I finally got my hands on the Official Presidential Decree that sets up the # rules for the DST changes. The rules are: # # 1. The country is divided in 3 timezones: # - Baja California Norte (the Mexico/BajaNorte TZ) # - Baja California Sur, Nayarit, Sinaloa and Sonora (the Mexico/BajaSur TZ) # - The rest of the country (the Mexico/General TZ) # # 2. From the first Sunday in April at 2:00 AM to the last Sunday in October # at 2:00 AM, the times in each zone are as follows: # BajaNorte: GMT+7 # BajaSur: GMT+6 # General: GMT+5 # # 3. The rest of the year, the times are as follows: # BajaNorte: GMT+8 # BajaSur: GMT+7 # General: GMT+6 # # The Decree was published in Mexico's Official Newspaper on January 4th. # # -------------- End Forwarded Message -------------- # From Paul Eggert (1996-06-12): # For an English translation of the decree, see # "Diario Oficial: Time Zone Changeover" (1996-01-04). # http://mexico-travel.com/extra/timezone_eng.html # From Rives McDow (1998-10-08): # The State of Quintana Roo has reverted back to central STD and DST times # (i.e. UTC -0600 and -0500 as of 1998-08-02). # From Rives McDow (2000-01-10): # Effective April 4, 1999 at 2:00 AM local time, Sonora changed to the time # zone 5 hours from the International Date Line, and will not observe daylight # savings time so as to stay on the same time zone as the southern part of # Arizona year round. # From Jesper Nørgaard, translating # (2001-01-17): # In Oaxaca, the 55.000 teachers from the Section 22 of the National # Syndicate of Education Workers, refuse to apply daylight saving each # year, so that the more than 10,000 schools work at normal hour the # whole year. # From Gwillim Law (2001-01-19): # ... says # (translated):... # January 17, 2000 - The Energy Secretary, Ernesto Martens, announced # that Summer Time will be reduced from seven to five months, starting # this year.... # http://www.publico.com.mx/scripts/texto3.asp?action=pagina&pag=21&pos=p&secc=naci&date=01/17/2001 # [translated], says "summer time will ... take effect on the first Sunday # in May, and end on the last Sunday of September. # From Arthur David Olson (2001-01-25): # The 2001-01-24 traditional Washington Post contained the page one # story "Timely Issue Divides Mexicans."... # http://www.washingtonpost.com/wp-dyn/articles/A37383-2001Jan23.html # ... Mexico City Mayor López Obrador "...is threatening to keep # Mexico City and its 20 million residents on a different time than # the rest of the country..." In particular, López Obrador would abolish # observation of Daylight Saving Time. # Official statute published by the Energy Department # http://www.conae.gob.mx/ahorro/decretohorver2001.html#decre # (2001-02-01) shows Baja and Chihauhua as still using US DST rules, # and Sonora with no DST. This was reported by Jesper Nørgaard (2001-02-03). # From Paul Eggert (2001-03-03): # # https://www.latimes.com/archives/la-xpm-2001-mar-03-mn-32561-story.html # James F. Smith writes in today's LA Times # * Sonora will continue to observe standard time. # * Last week Mexico City's mayor Andrés Manuel López Obrador decreed that # the Federal District will not adopt DST. # * 4 of 16 district leaders announced they'll ignore the decree. # * The decree does not affect federal-controlled facilities including # the airport, banks, hospitals, and schools. # # For now we'll assume that the Federal District will bow to federal rules. # From Jesper Nørgaard (2001-04-01): # I found some references to the Mexican application of daylight # saving, which modifies what I had already sent you, stating earlier # that a number of northern Mexican states would go on daylight # saving. The modification reverts this to only cover Baja California # (Norte), while all other states (except Sonora, who has no daylight # saving all year) will follow the original decree of president # Vicente Fox, starting daylight saving May 6, 2001 and ending # September 30, 2001. # References: "Diario de Monterrey" # Palabra (2001-03-31) # From Reuters (2001-09-04): # Mexico's Supreme Court on Tuesday declared that daylight savings was # unconstitutional in Mexico City, creating the possibility the # capital will be in a different time zone from the rest of the nation # next year.... The Supreme Court's ruling takes effect at 2:00 # a.m. (0800 GMT) on Sept. 30, when Mexico is scheduled to revert to # standard time. "This is so residents of the Federal District are not # subject to unexpected time changes," a statement from the court said. # From Jesper Nørgaard Welen (2002-03-12): # ... consulting my local grocery store(!) and my coworkers, they all insisted # that a new decision had been made to reinstate US style DST in Mexico.... # http://www.conae.gob.mx/ahorro/horaver2001_m1_2002.html (2002-02-20) # confirms this. Sonora as usual is the only state where DST is not applied. # From Steffen Thorsen (2009-12-28): # # Steffen Thorsen wrote: # > Mexico's House of Representatives has approved a proposal for northern # > Mexico's border cities to share the same daylight saving schedule as # > the United States. # Now this has passed both the Congress and the Senate, so starting from # 2010, some border regions will be the same: # http://www.signonsandiego.com/news/2009/dec/28/clocks-will-match-both-sides-border/ # http://www.elmananarey.com/diario/noticia/nacional/noticias/empatan_horario_de_frontera_con_eu/621939 # (Spanish) # # Could not find the new law text, but the proposed law text changes are here: # http://gaceta.diputados.gob.mx/Gaceta/61/2009/dic/20091210-V.pdf # (Gaceta Parlamentaria) # # There is also a list of the votes here: # http://gaceta.diputados.gob.mx/Gaceta/61/2009/dic/V2-101209.html # # Our page: # https://www.timeanddate.com/news/time/north-mexico-dst-change.html # From Arthur David Olson (2010-01-20): # The page # http://dof.gob.mx/nota_detalle.php?codigo=5127480&fecha=06/01/2010 # includes this text: # En los municipios fronterizos de Tijuana y Mexicali en Baja California; # Juárez y Ojinaga en Chihuahua; Acuña y Piedras Negras en Coahuila; # Anáhuac en Nuevo León; y Nuevo Laredo, Reynosa y Matamoros en # Tamaulipas, la aplicación de este horario estacional surtirá efecto # desde las dos horas del segundo domingo de marzo y concluirá a las dos # horas del primer domingo de noviembre. # En los municipios fronterizos que se encuentren ubicados en la franja # fronteriza norte en el territorio comprendido entre la línea # internacional y la línea paralela ubicada a una distancia de veinte # kilómetros, así como la Ciudad de Ensenada, Baja California, hacia el # interior del país, la aplicación de este horario estacional surtirá # efecto desde las dos horas del segundo domingo de marzo y concluirá a # las dos horas del primer domingo de noviembre. # From Steffen Thorsen (2014-12-08), translated by Gwillim Law: # The Mexican state of Quintana Roo will likely change to EST in 2015. # # http://www.unioncancun.mx/articulo/2014/12/04/medio-ambiente/congreso-aprueba-una-hora-mas-de-sol-en-qroo # "With this change, the time conflict that has existed between the municipios # of Quintana Roo and the municipio of Felipe Carrillo Puerto may come to an # end. The latter declared itself in rebellion 15 years ago when a time change # was initiated in Mexico, and since then it has refused to change its time # zone along with the rest of the country." # # From Steffen Thorsen (2015-01-14), translated by Gwillim Law: # http://sipse.com/novedades/confirman-aplicacion-de-nueva-zona-horaria-para-quintana-roo-132331.html # "...the new time zone will come into effect at two o'clock on the first Sunday # of February, when we will have to advance the clock one hour from its current # time..." # Also, the new zone will not use DST. # # From Carlos Raúl Perasso (2015-02-02): # The decree that modifies the Mexican Hour System Law has finally # been published at the Diario Oficial de la Federación # http://www.dof.gob.mx/nota_detalle.php?codigo=5380123&fecha=31/01/2015 # It establishes 5 zones for Mexico: # 1- Zona Centro (Central Zone): Corresponds to longitude 90 W, # includes most of Mexico, excluding what's mentioned below. # 2- Zona Pacífico (Pacific Zone): Longitude 105 W, includes the # states of Baja California Sur; Chihuahua; Nayarit (excluding Bahía # de Banderas which lies in Central Zone); Sinaloa and Sonora. # 3- Zona Noroeste (Northwest Zone): Longitude 120 W, includes the # state of Baja California. # 4- Zona Sureste (Southeast Zone): Longitude 75 W, includes the state # of Quintana Roo. # 5- The islands, reefs and keys shall take their timezone from the # longitude they are located at. # From Paul Eggert (2022-10-28): # The new Mexican law was published today: # https://www.dof.gob.mx/nota_detalle.php?codigo=5670045&fecha=28/10/2022 # This abolishes DST except where US DST rules are observed, # and in addition changes all of Chihuahua to -06 with no DST. # From Heitor David Pinto (2022-11-28): # Now the northern [municipios] want to have the same time zone as the # respective neighboring cities in the US, for example Juárez in UTC-7 with # DST, matching El Paso, and Ojinaga in UTC-6 with DST, matching Presidio.... # the president authorized the publication of the decree for November 29, # so the time change would occur on November 30 at 0:00. # http://puentelibre.mx/noticia/ciudad_juarez_cambio_horario_noviembre_2022/ # Rule NAME FROM TO - IN ON AT SAVE LETTER/S Rule Mexico 1931 only - Apr 30 0:00 1:00 D Rule Mexico 1931 only - Oct 1 0:00 0 S Rule Mexico 1939 only - Feb 5 0:00 1:00 D Rule Mexico 1939 only - Jun 25 0:00 0 S Rule Mexico 1940 only - Dec 9 0:00 1:00 D Rule Mexico 1941 only - Apr 1 0:00 0 S Rule Mexico 1943 only - Dec 16 0:00 1:00 W # War Rule Mexico 1944 only - May 1 0:00 0 S Rule Mexico 1950 only - Feb 12 0:00 1:00 D Rule Mexico 1950 only - Jul 30 0:00 0 S Rule Mexico 1996 2000 - Apr Sun>=1 2:00 1:00 D Rule Mexico 1996 2000 - Oct lastSun 2:00 0 S Rule Mexico 2001 only - May Sun>=1 2:00 1:00 D Rule Mexico 2001 only - Sep lastSun 2:00 0 S Rule Mexico 2002 2022 - Apr Sun>=1 2:00 1:00 D Rule Mexico 2002 2022 - Oct lastSun 2:00 0 S # Zone NAME STDOFF RULES FORMAT [UNTIL] # Quintana Roo; represented by Cancún Zone America/Cancun -5:47:04 - LMT 1922 Jan 1 6:00u -6:00 - CST 1981 Dec 26 2:00 -5:00 - EST 1983 Jan 4 0:00 -6:00 Mexico C%sT 1997 Oct 26 2:00 -5:00 Mexico E%sT 1998 Aug 2 2:00 -6:00 Mexico C%sT 2015 Feb 1 2:00 -5:00 - EST # Campeche, Yucatán; represented by Mérida Zone America/Merida -5:58:28 - LMT 1922 Jan 1 6:00u -6:00 - CST 1981 Dec 26 2:00 -5:00 - EST 1982 Nov 2 2:00 -6:00 Mexico C%sT # Coahuila, Nuevo León, Tamaulipas (near US border) # This includes the following municipios: # in Coahuila: Acuña, Allende, Guerrero, Hidalgo, Jiménez, Morelos, Nava, # Ocampo, Piedras Negras, Villa Unión, Zaragoza # in Nuevo León: Anáhuac # in Tamaulipas: Nuevo Laredo, Guerrero, Mier, Miguel Alemán, Camargo, # Gustavo Díaz Ordaz, Reynosa, Río Bravo, Valle Hermoso, Matamoros. # https://www.dof.gob.mx/nota_detalle.php?codigo=5670045&fecha=28/10/2022 Zone America/Matamoros -6:30:00 - LMT 1922 Jan 1 6:00u -6:00 - CST 1988 -6:00 US C%sT 1989 -6:00 Mexico C%sT 2010 -6:00 US C%sT # Durango; Coahuila, Nuevo León, Tamaulipas (away from US border) Zone America/Monterrey -6:41:16 - LMT 1922 Jan 1 6:00u -7:00 - MST 1927 Jun 10 -6:00 - CST 1930 Nov 15 -7:00 Mexico M%sT 1932 Apr 1 -6:00 - CST 1988 -6:00 US C%sT 1989 -6:00 Mexico C%sT # Central Mexico Zone America/Mexico_City -6:36:36 - LMT 1922 Jan 1 7:00u -7:00 - MST 1927 Jun 10 -6:00 - CST 1930 Nov 15 -7:00 Mexico M%sT 1932 Apr 1 -6:00 Mexico C%sT 2001 Sep 30 2:00 -6:00 - CST 2002 Feb 20 -6:00 Mexico C%sT # Chihuahua (near US border - western side) # This includes the municipios of Janos, Ascensión, Juárez, Guadalupe, and # Práxedis G Guerrero. # https://gaceta.diputados.gob.mx/PDF/65/2022/nov/20221124-VII.pdf Zone America/Ciudad_Juarez -7:05:56 - LMT 1922 Jan 1 7:00u -7:00 - MST 1927 Jun 10 -6:00 - CST 1930 Nov 15 -7:00 Mexico M%sT 1932 Apr 1 -6:00 - CST 1996 -6:00 Mexico C%sT 1998 -6:00 - CST 1998 Apr Sun>=1 3:00 -7:00 Mexico M%sT 2010 -7:00 US M%sT 2022 Oct 30 2:00 -6:00 - CST 2022 Nov 30 0:00 -7:00 US M%sT # Chihuahua (near US border - eastern side) # This includes the municipios of Coyame del Sotol, Ojinaga, and Manuel # Benavides. # https://gaceta.diputados.gob.mx/PDF/65/2022/nov/20221124-VII.pdf Zone America/Ojinaga -6:57:40 - LMT 1922 Jan 1 7:00u -7:00 - MST 1927 Jun 10 -6:00 - CST 1930 Nov 15 -7:00 Mexico M%sT 1932 Apr 1 -6:00 - CST 1996 -6:00 Mexico C%sT 1998 -6:00 - CST 1998 Apr Sun>=1 3:00 -7:00 Mexico M%sT 2010 -7:00 US M%sT 2022 Oct 30 2:00 -6:00 - CST 2022 Nov 30 0:00 -6:00 US C%sT # Chihuahua (away from US border) Zone America/Chihuahua -7:04:20 - LMT 1922 Jan 1 7:00u -7:00 - MST 1927 Jun 10 -6:00 - CST 1930 Nov 15 -7:00 Mexico M%sT 1932 Apr 1 -6:00 - CST 1996 -6:00 Mexico C%sT 1998 -6:00 - CST 1998 Apr Sun>=1 3:00 -7:00 Mexico M%sT 2022 Oct 30 2:00 -6:00 - CST # Sonora Zone America/Hermosillo -7:23:52 - LMT 1922 Jan 1 7:00u -7:00 - MST 1927 Jun 10 -6:00 - CST 1930 Nov 15 -7:00 Mexico M%sT 1932 Apr 1 -6:00 - CST 1942 Apr 24 -7:00 - MST 1996 -7:00 Mexico M%sT 1999 -7:00 - MST # Baja California Sur, Nayarit (except Bahía de Banderas), Sinaloa Zone America/Mazatlan -7:05:40 - LMT 1922 Jan 1 7:00u -7:00 - MST 1927 Jun 10 -6:00 - CST 1930 Nov 15 -7:00 Mexico M%sT 1932 Apr 1 -6:00 - CST 1942 Apr 24 -7:00 - MST 1970 -7:00 Mexico M%sT # Bahía de Banderas # From Alexander Krivenyshev (2010-04-21): # According to news, Bahía de Banderas (Mexican state of Nayarit) # changed time zone UTC-7 to new time zone UTC-6 on April 4, 2010 (to # share the same time zone as nearby city Puerto Vallarta, Jalisco). # # (Spanish) # Bahía de Banderas homologa su horario al del centro del # país, a partir de este domingo # http://www.nayarit.gob.mx/notes.asp?id=20748 # # Bahía de Banderas homologa su horario con el del Centro del # País # http://www.bahiadebanderas.gob.mx/principal/index.php?option=com_content&view=article&id=261:bahia-de-banderas-homologa-su-horario-con-el-del-centro-del-pais&catid=42:comunicacion-social&Itemid=50 # # (English) # Puerto Vallarta and Bahía de Banderas: One Time Zone # http://virtualvallarta.com/puertovallarta/puertovallarta/localnews/2009-12-03-Puerto-Vallarta-and-Bahia-de-Banderas-One-Time-Zone.shtml # http://www.worldtimezone.com/dst_news/dst_news_mexico08.html # # "Mexico's Senate approved the amendments to the Mexican Schedule System that # will allow Bahía de Banderas and Puerto Vallarta to share the same time # zone ..." # Baja California Sur, Nayarit, Sinaloa # From Arthur David Olson (2010-05-01): # Use "Bahia_Banderas" to keep the name to fourteen characters. Zone America/Bahia_Banderas -7:01:00 - LMT 1922 Jan 1 7:00u -7:00 - MST 1927 Jun 10 -6:00 - CST 1930 Nov 15 -7:00 Mexico M%sT 1932 Apr 1 -6:00 - CST 1942 Apr 24 -7:00 - MST 1970 -7:00 Mexico M%sT 2010 Apr 4 2:00 -6:00 Mexico C%sT # Baja California Zone America/Tijuana -7:48:04 - LMT 1922 Jan 1 7:00u -7:00 - MST 1924 -8:00 - PST 1927 Jun 10 -7:00 - MST 1930 Nov 15 -8:00 - PST 1931 Apr 1 -8:00 1:00 PDT 1931 Sep 30 -8:00 - PST 1942 Apr 24 -8:00 1:00 PWT 1945 Aug 14 23:00u -8:00 1:00 PPT 1945 Nov 15 # Peace -8:00 - PST 1948 Apr 5 -8:00 1:00 PDT 1949 Jan 14 -8:00 - PST 1950 May 1 -8:00 1:00 PDT 1950 Sep 24 -8:00 - PST 1951 Apr 29 2:00 -8:00 1:00 PDT 1951 Sep 30 2:00 -8:00 - PST 1952 Apr 27 2:00 -8:00 1:00 PDT 1952 Sep 28 2:00 -8:00 CA P%sT 1967 -8:00 US P%sT 1996 -8:00 Mexico P%sT 2001 -8:00 US P%sT 2002 Feb 20 -8:00 Mexico P%sT 2010 -8:00 US P%sT # From Paul Eggert (2006-03-22): # Formerly there was an America/Ensenada zone, which differed from # America/Tijuana only in that it did not observe DST from 1976 # through 1995. This was as per Shanks (1999). But Shanks & Pottenger say # Ensenada did not observe DST from 1948 through 1975. Guy Harris reports # that the 1987 OAG says "Only Ensenada, Mexicali, San Felipe and # Tijuana observe DST," which agrees with Shanks & Pottenger but implies that # DST-observance was a town-by-town matter back then. This concerns # data after 1970 so most likely there should be at least one Zone # other than America/Tijuana for Baja, but it's not clear yet what its # name or contents should be. # # From Paul Eggert (2015-10-08): # Formerly there was an America/Santa_Isabel zone, but this appears to # have come from a misreading of # http://dof.gob.mx/nota_detalle.php?codigo=5127480&fecha=06/01/2010 # It has been moved to the 'backward' file. # # From Paul Eggert (2022-10-28): # Today's new law states that the entire state of Baja California # follows US DST rules, which agrees with simplifications noted above. # # # Revillagigedo Is # no information ############################################################################### # Barbados # For 1899 Milne gives -3:58:29.2. # From P Chan (2020-12-09 and 2020-12-11): # Standard time of GMT-4 was adopted in 1911. # Definition of Time Act, 1911 (1911-7) [1911-08-28] # 1912, Laws of Barbados (5 v.), OCLC Number: 919801291, Vol. 4, Image No. 522 # 1944, Laws of Barbados (5 v.), OCLC Number: 84548697, Vol. 4, Image No. 122 # http://llmc.com/browse.aspx?type=2&coll=85&div=297 # # DST was observed in 1942-44. # Defence (Daylight Saving) Regulations, 1942, 1942-04-13 # Defence (Daylight Saving) (Repeal) Regulations, 1942, 1942-08-22 # Defence (Daylight Saving) Regulations, 1943, 1943-04-16 # Defence (Daylight Saving) (Repeal) Regulations, 1943, 1943-09-01 # Defence (Daylight Saving) Regulations, 1944, 1944-03-21 # [Defence (Daylight Saving) (Amendment) Regulations 1944, 1944-03-28] # Defence (Daylight Saving) (Repeal) Regulations, 1944, 1944-08-30 # # 1914-, Subsidiary Legis., Annual Vols. OCLC Number: 226290591 # 1942: Image Nos. 527-528, 555-556 # 1943: Image Nos. 178-179, 198 # 1944: Image Nos. 113-115, 129 # http://llmc.com/titledescfull.aspx?type=2&coll=85&div=297&set=98437 # # From Tim Parenti (2021-02-20): # The transitions below are derived from P Chan's sources, except that the 1977 # through 1980 transitions are from Shanks & Pottenger since we have no better # data there. Of particular note, the 1944 DST regulation only advanced the # time to "exactly three and a half hours later than Greenwich mean time", as # opposed to "three hours" in the 1942 and 1943 regulations. # Rule NAME FROM TO - IN ON AT SAVE LETTER/S Rule Barb 1942 only - Apr 19 5:00u 1:00 D Rule Barb 1942 only - Aug 31 6:00u 0 S Rule Barb 1943 only - May 2 5:00u 1:00 D Rule Barb 1943 only - Sep 5 6:00u 0 S Rule Barb 1944 only - Apr 10 5:00u 0:30 - Rule Barb 1944 only - Sep 10 6:00u 0 S Rule Barb 1977 only - Jun 12 2:00 1:00 D Rule Barb 1977 1978 - Oct Sun>=1 2:00 0 S Rule Barb 1978 1980 - Apr Sun>=15 2:00 1:00 D Rule Barb 1979 only - Sep 30 2:00 0 S Rule Barb 1980 only - Sep 25 2:00 0 S # Zone NAME STDOFF RULES FORMAT [UNTIL] #STDOFF -3:58:29.2 Zone America/Barbados -3:58:29 - LMT 1911 Aug 28 # Bridgetown -4:00 Barb A%sT 1944 -4:00 Barb AST/-0330 1945 -4:00 Barb A%sT # Belize # From P Chan (2020-11-03): # Below are some laws related to the time in British Honduras/Belize: # # Definition of Time Ordinance, 1927 (No.4 of 1927) [1927-04-01] # Ordinances of British Honduras Passed in the Year 1927, p 19-20 # https://books.google.com/books?id=LqEpAQAAMAAJ&pg=RA3-PA19 # # Definition of Time (Amendment) Ordinance, 1942 (No. 5 of 1942) [1942-06-27] # Ordinances of British Honduras Passed in the Year 1942, p 31-32 # https://books.google.com/books?id=h6MpAQAAMAAJ&pg=RA6-PA95-IA44 # # Definition of Time Ordinance, 1945 (No. 19 of 1945) [1945-12-15] # Ordinances of British Honduras Passed in the Year 1945, p 49-50 # https://books.google.com/books?id=xaMpAQAAMAAJ&pg=RA2-PP1 # # Definition of Time Ordinance, 1947 (No. 1 of 1947) [1947-03-11] # Ordinances of British Honduras Passed in the Year 1947, p 1-2 # https://books.google.com/books?id=xaMpAQAAMAAJ&pg=RA3-PA1 # # Time (Definition of) Ordinance (Chapter 180) # The Laws of British Honduras in Force on the 15th Day of September, 1958 , Volume IV, p 2580 # https://books.google.com/books?id=v5QpAQAAMAAJ&pg=PA2580 # # Time (Definition of) (Amendment) Ordinance, 1968 (No. 13 of 1968) [1968-08-03] # https://books.google.com/books?id=xij7KEB_58wC&pg=RA1-PA428-IA9 # # Definition of Time Act (Chapter 339) # Law of Belize, Revised Edition 2000 # http://www.belizelaw.org/web/lawadmin/PDF%20files/cap339.pdf # From Paul Eggert (2020-11-03): # The transitions below are derived from P Chan's sources, except that the # 1973 through 1983 transitions are from Shanks & Pottenger since we have # no better data there. # Rule NAME FROM TO - IN ON AT SAVE LETTER/S Rule Belize 1918 1941 - Oct Sat>=1 24:00 0:30 -0530 Rule Belize 1919 1942 - Feb Sat>=8 24:00 0 CST Rule Belize 1942 only - Jun 27 24:00 1:00 CWT Rule Belize 1945 only - Aug 14 23:00u 1:00 CPT Rule Belize 1945 only - Dec 15 24:00 0 CST Rule Belize 1947 1967 - Oct Sat>=1 24:00 0:30 -0530 Rule Belize 1948 1968 - Feb Sat>=8 24:00 0 CST Rule Belize 1973 only - Dec 5 0:00 1:00 CDT Rule Belize 1974 only - Feb 9 0:00 0 CST Rule Belize 1982 only - Dec 18 0:00 1:00 CDT Rule Belize 1983 only - Feb 12 0:00 0 CST # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone America/Belize -5:52:48 - LMT 1912 Apr 1 -6:00 Belize %s # Bermuda # From Paul Eggert (2022-07-27): # For 1899 Milne gives -4:19:18.3 as the meridian of the clock tower, # Bermuda dockyard, Ireland I. This agrees with standard offset given in the # Daylight Saving Act, 1917 cited below. # It is not known when this time became standard for Bermuda; guess 1890. # The transition to -04 was specified by: # 1930: The Time Zone Act, 1929 (1929: No. 39) [1929-11-08] # https://books.google.com/books?id=7tdMAQAAIAAJ&pg=RA54-PP1 # From P Chan (2020-11-20): # Most of the information can be found online from the Bermuda National # Library - Digital Collection which includes The Royal Gazette (RG) until 1957 # https://bnl.contentdm.oclc.org/digital/ # I will cite the ID. For example, [10000] means # https://bnl.contentdm.oclc.org/digital/collection/BermudaNP02/id/10000 # # 1917: Apr 5 midnight to Sep 30 midnight # Daylight Saving Act, 1917 (1917 No. 13) [1917-04-02] # Bermuda Acts and Resolves 1917, p 37-38 # https://books.google.com/books?id=M-lCAQAAMAAJ&pg=PA36-IA2 # RG, 1917-04-04, p 6 [42340] gives the spring forward date. # # 1918: Apr 13 midnight to Sep 15 midnight # Daylight Saving Act, 1918 (1918 No. 9) [1918-04-06] # Bermuda Acts and Resolves 1917, p 13 # https://books.google.com/books?id=K-lCAQAAMAAJ&pg=RA1-PA7 # # Note that local mean time was still used before 1930. # # During WWII, DST was introduced by Defence Regulations # 1942: Jan 11 02:00 to Oct 18 02:00 [113646], [115726] # 1943: Mar 21 02:00 to Oct 31 02:00 [116704], [118193] # 1944: Mar 12 02:00 to Nov 5 02:00 [119225], [121593] # 1945: Mar 11 02:00 to Nov 4 02:00 [122369], [124461] # RG, 1942-01-08, p 2, 1942-10-12, p 2 , 1943-03-06, p 2, 1943-09-03, p 1, # 1944-02-29, p 6, 1944-09-20, p 2, 1945-02-13, p 2, 1945-11-03, p 1 # # In 1946, the House of Assembly rejected DST twice. [128686], [128076] # RG, 1946-03-16 p 1,1946-04-13 p 1 # # 1947: third Sunday in May 02:00 to second Sunday in September 02:00 # DST in 1947 was defined in the Daylight Saving Act, 1947 (1947: No. 12) # which expired at the end of the year. [125784] ,[132405], [144454], [138226] # RG, 1947-02-27, p 1, 1947-05-15, p 1, 1947-09-13, p 1, 1947-12-30, p 1 # # 1948-1952: fourth Sunday in May 02:00 to first Sunday in September 02:00 # DST in 1948 was defined in the Daylight Saving Act, 1948 (1948 : No. 12) # which was set to expired at the end of the year but it was extended until # the end of 1952 and was not further extended. # [129802], [139403], [146008], [135240], [144330], [139049], [143309], # [148271], [149773], [153589], [153802], [155924] # RG, 1948-04-13, p 1, 1948-05-22, p 1, 1948-09-04, p 1, 1949-05-21, p1, # 1949-09-03, p 1, 1950-05-27 p 1, 1950-09-02, p 1, 1951-05-27, p 1, # 1951-09-01, p 1, 1952-05-23, p 1, 1952-09-26, p 1, 1952-12-21, p 8 # # In 1953-1955, the House of Assembly rejected DST each year. [158996], # [162620], [166720] RG, 1953-05-02, p 1, 1954-04-01 p 1, 1955-03-12, p 1 # # 1956: fourth Sunday in May 02:00 to last Sunday in October 02:00 # Time Zone (Seasonal Variation) Act, 1956 (1956: No.44) [1956-05-25] # Bermuda Public Acts 1956, p 331-332 # https://books.google.com/books?id=Xs1AlmD_cEwC&pg=PA63 # # The extension of the Act was rejected by the House of Assembly. [176218] # RG, 1956-12-13, p 1 # # From the Chronological Table of Public and Private Acts up to 1985, it seems # that there does not exist other Acts related to DST before 1973. # https://books.google.com/books?id=r9hMAQAAIAAJ&pg=RA23-PA1 # Public Acts of the Legislature of the Islands of Bermuda, Together with # Statutory Instruments in Force Thereunder, Vol VII # From Dan Jones, reporting in The Royal Gazette (2006-06-26): # Next year, however, clocks in the US will go forward on the second Sunday # in March, until the first Sunday in November. And, after the Time Zone # (Seasonal Variation) Bill 2006 was passed in the House of Assembly on # Friday, the same thing will happen in Bermuda. # http://www.theroyalgazette.com/apps/pbcs.dll/article?AID=/20060529/NEWS/105290135 # Rule NAME FROM TO - IN ON AT SAVE LETTER/S Rule Bermuda 1917 only - Apr 5 24:00 1:00 - Rule Bermuda 1917 only - Sep 30 24:00 0 - Rule Bermuda 1918 only - Apr 13 24:00 1:00 - Rule Bermuda 1918 only - Sep 15 24:00 0 S Rule Bermuda 1942 only - Jan 11 2:00 1:00 D Rule Bermuda 1942 only - Oct 18 2:00 0 S Rule Bermuda 1943 only - Mar 21 2:00 1:00 D Rule Bermuda 1943 only - Oct 31 2:00 0 S Rule Bermuda 1944 1945 - Mar Sun>=8 2:00 1:00 D Rule Bermuda 1944 1945 - Nov Sun>=1 2:00 0 S Rule Bermuda 1947 only - May Sun>=15 2:00 1:00 D Rule Bermuda 1947 only - Sep Sun>=8 2:00 0 S Rule Bermuda 1948 1952 - May Sun>=22 2:00 1:00 D Rule Bermuda 1948 1952 - Sep Sun>=1 2:00 0 S Rule Bermuda 1956 only - May Sun>=22 2:00 1:00 D Rule Bermuda 1956 only - Oct lastSun 2:00 0 S # Zone NAME STDOFF RULES FORMAT [UNTIL] #STDOFF -4:19:18.3 Zone Atlantic/Bermuda -4:19:18 - LMT 1890 # Hamilton -4:19:18 Bermuda BMT/BST 1930 Jan 1 2:00 -4:00 Bermuda A%sT 1974 Apr 28 2:00 -4:00 Canada A%sT 1976 -4:00 US A%sT # Costa Rica # Milne gives -5:36:13.3 as San José mean time. # Rule NAME FROM TO - IN ON AT SAVE LETTER/S Rule CR 1979 1980 - Feb lastSun 0:00 1:00 D Rule CR 1979 1980 - Jun Sun>=1 0:00 0 S Rule CR 1991 1992 - Jan Sat>=15 0:00 1:00 D # IATA SSIM (1991-09) says the following was at 1:00; # go with Shanks & Pottenger. Rule CR 1991 only - Jul 1 0:00 0 S Rule CR 1992 only - Mar 15 0:00 0 S # There are too many San Josés elsewhere, so we'll use 'Costa Rica'. # Zone NAME STDOFF RULES FORMAT [UNTIL] #STDOFF -5:36:13.3 Zone America/Costa_Rica -5:36:13 - LMT 1890 # San José -5:36:13 - SJMT 1921 Jan 15 # San José Mean Time -6:00 CR C%sT # Coco # no information; probably like America/Costa_Rica # Cuba # From Paul Eggert (2013-02-21): # Milne gives -5:28:50.45 for the observatory at Havana, -5:29:23.57 # for the port, and -5:30 for meteorological observations. # For now, stick with Shanks & Pottenger. # From Arthur David Olson (1999-03-29): # The 1999-03-28 exhibition baseball game held in Havana, Cuba, between # the Cuban National Team and the Baltimore Orioles was carried live on # the Orioles Radio Network, including affiliate WTOP in Washington, DC. # During the game, play-by-play announcer Jim Hunter noted that # "We'll be losing two hours of sleep...Cuba switched to Daylight Saving # Time today." (The "two hour" remark referred to losing one hour of # sleep on 1999-03-28 - when the announcers were in Cuba as it switched # to DST - and one more hour on 1999-04-04 - when the announcers will have # returned to Baltimore, which switches on that date.) # From Steffen Thorsen (2013-11-11): # DST start in Cuba in 2004 ... does not follow the same rules as the # years before. The correct date should be Sunday 2004-03-28 00:00 ... # https://web.archive.org/web/20040402060750/http://www.granma.cu/espanol/2004/marzo/sab27/reloj.html # From Evert van der Veer via Steffen Thorsen (2004-10-28): # Cuba is not going back to standard time this year. # From Paul Eggert (2006-03-22): # http://www.granma.cu/ingles/2004/septiembre/juev30/41medid-i.html # says that it's due to a problem at the Antonio Guiteras # thermoelectric plant, and says "This October there will be no return # to normal hours (after daylight saving time)". # For now, let's assume that it's a temporary measure. # From Carlos A. Carnero Delgado (2005-11-12): # This year (just like in 2004-2005) there's no change in time zone # adjustment in Cuba. We will stay in daylight saving time: # http://www.granma.cu/espanol/2005/noviembre/mier9/horario.html # From Jesper Nørgaard Welen (2006-10-21): # An article in GRANMA INTERNACIONAL claims that Cuba will end # the 3 years of permanent DST next weekend, see # http://www.granma.cu/ingles/2006/octubre/lun16/43horario.html # "On Saturday night, October 28 going into Sunday, October 29, at 01:00, # watches should be set back one hour - going back to 00:00 hours - returning # to the normal schedule.... # From Paul Eggert (2007-03-02): # , dated yesterday, # says Cuban clocks will advance at midnight on March 10. # For lack of better information, assume Cuba will use US rules, # except that it switches at midnight standard time as usual. # # From Steffen Thorsen (2007-10-25): # Carlos Alberto Fonseca Arauz informed me that Cuba will end DST one week # earlier - on the last Sunday of October, just like in 2006. # # He supplied these references: # # http://www.prensalatina.com.mx/article.asp?ID={4CC32C1B-A9F7-42FB-8A07-8631AFC923AF}&language=ES # http://actualidad.terra.es/sociedad/articulo/cuba_llama_ahorrar_energia_cambio_1957044.htm # # From Alex Krivenyshev (2007-10-25): # Here is also article from Granma (Cuba): # # Regirá el Horario Normal desde el próximo domingo 28 de octubre # http://www.granma.cubaweb.cu/2007/10/24/nacional/artic07.html # # http://www.worldtimezone.com/dst_news/dst_news_cuba03.html # From Arthur David Olson (2008-03-09): # I'm in Maryland which is now observing United States Eastern Daylight # Time. At 9:44 local time I used RealPlayer to listen to # http://media.enet.cu/radioreloj # a Cuban information station, and heard # the time announced as "ocho cuarenta y cuatro" ("eight forty-four"), # indicating that Cuba is still on standard time. # From Steffen Thorsen (2008-03-12): # It seems that Cuba will start DST on Sunday, 2007-03-16... # It was announced yesterday, according to this source (in Spanish): # http://www.nnc.cubaweb.cu/marzo-2008/cien-1-11-3-08.htm # # Some more background information is posted here: # https://www.timeanddate.com/news/time/cuba-starts-dst-march-16.html # # The article also says that Cuba has been observing DST since 1963, # while Shanks (and tzdata) has 1965 as the first date (except in the # 1940's). Many other web pages in Cuba also claim that it has been # observed since 1963, but with the exception of 1970 - an exception # which is not present in tzdata/Shanks. So there is a chance we need to # change some historic records as well. # # One example: # http://www.radiohc.cu/espanol/noticias/mar07/11mar/hor.htm # From Jesper Nørgaard Welen (2008-03-13): # The Cuban time change has just been confirmed on the most authoritative # web site, the Granma. Please check out # http://www.granma.cubaweb.cu/2008/03/13/nacional/artic10.html # # Basically as expected after Steffen Thorsen's information, the change # will take place midnight between Saturday and Sunday. # From Arthur David Olson (2008-03-12): # Assume Sun>=15 (third Sunday) going forward. # From Alexander Krivenyshev (2009-03-04) # According to the Radio Reloj - Cuba will start Daylight Saving Time on # midnight between Saturday, March 07, 2009 and Sunday, March 08, 2009- # not on midnight March 14 / March 15 as previously thought. # # http://www.worldtimezone.com/dst_news/dst_news_cuba05.html # (in Spanish) # From Arthur David Olson (2009-03-09) # I listened over the Internet to # http://media.enet.cu/readioreloj # this morning; when it was 10:05 a. m. here in Bethesda, Maryland the # the time was announced as "diez cinco" - the same time as here, indicating # that has indeed switched to DST. Assume second Sunday from 2009 forward. # From Steffen Thorsen (2011-03-08): # Granma announced that Cuba is going to start DST on 2011-03-20 00:00:00 # this year. Nothing about the end date known so far (if that has # changed at all). # # Source: # http://granma.co.cu/2011/03/08/nacional/artic01.html # # Our info: # https://www.timeanddate.com/news/time/cuba-starts-dst-2011.html # # From Steffen Thorsen (2011-10-30) # Cuba will end DST two weeks later this year. Instead of going back # tonight, it has been delayed to 2011-11-13 at 01:00. # # One source (Spanish) # http://www.radioangulo.cu/noticias/cuba/17105-cuba-restablecera-el-horario-del-meridiano-de-greenwich.html # # Our page: # https://www.timeanddate.com/news/time/cuba-time-changes-2011.html # # From Steffen Thorsen (2012-03-01) # According to Radio Reloj, Cuba will start DST on Midnight between March # 31 and April 1. # # Radio Reloj has the following info (Spanish): # http://www.radioreloj.cu/index.php/noticias-radio-reloj/71-miscelaneas/7529-cuba-aplicara-el-horario-de-verano-desde-el-1-de-abril # # Our info on it: # https://www.timeanddate.com/news/time/cuba-starts-dst-2012.html # From Steffen Thorsen (2012-11-03): # Radio Reloj and many other sources report that Cuba is changing back # to standard time on 2012-11-04: # http://www.radioreloj.cu/index.php/noticias-radio-reloj/36-nacionales/9961-regira-horario-normal-en-cuba-desde-el-domingo-cuatro-de-noviembre # From Paul Eggert (2012-11-03): # For now, assume the future rule is first Sunday in November. # Rule NAME FROM TO - IN ON AT SAVE LETTER/S Rule Cuba 1928 only - Jun 10 0:00 1:00 D Rule Cuba 1928 only - Oct 10 0:00 0 S Rule Cuba 1940 1942 - Jun Sun>=1 0:00 1:00 D Rule Cuba 1940 1942 - Sep Sun>=1 0:00 0 S Rule Cuba 1945 1946 - Jun Sun>=1 0:00 1:00 D Rule Cuba 1945 1946 - Sep Sun>=1 0:00 0 S Rule Cuba 1965 only - Jun 1 0:00 1:00 D Rule Cuba 1965 only - Sep 30 0:00 0 S Rule Cuba 1966 only - May 29 0:00 1:00 D Rule Cuba 1966 only - Oct 2 0:00 0 S Rule Cuba 1967 only - Apr 8 0:00 1:00 D Rule Cuba 1967 1968 - Sep Sun>=8 0:00 0 S Rule Cuba 1968 only - Apr 14 0:00 1:00 D Rule Cuba 1969 1977 - Apr lastSun 0:00 1:00 D Rule Cuba 1969 1971 - Oct lastSun 0:00 0 S Rule Cuba 1972 1974 - Oct 8 0:00 0 S Rule Cuba 1975 1977 - Oct lastSun 0:00 0 S Rule Cuba 1978 only - May 7 0:00 1:00 D Rule Cuba 1978 1990 - Oct Sun>=8 0:00 0 S Rule Cuba 1979 1980 - Mar Sun>=15 0:00 1:00 D Rule Cuba 1981 1985 - May Sun>=5 0:00 1:00 D Rule Cuba 1986 1989 - Mar Sun>=14 0:00 1:00 D Rule Cuba 1990 1997 - Apr Sun>=1 0:00 1:00 D Rule Cuba 1991 1995 - Oct Sun>=8 0:00s 0 S Rule Cuba 1996 only - Oct 6 0:00s 0 S Rule Cuba 1997 only - Oct 12 0:00s 0 S Rule Cuba 1998 1999 - Mar lastSun 0:00s 1:00 D Rule Cuba 1998 2003 - Oct lastSun 0:00s 0 S Rule Cuba 2000 2003 - Apr Sun>=1 0:00s 1:00 D Rule Cuba 2004 only - Mar lastSun 0:00s 1:00 D Rule Cuba 2006 2010 - Oct lastSun 0:00s 0 S Rule Cuba 2007 only - Mar Sun>=8 0:00s 1:00 D Rule Cuba 2008 only - Mar Sun>=15 0:00s 1:00 D Rule Cuba 2009 2010 - Mar Sun>=8 0:00s 1:00 D Rule Cuba 2011 only - Mar Sun>=15 0:00s 1:00 D Rule Cuba 2011 only - Nov 13 0:00s 0 S Rule Cuba 2012 only - Apr 1 0:00s 1:00 D Rule Cuba 2012 max - Nov Sun>=1 0:00s 0 S Rule Cuba 2013 max - Mar Sun>=8 0:00s 1:00 D # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone America/Havana -5:29:28 - LMT 1890 -5:29:36 - HMT 1925 Jul 19 12:00 # Havana MT -5:00 Cuba C%sT # Dominican Republic # From Steffen Thorsen (2000-10-30): # Enrique Morales reported to me that the Dominican Republic has changed the # time zone to Eastern Standard Time as of Sunday 29 at 2 am.... # http://www.listin.com.do/antes/261000/republica/princi.html # From Paul Eggert (2000-12-04): # That URL (2000-10-26, in Spanish) says they planned to use US-style DST. # From Rives McDow (2000-12-01): # Dominican Republic changed its mind and presidential decree on Tuesday, # November 28, 2000, with a new decree. On Sunday, December 3 at 1:00 AM the # Dominican Republic will be reverting to 8 hours from the International Date # Line, and will not be using DST in the foreseeable future. The reason they # decided to use DST was to be in synch with Puerto Rico, who was also going # to implement DST. When Puerto Rico didn't implement DST, the president # decided to revert. # Rule NAME FROM TO - IN ON AT SAVE LETTER/S Rule DR 1966 only - Oct 30 0:00 1:00 EDT Rule DR 1967 only - Feb 28 0:00 0 EST Rule DR 1969 1973 - Oct lastSun 0:00 0:30 -0430 Rule DR 1970 only - Feb 21 0:00 0 EST Rule DR 1971 only - Jan 20 0:00 0 EST Rule DR 1972 1974 - Jan 21 0:00 0 EST # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone America/Santo_Domingo -4:39:36 - LMT 1890 -4:40 - SDMT 1933 Apr 1 12:00 # S. Dom. MT -5:00 DR %s 1974 Oct 27 -4:00 - AST 2000 Oct 29 2:00 -5:00 US E%sT 2000 Dec 3 1:00 -4:00 - AST # El Salvador # Rule NAME FROM TO - IN ON AT SAVE LETTER/S Rule Salv 1987 1988 - May Sun>=1 0:00 1:00 D Rule Salv 1987 1988 - Sep lastSun 0:00 0 S # There are too many San Salvadors elsewhere, so use America/El_Salvador # instead of America/San_Salvador. # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone America/El_Salvador -5:56:48 - LMT 1921 # San Salvador -6:00 Salv C%sT # Guatemala # # From Gwillim Law (2006-04-22), after a heads-up from Oscar van Vlijmen: # Diario Co Latino, at # , # says in an article dated 2006-04-19 that the Guatemalan government had # decided on that date to advance official time by 60 minutes, to lessen the # impact of the elevated cost of oil.... Daylight saving time will last from # 2006-04-29 24:00 (Guatemalan standard time) to 2006-09-30 (time unspecified). # From Paul Eggert (2006-06-22): # The Ministry of Energy and Mines, press release CP-15/2006 # (2006-04-19), says DST ends at 24:00. See # http://www.sieca.org.gt/Sitio_publico/Energeticos/Doc/Medidas/Cambio_Horario_Nac_190406.pdf # Rule NAME FROM TO - IN ON AT SAVE LETTER/S Rule Guat 1973 only - Nov 25 0:00 1:00 D Rule Guat 1974 only - Feb 24 0:00 0 S Rule Guat 1983 only - May 21 0:00 1:00 D Rule Guat 1983 only - Sep 22 0:00 0 S Rule Guat 1991 only - Mar 23 0:00 1:00 D Rule Guat 1991 only - Sep 7 0:00 0 S Rule Guat 2006 only - Apr 30 0:00 1:00 D Rule Guat 2006 only - Oct 1 0:00 0 S # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone America/Guatemala -6:02:04 - LMT 1918 Oct 5 -6:00 Guat C%sT # Haiti # From Gwillim Law (2005-04-15): # Risto O. Nykänen wrote me that Haiti is now on DST. # I searched for confirmation, and I found a press release # on the Web page of the Haitian Consulate in Chicago (2005-03-31), # . Translated from French, it says: # # "The Prime Minister's Communication Office notifies the public in general # and the press in particular that, following a decision of the Interior # Ministry and the Territorial Collectivities [I suppose that means the # provinces], Haiti will move to Eastern Daylight Time in the night from next # Saturday the 2nd to Sunday the 3rd. # # "Consequently, the Prime Minister's Communication Office wishes to inform # the population that the country's clocks will be set forward one hour # starting at midnight. This provision will hold until the last Saturday in # October 2005. # # "Port-au-Prince, March 31, 2005" # # From Steffen Thorsen (2006-04-04): # I have been informed by users that Haiti observes DST this year like # last year, so the current "only" rule for 2005 might be changed to a # "max" rule or to last until 2006. (Who knows if they will observe DST # next year or if they will extend their DST like US/Canada next year). # # I have found this article about it (in French): # http://www.haitipressnetwork.com/news.cfm?articleID=7612 # # The reason seems to be an energy crisis. # From Stephen Colebourne (2007-02-22): # Some IATA info: Haiti won't be having DST in 2007. # From Steffen Thorsen (2012-03-11): # According to several news sources, Haiti will observe DST this year, # apparently using the same start and end date as USA/Canada. # So this means they have already changed their time. # # http://www.alterpresse.org/spip.php?article12510 # http://radiovision2000haiti.net/home/?p=13253 # # From Arthur David Olson (2012-03-11): # The alterpresse.org source seems to show a US-style leap from 2:00 a.m. to # 3:00 a.m. rather than the traditional Haitian jump at midnight. # Assume a US-style fall back as well. # From Steffen Thorsen (2013-03-10): # It appears that Haiti is observing DST this year as well, same rules # as US/Canada. They did it last year as well, and it looks like they # are going to observe DST every year now... # # http://radiovision2000haiti.net/public/haiti-avis-changement-dheure-dimanche/ # http://www.canalplushaiti.net/?p=6714 # From Steffen Thorsen (2016-03-12): # Jean Antoine, editor of www.haiti-reference.com informed us that Haiti # are not going on DST this year. Several other resources confirm this: ... # https://www.radiotelevisioncaraibes.com/presse/heure_d_t_pas_de_changement_d_heure_pr_vu_pour_cet_ann_e.html # https://www.vantbefinfo.com/changement-dheure-pas-pour-haiti/ # http://news.anmwe.com/haiti-lheure-nationale-ne-sera-ni-avancee-ni-reculee-cette-annee/ # From Steffen Thorsen (2017-03-12): # We have received 4 mails from different people telling that Haiti # has started DST again today, and this source seems to confirm that, # I have not been able to find a more authoritative source: # https://www.haitilibre.com/en/news-20319-haiti-notices-time-change-in-haiti.html # Rule NAME FROM TO - IN ON AT SAVE LETTER/S Rule Haiti 1983 only - May 8 0:00 1:00 D Rule Haiti 1984 1987 - Apr lastSun 0:00 1:00 D Rule Haiti 1983 1987 - Oct lastSun 0:00 0 S # Shanks & Pottenger say AT is 2:00, but IATA SSIM (1991/1997) says 1:00s. # Go with IATA. Rule Haiti 1988 1997 - Apr Sun>=1 1:00s 1:00 D Rule Haiti 1988 1997 - Oct lastSun 1:00s 0 S Rule Haiti 2005 2006 - Apr Sun>=1 0:00 1:00 D Rule Haiti 2005 2006 - Oct lastSun 0:00 0 S Rule Haiti 2012 2015 - Mar Sun>=8 2:00 1:00 D Rule Haiti 2012 2015 - Nov Sun>=1 2:00 0 S Rule Haiti 2017 max - Mar Sun>=8 2:00 1:00 D Rule Haiti 2017 max - Nov Sun>=1 2:00 0 S # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone America/Port-au-Prince -4:49:20 - LMT 1890 -4:49 - PPMT 1917 Jan 24 12:00 # P-a-P MT -5:00 Haiti E%sT # Honduras # Shanks & Pottenger say 1921 Jan 1; go with Whitman's more precise Apr 1. # From Paul Eggert (2006-05-05): # worldtimezone.com reports a 2006-05-02 Spanish-language AP article # saying Honduras will start using DST midnight Saturday, effective 4 # months until September. La Tribuna reported today # that Manuel Zelaya, the president # of Honduras, refused to back down on this. # From Jesper Nørgaard Welen (2006-08-08): # It seems that Honduras has returned from DST to standard time this Monday at # 00:00 hours (prolonging Sunday to 25 hours duration). # http://www.worldtimezone.com/dst_news/dst_news_honduras04.html # From Paul Eggert (2006-08-08): # Also see Diario El Heraldo, The country returns to standard time (2006-08-08). # http://www.elheraldo.hn/nota.php?nid=54941&sec=12 # It mentions executive decree 18-2006. # From Steffen Thorsen (2006-08-17): # Honduras will observe DST from 2007 to 2009, exact dates are not # published, I have located this authoritative source: # http://www.presidencia.gob.hn/noticia.aspx?nId=47 # From Steffen Thorsen (2007-03-30): # http://www.laprensahn.com/pais_nota.php?id04962=7386 # So it seems that Honduras will not enter DST this year.... # Rule NAME FROM TO - IN ON AT SAVE LETTER/S Rule Hond 1987 1988 - May Sun>=1 0:00 1:00 D Rule Hond 1987 1988 - Sep lastSun 0:00 0 S Rule Hond 2006 only - May Sun>=1 0:00 1:00 D Rule Hond 2006 only - Aug Mon>=1 0:00 0 S # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone America/Tegucigalpa -5:48:52 - LMT 1921 Apr -6:00 Hond C%sT # # Great Swan I ceded by US to Honduras in 1972 # Jamaica # Shanks & Pottenger give -5:07:12, but Milne records -5:07:10.41 from an # unspecified official document, and says "This time is used throughout the # island". Go with Milne. # # Shanks & Pottenger give April 28 for the 1974 spring-forward transition, but # Lance Neita writes that Prime Minister Michael Manley decreed it January 5. # Assume Neita meant Jan 6 02:00, the same as the US. Neita also writes that # Manley's supporters associated this act with Manley's nickname "Joshua" # (recall that in the Bible the sun stood still at Joshua's request), # and with the Rod of Correction which Manley said he had received from # Haile Selassie, Emperor of Ethiopia. See: # Neita L. The politician in all of us. Jamaica Observer 2014-09-20 # http://www.jamaicaobserver.com/columns/The-politician-in-all-of-us_17573647 # # Zone NAME STDOFF RULES FORMAT [UNTIL] #STDOFF -5:07:10.41 Zone America/Jamaica -5:07:10 - LMT 1890 # Kingston -5:07:10 - KMT 1912 Feb # Kingston Mean Time -5:00 - EST 1974 -5:00 US E%sT 1984 -5:00 - EST # Martinique # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone America/Martinique -4:04:20 - LMT 1890 # Fort-de-France -4:04:20 - FFMT 1911 May 1 # Fort-de-France MT -4:00 - AST 1980 Apr 6 -4:00 1:00 ADT 1980 Sep 28 -4:00 - AST # Nicaragua # # This uses Shanks & Pottenger for times before 2005. # # From Steffen Thorsen (2005-04-12): # I've got reports from 8 different people that Nicaragua just started # DST on Sunday 2005-04-10, in order to save energy because of # expensive petroleum. The exact end date for DST is not yet # announced, only "September" but some sites also say "mid-September". # Some background information is available on the President's official site: # http://www.presidencia.gob.ni/Presidencia/Files_index/Secretaria/Notas%20de%20Prensa/Presidente/2005/ABRIL/Gobierno-de-nicaragua-adelanta-hora-oficial-06abril.htm # The Decree, no 23-2005 is available here: # http://www.presidencia.gob.ni/buscador_gaceta/BD/DECRETOS/2005/Decreto%2023-2005%20Se%20adelanta%20en%20una%20hora%20en%20todo%20el%20territorio%20nacional%20apartir%20de%20las%2024horas%20del%2009%20de%20Abril.pdf # # From Paul Eggert (2005-05-01): # The decree doesn't say anything about daylight saving, but for now let's # assume that it is daylight saving.... # # From Gwillim Law (2005-04-21): # The Associated Press story on the time change, which can be found at # http://www.lapalmainteractivo.com/guias/content/gen/ap/America_Latina/AMC_GEN_NICARAGUA_HORA.html # and elsewhere, says (fifth paragraph, translated from Spanish): "The last # time that a change of clocks was applied to save energy was in the year 2000 # during the Arnoldo Alemán administration."... # The northamerica file says that Nicaragua has been on UTC-6 continuously # since December 1998. I wasn't able to find any details of Nicaraguan time # changes in 2000. Perhaps a note could be added to the northamerica file, to # the effect that we have indirect evidence that DST was observed in 2000. # # From Jesper Nørgaard Welen (2005-11-02): # Nicaragua left DST the 2005-10-02 at 00:00 (local time). # http://www.presidencia.gob.ni/presidencia/files_index/secretaria/comunicados/2005/septiembre/26septiembre-cambio-hora.htm # (2005-09-26) # # From Jesper Nørgaard Welen (2006-05-05): # http://www.elnuevodiario.com.ni/2006/05/01/nacionales/18410 # (my informal translation) # By order of the president of the republic, Enrique Bolaños, Nicaragua # advanced by sixty minutes their official time, yesterday at 2 in the # morning, and will stay that way until 30th of September. # # From Jesper Nørgaard Welen (2006-09-30): # http://www.presidencia.gob.ni/buscador_gaceta/BD/DECRETOS/2006/D-063-2006P-PRN-Cambio-Hora.pdf # My informal translation runs: # The natural sun time is restored in all the national territory, in that the # time is returned one hour at 01:00 am of October 1 of 2006. # # Rule NAME FROM TO - IN ON AT SAVE LETTER/S Rule Nic 1979 1980 - Mar Sun>=16 0:00 1:00 D Rule Nic 1979 1980 - Jun Mon>=23 0:00 0 S Rule Nic 2005 only - Apr 10 0:00 1:00 D Rule Nic 2005 only - Oct Sun>=1 0:00 0 S Rule Nic 2006 only - Apr 30 2:00 1:00 D Rule Nic 2006 only - Oct Sun>=1 1:00 0 S # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone America/Managua -5:45:08 - LMT 1890 -5:45:12 - MMT 1934 Jun 23 # Managua Mean Time? -6:00 - CST 1973 May -5:00 - EST 1975 Feb 16 -6:00 Nic C%sT 1992 Jan 1 4:00 -5:00 - EST 1992 Sep 24 -6:00 - CST 1993 -5:00 - EST 1997 -6:00 Nic C%sT # Cayman Is # Panama # # Atikokan and Coral Harbour, Canada, match Panama since 1970. # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone America/Panama -5:18:08 - LMT 1890 -5:19:36 - CMT 1908 Apr 22 # Colón Mean Time -5:00 - EST # Anguilla # Antigua & Barbuda # Aruba # Caribbean Netherlands # Curaçao # Dominica # Grenada # Guadeloupe # Montserrat # Puerto Rico # St Barthélemy # St Kitts-Nevis # Sint Maarten / St Martin # St Lucia # St Vincent & the Grenadines # Trinidad & Tobago # Virgin Is (UK & US) # # There are too many San Juans elsewhere, so we'll use 'Puerto_Rico'. # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone America/Puerto_Rico -4:24:25 - LMT 1899 Mar 28 12:00 # San Juan -4:00 - AST 1942 May 3 -4:00 US A%sT 1946 -4:00 - AST # St Pierre and Miquelon # There are too many St Pierres elsewhere, so we'll use 'Miquelon'. # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone America/Miquelon -3:44:40 - LMT 1911 Jun 15 # St Pierre -4:00 - AST 1980 May -3:00 - %z 1987 -3:00 Canada %z # Turks and Caicos # # From Chris Dunn in # https://bugs.debian.org/415007 # (2007-03-15): In the Turks & Caicos Islands (America/Grand_Turk) the # daylight saving dates for time changes have been adjusted to match # the recent U.S. change of dates. # # From Brian Inglis (2007-04-28): # http://www.turksandcaicos.tc/calendar/index.htm [2007-04-26] # there is an entry for Nov 4 "Daylight Savings Time Ends 2007" and three # rows before that there is an out of date entry for Oct: # "Eastern Standard Times Begins 2007 # Clocks are set back one hour at 2:00 a.m. local Daylight Saving Time" # indicating that the normal ET rules are followed. # From Paul Eggert (2014-08-19): # The 2014-08-13 Cabinet meeting decided to stay on UT -04 year-round. See: # http://tcweeklynews.com/daylight-savings-time-to-be-maintained-p5353-127.htm # Model this as a switch from EST/EDT to AST ... # From Chris Walton (2014-11-04): # ... the TCI government appears to have delayed the switch to # "permanent daylight saving time" by one year.... # http://tcweeklynews.com/time-change-to-go-ahead-this-november-p5437-127.htm # # From the Turks & Caicos Cabinet (2017-07-20), heads-up from Steffen Thorsen: # ... agreed to the reintroduction in TCI of Daylight Saving Time (DST) # during the summer months and Standard Time, also known as Local # Time, during the winter months with effect from April 2018 ... # https://www.gov.uk/government/news/turks-and-caicos-post-cabinet-meeting-statement--3 # From Paul Eggert (2017-08-26): # The date of effect of the spring 2018 change appears to be March 11, # which makes more sense. See: Hamilton D. Time change back # by March 2018 for TCI. Magnetic Media. 2017-08-25. # http://magneticmediatv.com/2017/08/time-change-back-by-march-2018-for-tci/ # # From P Chan (2020-11-27): # Standard Time Declaration Order 2015 (L.N. 15/2015) # http://online.fliphtml5.com/fizd/czin/#p=2 # # Standard Time Declaration Order 2017 (L.N. 31/2017) # http://online.fliphtml5.com/fizd/dmcu/#p=2 # # From Tim Parenti (2020-12-05): # Although L.N. 31/2017 reads that it "shall come into operation at 2:00 a.m. # on 11th March 2018", a precise interpretation here poses some problems. The # order states that "the standard time to be observed throughout the Turks and # Caicos Islands shall be the same time zone as the Eastern United States of # America" and further clarifies "[f]or the avoidance of doubt" that it # "applies to the Eastern Standard Time as well as any changes thereto for # Daylight Saving Time." However, as clocks in Turks and Caicos approached # 02:00 -04, and thus the declared implementation time, it was still 01:00 EST # (-05), as DST in the Eastern US would not start until an hour later. # # Since it is unlikely that those on the islands switched their clocks twice in # the span of an hour, we assume instead that the adoption of EDT actually took # effect once clocks in the Eastern US had sprung forward, from 03:00 -04. # This discrepancy only affects the time zone abbreviation and DST flag for the # intervening hour, not wall clock times, as -04 was maintained throughout. # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone America/Grand_Turk -4:44:32 - LMT 1890 #STDOFF -5:07:10.41 -5:07:10 - KMT 1912 Feb # Kingston Mean Time -5:00 - EST 1979 -5:00 US E%sT 2015 Mar 8 2:00 -4:00 - AST 2018 Mar 11 3:00 -5:00 US E%sT # Local Variables: # coding: utf-8 # End: pr0m1th3as-datatypes-9c9a8d3/inst/tzdata/southamerica000066400000000000000000002721301522766574100227350ustar00rootroot00000000000000# tzdb data for South America and environs # This file is in the public domain, so clarified as of # 2009-05-17 by Arthur David Olson. # This file is by no means authoritative; if you think you know better, # go ahead and edit the file, and please send any changes to # the public mailing list tz@iana.org for general use in the future. # For more, please see the file CONTRIBUTING in the tz distribution. # From Paul Eggert (2016-12-05): # # Unless otherwise specified, the source for data through 1990 is: # Thomas G. Shanks and Rique Pottenger, The International Atlas (6th edition), # San Diego: ACS Publications, Inc. (2003). # Unfortunately this book contains many errors and cites no sources. # # Many years ago Gwillim Law wrote that a good source # for time zone data was the International Air Transport # Association's Standard Schedules Information Manual (IATA SSIM), # published semiannually. Law sent in several helpful summaries # of the IATA's data after 1990. Except where otherwise noted, # IATA SSIM is the source for entries after 1990. # # For data circa 1899, a common source is: # Milne J. Civil time. Geogr J. 1899 Feb;13(2):173-94. # https://www.jstor.org/stable/1774359 # # These tables use numeric abbreviations like -03 and -0330 for # integer hour and minute UT offsets. Although earlier editions used # alphabetic time zone abbreviations, these abbreviations were # invented and did not reflect common practice. ############################################################################### ############################################################################### # Argentina # From Bob Devine (1988-01-28): # Argentina: first Sunday in October to first Sunday in April since 1976. # Double Summer time from 1969 to 1974. Switches at midnight. # From U. S. Naval Observatory (1988-01-19): # ARGENTINA 3 H BEHIND UTC # From Hernan G. Otero (1995-06-26): # I am sending modifications to the Argentine time zone table... # AR was chosen because they are the ISO letters that represent Argentina. # Rule NAME FROM TO - IN ON AT SAVE LETTER/S Rule Arg 1930 only - Dec 1 0:00 1:00 - Rule Arg 1931 only - Apr 1 0:00 0 - Rule Arg 1931 only - Oct 15 0:00 1:00 - Rule Arg 1932 1940 - Mar 1 0:00 0 - Rule Arg 1932 1939 - Nov 1 0:00 1:00 - Rule Arg 1940 only - Jul 1 0:00 1:00 - Rule Arg 1941 only - Jun 15 0:00 0 - Rule Arg 1941 only - Oct 15 0:00 1:00 - Rule Arg 1943 only - Aug 1 0:00 0 - Rule Arg 1943 only - Oct 15 0:00 1:00 - Rule Arg 1946 only - Mar 1 0:00 0 - Rule Arg 1946 only - Oct 1 0:00 1:00 - Rule Arg 1963 only - Oct 1 0:00 0 - Rule Arg 1963 only - Dec 15 0:00 1:00 - Rule Arg 1964 1966 - Mar 1 0:00 0 - Rule Arg 1964 1966 - Oct 15 0:00 1:00 - Rule Arg 1967 only - Apr 2 0:00 0 - Rule Arg 1967 1968 - Oct Sun>=1 0:00 1:00 - Rule Arg 1968 1969 - Apr Sun>=1 0:00 0 - Rule Arg 1974 only - Jan 23 0:00 1:00 - Rule Arg 1974 only - May 1 0:00 0 - Rule Arg 1988 only - Dec 1 0:00 1:00 - # # From Hernan G. Otero (1995-06-26): # These corrections were contributed by InterSoft Argentina S.A., # obtaining the data from the: # Talleres de Hidrografía Naval Argentina # (Argentine Naval Hydrography Institute) Rule Arg 1989 1993 - Mar Sun>=1 0:00 0 - Rule Arg 1989 1992 - Oct Sun>=15 0:00 1:00 - # # From Hernan G. Otero (1995-06-26): # From this moment on, the law that mandated the daylight saving # time corrections was derogated and no more modifications # to the time zones (for daylight saving) are now made. # # From Rives McDow (2000-01-10): # On October 3, 1999, 0:00 local, Argentina implemented daylight savings time, # which did not result in the switch of a time zone, as they stayed 9 hours # from the International Date Line. Rule Arg 1999 only - Oct Sun>=1 0:00 1:00 - # From Paul Eggert (2007-12-28): # DST was set to expire on March 5, not March 3, but since it was converted # to standard time on March 3 it's more convenient for us to pretend that # it ended on March 3. Rule Arg 2000 only - Mar 3 0:00 0 - # # From Peter Gradelski via Steffen Thorsen (2000-03-01): # We just checked with our São Paulo office and they say the government of # Argentina decided not to become one of the countries that go on or off DST. # So Buenos Aires should be -3 hours from GMT at all times. # # From Fabián L. Arce Jofré (2000-04-04): # The law that claimed DST for Argentina was derogated by President Fernando # de la Rúa on March 2, 2000, because it would make people spend more energy # in the winter time, rather than less. The change took effect on March 3. # # From Mariano Absatz (2001-06-06): # one of the major newspapers here in Argentina said that the 1999 # Timezone Law (which never was effectively applied) will (would?) be # in effect.... The article is at # http://ar.clarin.com/diario/2001-06-06/e-01701.htm # ... The Law itself is "Ley No. 25155", sanctioned on 1999-08-25, enacted # 1999-09-17, and published 1999-09-21. The official publication is at: # http://www.boletin.jus.gov.ar/BON/Primera/1999/09-Septiembre/21/PDF/BO21-09-99LEG.PDF # Regretfully, you have to subscribe (and pay) for the on-line version.... # # (2001-06-12): # the timezone for Argentina will not change next Sunday. # Apparently it will do so on Sunday 24th.... # http://ar.clarin.com/diario/2001-06-12/s-03501.htm # # (2001-06-25): # Last Friday (yes, the last working day before the date of the change), the # Senate annulled the 1999 law that introduced the changes later postponed. # http://www.clarin.com.ar/diario/2001-06-22/s-03601.htm # It remains the vote of the Deputies..., but it will be the same.... # This kind of things had always been done this way in Argentina. # We are still -03:00 all year round in all of the country. # # From Steffen Thorsen (2007-12-21): # A user (Leonardo Chaim) reported that Argentina will adopt DST.... # all of the country (all Zone-entries) are affected. News reports like # http://www.lanacion.com.ar/opinion/nota.asp?nota_id=973037 indicate # that Argentina will use DST next year as well, from October to # March, although exact rules are not given. # # From Jesper Nørgaard Welen (2007-12-26) # The last hurdle of Argentina DST is over, the proposal was approved in # the lower chamber too (Diputados) with a vote 192 for and 2 against. # By the way thanks to Mariano Absatz and Daniel Mario Vega for the link to # the original scanned proposal, where the dates and the zero hours are # clear and unambiguous...This is the article about final approval: # http://www.lanacion.com.ar/politica/nota.asp?nota_id=973996 # # From Paul Eggert (2007-12-22): # For dates after mid-2008, the following rules are my guesses and # are quite possibly wrong, but are more likely than no DST at all. # From Alexander Krivenyshev (2008-09-05): # As per message from Carlos Alberto Fonseca Arauz (Nicaragua), # Argentina will start DST on Sunday October 19, 2008. # # http://www.worldtimezone.com/dst_news/dst_news_argentina03.html # http://www.impulsobaires.com.ar/nota.php?id=57832 (in spanish) # From Juan Manuel Docile in https://bugs.gentoo.org/240339 (2008-10-07) # via Rodrigo Severo: # Argentinian law No. 25.155 is no longer valid. # http://www.infoleg.gov.ar/infolegInternet/anexos/60000-64999/60036/norma.htm # The new one is law No. 26.350 # http://www.infoleg.gov.ar/infolegInternet/anexos/135000-139999/136191/norma.htm # So there is no summer time in Argentina for now. # From Mariano Absatz (2008-10-20): # Decree 1693/2008 applies Law 26.350 for the summer 2008/2009 establishing DST # in Argentina from 2008-10-19 until 2009-03-15. # http://www.boletinoficial.gov.ar/Bora.Portal/CustomControls/PdfContent.aspx?fp=16102008&pi=3&pf=4&s=0&sec=01 # # Decree 1705/2008 excepting 12 Provinces from applying DST in the summer # 2008/2009: Catamarca, La Rioja, Mendoza, Salta, San Juan, San Luis, La # Pampa, Neuquén, Rio Negro, Chubut, Santa Cruz and Tierra del Fuego # http://www.boletinoficial.gov.ar/Bora.Portal/CustomControls/PdfContent.aspx?fp=17102008&pi=1&pf=1&s=0&sec=01 # # Press release 235 dated Saturday October 18th, from the Government of the # Province of Jujuy saying it will not apply DST either (even when it was not # included in Decree 1705/2008). # http://www.jujuy.gov.ar/index2/partes_prensa/18_10_08/235-181008.doc # From fullinet (2009-10-18): # As announced in # http://www.argentina.gob.ar/argentina/portal/paginas.dhtml?pagina=356 # (an official .gob.ar) under title: "Sin Cambio de Hora" # (English: "No hour change"). # # "Por el momento, el Gobierno Nacional resolvió no modificar la hora # oficial, decisión que estaba en estudio para su implementación el # domingo 18 de octubre. Desde el Ministerio de Planificación se anunció # que la Argentina hoy, en estas condiciones meteorológicas, no necesita # la modificación del huso horario, ya que 2009 nos encuentra con # crecimiento en la producción y distribución energética." Rule Arg 2007 only - Dec 30 0:00 1:00 - Rule Arg 2008 2009 - Mar Sun>=15 0:00 0 - Rule Arg 2008 only - Oct Sun>=15 0:00 1:00 - # From Mariano Absatz (2004-05-21): # Today it was officially published that the Province of Mendoza is changing # its timezone this winter... starting tomorrow night.... # http://www.gobernac.mendoza.gov.ar/boletin/pdf/20040521-27158-normas.pdf # From Paul Eggert (2004-05-24): # It's Law No. 7,210. This change is due to a public power emergency, so for # now we'll assume it's for this year only. # # From Paul Eggert (2018-01-31): # Hora de verano para la República Argentina # http://buenasiembra.com.ar/esoterismo/astrologia/hora-de-verano-de-la-republica-argentina-27.html # says that standard time in Argentina from 1894-10-31 # to 1920-05-01 was -4:16:48.25. Go with this more precise value # over Shanks & Pottenger. It is upward compatible with Milne, who # says Córdoba time was -4:16:48.2. # # From Mariano Absatz (2004-06-05): # These media articles from a major newspaper mostly cover the current state: # http://www.lanacion.com.ar/04/05/27/de_604825.asp # http://www.lanacion.com.ar/04/05/28/de_605203.asp # # The following eight (8) provinces pulled clocks back to UTC-04:00 at # midnight Monday May 31st. (that is, the night between 05/31 and 06/01). # Apparently, all nine provinces would go back to UTC-03:00 at the same # time in October 17th. # # Catamarca, Chubut, La Rioja, San Juan, San Luis, Santa Cruz, # Tierra del Fuego, Tucumán. # # From Mariano Absatz (2004-06-14): # ... this weekend, the Province of Tucumán decided it'd go back to UTC-03:00 # yesterday midnight (that is, at 24:00 Saturday 12th), since the people's # annoyance with the change is much higher than the power savings obtained.... # # From Gwillim Law (2004-06-14): # http://www.lanacion.com.ar/04/06/10/de_609078.asp ... # "The time change in Tierra del Fuego was a conflicted decision from # the start. The government had decreed that the measure would take # effect on June 1, but a normative error forced the new time to begin # three days earlier, from a Saturday to a Sunday.... # Our understanding was that the change was originally scheduled to take place # on June 1 at 00:00 in Chubut, Santa Cruz, Tierra del Fuego (and some other # provinces). Sunday was May 30, only two days earlier. So the article # contains a contradiction. I would give more credence to the Saturday/Sunday # date than the "three days earlier" phrase, and conclude that Tierra del # Fuego set its clocks back at 2004-05-30 00:00. # # From Steffen Thorsen (2004-10-05): # The previous law 7210 which changed the province of Mendoza's time zone # back in May have been modified slightly in a new law 7277, which set the # new end date to 2004-09-26 (original date was 2004-10-17). # http://www.gobernac.mendoza.gov.ar/boletin/pdf/20040924-27244-normas.pdf # # From Mariano Absatz (2004-10-05): # San Juan changed from UTC-03:00 to UTC-04:00 at midnight between # Sunday, May 30th and Monday, May 31st. It changed back to UTC-03:00 # at midnight between Saturday, July 24th and Sunday, July 25th.... # http://www.sanjuan.gov.ar/prensa/archivo/000329.html # http://www.sanjuan.gov.ar/prensa/archivo/000426.html # http://www.sanjuan.gov.ar/prensa/archivo/000441.html # From Alex Krivenyshev (2008-01-17): # Here are articles that Argentina Province San Luis is planning to end DST # as earlier as upcoming Monday January 21, 2008 or February 2008: # # Provincia argentina retrasa reloj y marca diferencia con resto del país # (Argentine Province delayed clock and mark difference with the rest of the # country) # http://cl.invertia.com/noticias/noticia.aspx?idNoticia=200801171849_EFE_ET4373&idtel # # Es inminente que en San Luis atrasen una hora los relojes # (It is imminent in San Luis clocks one hour delay) # https://www.lagaceta.com.ar/nota/253414/Economia/Es-inminente-que-en-San-Luis-atrasen-una-hora-los-relojes.html # http://www.worldtimezone.com/dst_news/dst_news_argentina02.html # From Jesper Nørgaard Welen (2008-01-18): # The page of the San Luis provincial government # http://www.sanluis.gov.ar/notas.asp?idCanal=0&id=22812 # confirms what Alex Krivenyshev has earlier sent to the tz # emailing list about that San Luis plans to return to standard # time much earlier than the rest of the country. It also # confirms that upon request the provinces San Juan and Mendoza # refused to follow San Luis in this change. # # The change is supposed to take place Monday the 21st at 0:00 # hours. As far as I understand it if this goes ahead, we need # a new timezone for San Luis (although there are also documented # independent changes in the southamerica file of San Luis in # 1990 and 1991 which has not been confirmed). # From Jesper Nørgaard Welen (2008-01-25): # Unfortunately the below page has become defunct, about the San Luis # time change. Perhaps because it now is part of a group of pages "Most # important pages of 2008." # # You can use # http://www.sanluis.gov.ar/notas.asp?idCanal=8141&id=22834 # instead it seems. Or use "Buscador" from the main page of the San Luis # government, and fill in "huso" and click OK, and you will get 3 pages # from which the first one is identical to the above. # From Mariano Absatz (2008-01-28): # I can confirm that the Province of San Luis (and so far only that # province) decided to go back to UTC-3 effective midnight Jan 20th 2008 # (that is, Monday 21st at 0:00 is the time the clocks were delayed back # 1 hour), and they intend to keep UTC-3 as their timezone all year round # (that is, unless they change their mind any minute now). # # So we'll have to add yet another city to 'southamerica' (I think San # Luis city is the mos populated city in the Province, so it'd be # America/Argentina/San_Luis... of course I can't remember if San Luis's # history of particular changes goes along with Mendoza or San Juan :-( # (I only remember not being able to collect hard facts about San Luis # back in 2004, when these provinces changed to UTC-4 for a few days, I # mailed them personally and never got an answer). # From Paul Eggert (2014-08-12): # Unless otherwise specified, data entries are from Shanks & Pottenger through # 1992, from the IATA otherwise. As noted below, Shanks & Pottenger say that # America/Cordoba split into 6 subregions during 1991/1992, one of which # was America/San_Luis, but we haven't verified this yet so for now we'll # keep America/Cordoba a single region rather than splitting it into the # other 5 subregions. # From Mariano Absatz (2009-03-13): # Yesterday (with our usual 2-day notice) the Province of San Luis # decided that next Sunday instead of "staying" @utc-03:00 they will go # to utc-04:00 until the second Saturday in October... # # The press release is at # http://www.sanluis.gov.ar/SL/Paginas/NoticiaDetalle.asp?TemaId=1&InfoPrensaId=3102 # (I couldn't find the decree, but www.sanluis.gov.ar # is the official page for the Province Government.) # # There's also a note in only one of the major national papers ... # http://www.lanacion.com.ar/nota.asp?nota_id=1107912 # # The press release says [quick and dirty translation]: # ... announced that next Sunday, at 00:00, Puntanos (the San Luis # inhabitants) will have to turn back one hour their clocks # # Since then, San Luis will establish its own Province timezone. Thus, # during 2009, this timezone change will run from 00:00 the third Sunday # in March until 24:00 of the second Saturday in October. # From Mariano Absatz (2009-10-16): # ...the Province of San Luis is a case in itself. # # The Law at # http://www.diputadossanluis.gov.ar/diputadosasp/paginas/verNorma.asp?NormaID=276 # is ambiguous because establishes a calendar from the 2nd Sunday in # October at 0:00 thru the 2nd Saturday in March at 24:00 and the # complement of that starting on the 2nd Sunday of March at 0:00 and # ending on the 2nd Saturday of March at 24:00. # # This clearly breaks every time the 1st of March or October is a Sunday. # # IMHO, the "spirit of the Law" is to make the changes at 0:00 on the 2nd # Sunday of October and March. # # The problem is that the changes in the rest of the Provinces that did # change in 2007/2008, were made according to the Federal Law and Decrees # that did so on the 3rd Sunday of October and March. # # In fact, San Luis actually switched from UTC-4 to UTC-3 last Sunday # (October 11th) at 0:00. # # So I guess a new set of rules, besides "Arg", must be made and the last # America/Argentina/San_Luis entries should change to use these... # ... # From Alexander Krivenyshev (2010-04-09): # According to news reports from El Diario de la República Province San # Luis, Argentina (standard time UTC-04) will keep Daylight Saving Time # after April 11, 2010 - will continue to have same time as rest of # Argentina (UTC-3) (no DST). # # Confirmaron la prórroga del huso horario de verano (Spanish) # http://www.eldiariodelarepublica.com/index.php?option=com_content&task=view&id=29383&Itemid=9 # or (some English translation): # http://www.worldtimezone.com/dst_news/dst_news_argentina08.html # From Mariano Absatz (2010-04-12): # yes...I can confirm this...and given that San Luis keeps calling # UTC-03:00 "summer time", we should't just let San Luis go back to "Arg" # rules...San Luis is still using "Western ARgentina Time" and it got # stuck on Summer daylight savings time even though the summer is over. # From Paul Eggert (2018-01-23): # Perhaps San Luis operates on the legal fiction that it is at -04 # with perpetual daylight saving time, but ordinary usage typically seems to # just say it's at -03; see, for example, # https://es.wikipedia.org/wiki/Hora_oficial_argentina # We've documented similar situations as being plain changes to # standard time, so let's do that here too. This does not change UTC # offsets, only tm_isdst and the time zone abbreviations. One minor # plus is that this silences a zic complaint that there's no POSIX TZ # setting for timestamps past 2038. # Zone NAME STDOFF RULES FORMAT [UNTIL] # # Buenos Aires (BA), Capital Federal (CF), Zone America/Argentina/Buenos_Aires -3:53:48 - LMT 1894 Oct 31 #STDOFF -4:16:48.25 -4:16:48 - CMT 1920 May # Córdoba Mean Time -4:00 - %z 1930 Dec -4:00 Arg %z 1969 Oct 5 -3:00 Arg %z 1999 Oct 3 -4:00 Arg %z 2000 Mar 3 -3:00 Arg %z # # Córdoba (CB), Santa Fe (SF), Entre Ríos (ER), Corrientes (CN), Misiones (MN), # Chaco (CC), Formosa (FM), Santiago del Estero (SE) # # Shanks & Pottenger also make the following claims, which we haven't verified: # - Formosa switched to -3:00 on 1991-01-07. # - Misiones switched to -3:00 on 1990-12-29. # - Chaco switched to -3:00 on 1991-01-04. # - Santiago del Estero switched to -4:00 on 1991-04-01, # then to -3:00 on 1991-04-26. # #STDOFF -4:16:48.25 Zone America/Argentina/Cordoba -4:16:48 - LMT 1894 Oct 31 -4:16:48 - CMT 1920 May -4:00 - %z 1930 Dec -4:00 Arg %z 1969 Oct 5 -3:00 Arg %z 1991 Mar 3 -4:00 - %z 1991 Oct 20 -3:00 Arg %z 1999 Oct 3 -4:00 Arg %z 2000 Mar 3 -3:00 Arg %z # # Salta (SA), La Pampa (LP), Neuquén (NQ), Rio Negro (RN) Zone America/Argentina/Salta -4:21:40 - LMT 1894 Oct 31 #STDOFF -4:16:48.25 -4:16:48 - CMT 1920 May -4:00 - %z 1930 Dec -4:00 Arg %z 1969 Oct 5 -3:00 Arg %z 1991 Mar 3 -4:00 - %z 1991 Oct 20 -3:00 Arg %z 1999 Oct 3 -4:00 Arg %z 2000 Mar 3 -3:00 Arg %z 2008 Oct 18 -3:00 - %z # # Tucumán (TM) Zone America/Argentina/Tucuman -4:20:52 - LMT 1894 Oct 31 #STDOFF -4:16:48.25 -4:16:48 - CMT 1920 May -4:00 - %z 1930 Dec -4:00 Arg %z 1969 Oct 5 -3:00 Arg %z 1991 Mar 3 -4:00 - %z 1991 Oct 20 -3:00 Arg %z 1999 Oct 3 -4:00 Arg %z 2000 Mar 3 -3:00 - %z 2004 Jun 1 -4:00 - %z 2004 Jun 13 -3:00 Arg %z # # La Rioja (LR) Zone America/Argentina/La_Rioja -4:27:24 - LMT 1894 Oct 31 #STDOFF -4:16:48.25 -4:16:48 - CMT 1920 May -4:00 - %z 1930 Dec -4:00 Arg %z 1969 Oct 5 -3:00 Arg %z 1991 Mar 1 -4:00 - %z 1991 May 7 -3:00 Arg %z 1999 Oct 3 -4:00 Arg %z 2000 Mar 3 -3:00 - %z 2004 Jun 1 -4:00 - %z 2004 Jun 20 -3:00 Arg %z 2008 Oct 18 -3:00 - %z # # San Juan (SJ) Zone America/Argentina/San_Juan -4:34:04 - LMT 1894 Oct 31 #STDOFF -4:16:48.25 -4:16:48 - CMT 1920 May -4:00 - %z 1930 Dec -4:00 Arg %z 1969 Oct 5 -3:00 Arg %z 1991 Mar 1 -4:00 - %z 1991 May 7 -3:00 Arg %z 1999 Oct 3 -4:00 Arg %z 2000 Mar 3 -3:00 - %z 2004 May 31 -4:00 - %z 2004 Jul 25 -3:00 Arg %z 2008 Oct 18 -3:00 - %z # # Jujuy (JY) Zone America/Argentina/Jujuy -4:21:12 - LMT 1894 Oct 31 #STDOFF -4:16:48.25 -4:16:48 - CMT 1920 May -4:00 - %z 1930 Dec -4:00 Arg %z 1969 Oct 5 -3:00 Arg %z 1990 Mar 4 -4:00 - %z 1990 Oct 28 -4:00 1:00 %z 1991 Mar 17 -4:00 - %z 1991 Oct 6 -3:00 1:00 %z 1992 -3:00 Arg %z 1999 Oct 3 -4:00 Arg %z 2000 Mar 3 -3:00 Arg %z 2008 Oct 18 -3:00 - %z # # Catamarca (CT), Chubut (CH) Zone America/Argentina/Catamarca -4:23:08 - LMT 1894 Oct 31 #STDOFF -4:16:48.25 -4:16:48 - CMT 1920 May -4:00 - %z 1930 Dec -4:00 Arg %z 1969 Oct 5 -3:00 Arg %z 1991 Mar 3 -4:00 - %z 1991 Oct 20 -3:00 Arg %z 1999 Oct 3 -4:00 Arg %z 2000 Mar 3 -3:00 - %z 2004 Jun 1 -4:00 - %z 2004 Jun 20 -3:00 Arg %z 2008 Oct 18 -3:00 - %z # # Mendoza (MZ) Zone America/Argentina/Mendoza -4:35:16 - LMT 1894 Oct 31 #STDOFF -4:16:48.25 -4:16:48 - CMT 1920 May -4:00 - %z 1930 Dec -4:00 Arg %z 1969 Oct 5 -3:00 Arg %z 1990 Mar 4 -4:00 - %z 1990 Oct 15 -4:00 1:00 %z 1991 Mar 1 -4:00 - %z 1991 Oct 15 -4:00 1:00 %z 1992 Mar 1 -4:00 - %z 1992 Oct 18 -3:00 Arg %z 1999 Oct 3 -4:00 Arg %z 2000 Mar 3 -3:00 - %z 2004 May 23 -4:00 - %z 2004 Sep 26 -3:00 Arg %z 2008 Oct 18 -3:00 - %z # # San Luis (SL) Rule SanLuis 2008 2009 - Mar Sun>=8 0:00 0 - Rule SanLuis 2007 2008 - Oct Sun>=8 0:00 1:00 - Zone America/Argentina/San_Luis -4:25:24 - LMT 1894 Oct 31 #STDOFF -4:16:48.25 -4:16:48 - CMT 1920 May -4:00 - %z 1930 Dec -4:00 Arg %z 1969 Oct 5 -3:00 Arg %z 1990 -3:00 1:00 %z 1990 Mar 14 -4:00 - %z 1990 Oct 15 -4:00 1:00 %z 1991 Mar 1 -4:00 - %z 1991 Jun 1 -3:00 - %z 1999 Oct 3 -4:00 1:00 %z 2000 Mar 3 -3:00 - %z 2004 May 31 -4:00 - %z 2004 Jul 25 -3:00 Arg %z 2008 Jan 21 -4:00 SanLuis %z 2009 Oct 11 -3:00 - %z # # Santa Cruz (SC) Zone America/Argentina/Rio_Gallegos -4:36:52 - LMT 1894 Oct 31 #STDOFF -4:16:48.25 -4:16:48 - CMT 1920 May -4:00 - %z 1930 Dec -4:00 Arg %z 1969 Oct 5 -3:00 Arg %z 1999 Oct 3 -4:00 Arg %z 2000 Mar 3 -3:00 - %z 2004 Jun 1 -4:00 - %z 2004 Jun 20 -3:00 Arg %z 2008 Oct 18 -3:00 - %z # # Tierra del Fuego, Antártida e Islas del Atlántico Sur (TF) Zone America/Argentina/Ushuaia -4:33:12 - LMT 1894 Oct 31 #STDOFF -4:16:48.25 -4:16:48 - CMT 1920 May -4:00 - %z 1930 Dec -4:00 Arg %z 1969 Oct 5 -3:00 Arg %z 1999 Oct 3 -4:00 Arg %z 2000 Mar 3 -3:00 - %z 2004 May 30 -4:00 - %z 2004 Jun 20 -3:00 Arg %z 2008 Oct 18 -3:00 - %z # Bolivia # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone America/La_Paz -4:32:36 - LMT 1890 -4:32:36 - CMT 1931 Oct 15 # Calamarca MT -4:32:36 1:00 BST 1932 Mar 21 # Bolivia ST -4:00 - %z # Brazil # From Paul Eggert (1993-11-18): # The mayor of Rio recently attempted to change the time zone rules # just in his city, in order to leave more summer time for the tourist trade. # The rule change lasted only part of the day; # the federal government refused to follow the city's rules, and business # was in a chaos, so the mayor backed down that afternoon. # From IATA SSIM (1996-02): # _Only_ the following states in BR1 observe DST: Rio Grande do Sul (RS), # Santa Catarina (SC), Paraná (PR), São Paulo (SP), Rio de Janeiro (RJ), # Espírito Santo (ES), Minas Gerais (MG), Bahia (BA), Goiás (GO), # Distrito Federal (DF), Tocantins (TO), Sergipe [SE] and Alagoas [AL]. # [The last three states are new to this issue of the IATA SSIM.] # From Gwillim Law (1996-10-07): # Geography, history (Tocantins was part of Goiás until 1989), and other # sources of time zone information lead me to believe that AL, SE, and TO were # always in BR1, and so the only change was whether or not they observed DST.... # The earliest issue of the SSIM I have is 2/91. Each issue from then until # 9/95 says that DST is observed only in the ten states I quoted from 9/95, # along with Mato Grosso (MT) and Mato Grosso do Sul (MS), which are in BR2 # (UTC-4).... The other two time zones given for Brazil are BR3, which is # UTC-5, no DST, and applies only in the state of Acre (AC); and BR4, which is # UTC-2, and applies to Fernando de Noronha (formerly FN, but I believe it's # become part of the state of Pernambuco). The boundary between BR1 and BR2 # has never been clearly stated. They've simply been called East and West. # However, some conclusions can be drawn from another IATA manual: the Airline # Coding Directory, which lists close to 400 airports in Brazil. For each # airport it gives a time zone which is coded to the SSIM. From that # information, I'm led to conclude that the states of Amapá (AP), Ceará (CE), # Maranhão (MA), Paraíba (PR), Pernambuco (PE), Piauí (PI), and Rio Grande do # Norte (RN), and the eastern part of Pará (PA) are all in BR1 without DST. # From Marcos Tadeu (1998-09-27): # Brazilian official page # From Jesper Nørgaard (2000-11-03): # [For an official list of which regions in Brazil use which time zones, see:] # http://pcdsh01.on.br/Fusbr.htm # http://pcdsh01.on.br/Fusbrhv.htm # From Celso Doria via David Madeo (2002-10-09): # The reason for the delay this year has to do with elections in Brazil. # # Unlike in the United States, elections in Brazil are 100% computerized and # the results are known almost immediately. Yesterday, it was the first # round of the elections when 115 million Brazilians voted for President, # Governor, Senators, Federal Deputies, and State Deputies. Nobody is # counting (or re-counting) votes anymore and we know there will be a second # round for the Presidency and also for some Governors. The 2nd round will # take place on October 27th. # # The reason why the DST will only begin November 3rd is that the thousands # of electoral machines used cannot have their time changed, and since the # Constitution says the elections must begin at 8:00 AM and end at 5:00 PM, # the Government decided to postpone DST, instead of changing the Constitution # (maybe, for the next elections, it will be possible to change the clock)... # From Rodrigo Severo (2004-10-04): # It's just the biannual change made necessary by the much hyped, supposedly # modern Brazilian ... voting machines which, apparently, can't deal # with a time change between the first and the second rounds of the elections. # From Steffen Thorsen (2007-09-20): # Brazil will start DST on 2007-10-14 00:00 and end on 2008-02-17 00:00: # http://www.mme.gov.br/site/news/detail.do;jsessionid=BBA06811AFCAAC28F0285210913513DA?newsId=13975 # From Paul Schulze (2008-06-24): # ...by law number 11.662 of April 24, 2008 (published in the "Diario # Oficial da União"...) in Brazil there are changes in the timezones, # effective today (00:00am at June 24, 2008) as follows: # # a) The timezone UTC+5 is extinguished, with all the Acre state and the # part of the Amazonas state that had this timezone now being put to the # timezone UTC+4 # b) The whole Pará state now is put at timezone UTC+3, instead of just # part of it, as was before. # # This change follows a proposal of senator Tiao Viana of Acre state, that # proposed it due to concerns about open television channels displaying # programs inappropriate to youths in the states that had the timezone # UTC+5 too early in the night. In the occasion, some more corrections # were proposed, trying to unify the timezones of any given state. This # change modifies timezone rules defined in decree 2.784 of 18 June, # 1913. # From Rodrigo Severo (2008-06-24): # Just correcting the URL: # https://www.in.gov.br/imprensa/visualiza/index.jsp?jornal=do&secao=1&pagina=1&data=25/04/2008 # # As a result of the above Decree I believe the America/Rio_Branco # timezone shall be modified from UTC-5 to UTC-4 and a new timezone shall # be created to represent the...west side of the Pará State. I # suggest this new timezone be called Santarem as the most # important/populated city in the affected area. # # This new timezone would be the same as the Rio_Branco timezone up to # the 2008/06/24 change which would be to UTC-3 instead of UTC-4. # From Alex Krivenyshev (2008-06-24): # This is a quick reference page for New and Old Brazil Time Zones map. # http://www.worldtimezone.com/brazil-time-new-old.php # # - 4 time zones replaced by 3 time zones - eliminating time zone UTC-05 # (state Acre and the part of the Amazonas will be UTC/GMT-04) - western # part of Par state is moving to one timezone UTC-03 (from UTC-04). # From Paul Eggert (2002-10-10): # The official decrees referenced below are mostly taken from # Decretos sobre o Horário de Verão no Brasil. # http://pcdsh01.on.br/DecHV.html # From Steffen Thorsen (2008-08-29): # As announced by the government and many newspapers in Brazil late # yesterday, Brazil will start DST on 2008-10-19 (need to change rule) and # it will end on 2009-02-15 (current rule for Brazil is fine). Based on # past years experience with the elections, there was a good chance that # the start was postponed to November, but it did not happen this year. # # It has not yet been posted to http://pcdsh01.on.br/DecHV.html # # An official page about it: # http://www.mme.gov.br/site/news/detail.do?newsId=16722 # Note that this link does not always work directly, but must be accessed # by going to # http://www.mme.gov.br/first # # One example link that works directly: # http://jornale.com.br/index.php?option=com_content&task=view&id=13530&Itemid=54 # (Portuguese) # # We have a written a short article about it as well: # https://www.timeanddate.com/news/time/brazil-dst-2008-2009.html # # From Alexander Krivenyshev (2011-10-04): # State Bahia will return to Daylight savings time this year after 8 years off. # The announcement was made by Governor Jaques Wagner in an interview to a # television station in Salvador. # In Portuguese: # http://g1.globo.com/bahia/noticia/2011/10/governador-jaques-wagner-confirma-horario-de-verao-na-bahia.html # https://noticias.terra.com.br/brasil/noticias/0,,OI5390887-EI8139,00-Bahia+volta+a+ter+horario+de+verao+apos+oito+anos.html # From Guilherme Bernardes Rodrigues (2011-10-07): # There is news in the media, however there is still no decree about it. # I just send a e-mail to Zulmira Brandao at http://pcdsh01.on.br/ the # official agency about time in Brazil, and she confirmed that the old rule is # still in force. # From Guilherme Bernardes Rodrigues (2011-10-14) # It's official, the President signed a decree that includes Bahia in summer # time. # [ and in a second message (same day): ] # I found the decree. # # DECRETO No. 7.584, DE 13 DE OUTUBRO DE 2011 # Link : # http://www.in.gov.br/visualiza/index.jsp?data=13/10/2011&jornal=1000&pagina=6&totalArquivos=6 # From Kelley Cook (2012-10-16): # The governor of state of Bahia in Brazil announced on Thursday that # due to public pressure, he is reversing the DST policy they implemented # last year and will not be going to Summer Time on October 21st.... # http://www.correio24horas.com.br/r/artigo/apos-pressoes-wagner-suspende-horario-de-verao-na-bahia # From Rodrigo Severo (2012-10-16): # Tocantins state will have DST. # https://noticias.terra.com.br/brasil/noticias/0,,OI6232536-EI306.html # From Steffen Thorsen (2013-09-20): # Tocantins in Brazil is very likely not to observe DST from October.... # http://conexaoto.com.br/2013/09/18/ministerio-confirma-que-tocantins-esta-fora-do-horario-de-verao-em-2013-mas-falta-publicacao-de-decreto # We will keep this article updated when this is confirmed: # https://www.timeanddate.com/news/time/brazil-starts-dst-2013.html # From Steffen Thorsen (2013-10-17): # https://www.timeanddate.com/news/time/acre-amazonas-change-time-zone.html # Senator Jorge Viana announced that Acre will change time zone on November 10. # He did not specify the time of the change, nor if western parts of Amazonas # will change as well. # # From Paul Eggert (2013-10-17): # For now, assume western Amazonas will change as well. # Rule NAME FROM TO - IN ON AT SAVE LETTER/S # Decree 20,466 (1931-10-01) # Decree 21,896 (1932-01-10) Rule Brazil 1931 only - Oct 3 11:00 1:00 - Rule Brazil 1932 1933 - Apr 1 0:00 0 - Rule Brazil 1932 only - Oct 3 0:00 1:00 - # Decree 23,195 (1933-10-10) # revoked DST. # Decree 27,496 (1949-11-24) # Decree 27,998 (1950-04-13) Rule Brazil 1949 1952 - Dec 1 0:00 1:00 - Rule Brazil 1950 only - Apr 16 1:00 0 - Rule Brazil 1951 1952 - Apr 1 0:00 0 - # Decree 32,308 (1953-02-24) Rule Brazil 1953 only - Mar 1 0:00 0 - # Decree 34,724 (1953-11-30) # revoked DST. # Decree 52,700 (1963-10-18) # established DST from 1963-10-23 00:00 to 1964-02-29 00:00 # in SP, RJ, GB, MG, ES, due to the prolongation of the drought. # Decree 53,071 (1963-12-03) # extended the above decree to all of the national territory on 12-09. Rule Brazil 1963 only - Dec 9 0:00 1:00 - # Decree 53,604 (1964-02-25) # extended summer time by one day to 1964-03-01 00:00 (start of school). Rule Brazil 1964 only - Mar 1 0:00 0 - # Decree 55,639 (1965-01-27) Rule Brazil 1965 only - Jan 31 0:00 1:00 - Rule Brazil 1965 only - Mar 31 0:00 0 - # Decree 57,303 (1965-11-22) Rule Brazil 1965 only - Dec 1 0:00 1:00 - # Decree 57,843 (1966-02-18) Rule Brazil 1966 1968 - Mar 1 0:00 0 - Rule Brazil 1966 1967 - Nov 1 0:00 1:00 - # Decree 63,429 (1968-10-15) # revoked DST. # Decree 91,698 (1985-09-27) Rule Brazil 1985 only - Nov 2 0:00 1:00 - # Decree 92,310 (1986-01-21) # Decree 92,463 (1986-03-13) Rule Brazil 1986 only - Mar 15 0:00 0 - # Decree 93,316 (1986-10-01) Rule Brazil 1986 only - Oct 25 0:00 1:00 - Rule Brazil 1987 only - Feb 14 0:00 0 - # Decree 94,922 (1987-09-22) Rule Brazil 1987 only - Oct 25 0:00 1:00 - Rule Brazil 1988 only - Feb 7 0:00 0 - # Decree 96,676 (1988-09-12) # except for the states of AC, AM, PA, RR, RO, and AP (then a territory) Rule Brazil 1988 only - Oct 16 0:00 1:00 - Rule Brazil 1989 only - Jan 29 0:00 0 - # Decree 98,077 (1989-08-21) # with the same exceptions Rule Brazil 1989 only - Oct 15 0:00 1:00 - Rule Brazil 1990 only - Feb 11 0:00 0 - # Decree 99,530 (1990-09-17) # adopted by RS, SC, PR, SP, RJ, ES, MG, GO, MS, DF. # Decree 99,629 (1990-10-19) adds BA, MT. Rule Brazil 1990 only - Oct 21 0:00 1:00 - Rule Brazil 1991 only - Feb 17 0:00 0 - # Unnumbered decree (1991-09-25) # adopted by RS, SC, PR, SP, RJ, ES, MG, BA, GO, MT, MS, DF. Rule Brazil 1991 only - Oct 20 0:00 1:00 - Rule Brazil 1992 only - Feb 9 0:00 0 - # Unnumbered decree (1992-10-16) # adopted by same states. Rule Brazil 1992 only - Oct 25 0:00 1:00 - Rule Brazil 1993 only - Jan 31 0:00 0 - # Decree 942 (1993-09-28) # adopted by same states, plus AM. # Decree 1,252 (1994-09-22; # web page corrected 2004-01-07) adopted by same states, minus AM. # Decree 1,636 (1995-09-14) # adopted by same states, plus MT and TO. # Decree 1,674 (1995-10-13) # adds AL, SE. Rule Brazil 1993 1995 - Oct Sun>=11 0:00 1:00 - Rule Brazil 1994 1995 - Feb Sun>=15 0:00 0 - Rule Brazil 1996 only - Feb 11 0:00 0 - # Decree 2,000 (1996-09-04) # adopted by same states, minus AL, SE. Rule Brazil 1996 only - Oct 6 0:00 1:00 - Rule Brazil 1997 only - Feb 16 0:00 0 - # From Daniel C. Sobral (1998-02-12): # In 1997, the DS began on October 6. The stated reason was that # because international television networks ignored Brazil's policy on DS, # they bought the wrong times on satellite for coverage of Pope's visit. # This year, the ending date of DS was postponed to March 1 # to help dealing with the shortages of electric power. # # Decree 2,317 (1997-09-04), adopted by same states. Rule Brazil 1997 only - Oct 6 0:00 1:00 - # Decree 2,495 # (1998-02-10) Rule Brazil 1998 only - Mar 1 0:00 0 - # Decree 2,780 (1998-09-11) # adopted by the same states as before. Rule Brazil 1998 only - Oct 11 0:00 1:00 - Rule Brazil 1999 only - Feb 21 0:00 0 - # Decree 3,150 # (1999-08-23) adopted by same states. # Decree 3,188 (1999-09-30) # adds SE, AL, PB, PE, RN, CE, PI, MA and RR. Rule Brazil 1999 only - Oct 3 0:00 1:00 - Rule Brazil 2000 only - Feb 27 0:00 0 - # Decree 3,592 (2000-09-06) # adopted by the same states as before. # Decree 3,630 (2000-10-13) # repeals DST in PE and RR, effective 2000-10-15 00:00. # Decree 3,632 (2000-10-17) # repeals DST in SE, AL, PB, RN, CE, PI and MA, effective 2000-10-22 00:00. # Decree 3,916 # (2001-09-13) reestablishes DST in AL, CE, MA, PB, PE, PI, RN, SE. Rule Brazil 2000 2001 - Oct Sun>=8 0:00 1:00 - Rule Brazil 2001 2006 - Feb Sun>=15 0:00 0 - # Decree 4,399 (2002-10-01) repeals DST in AL, CE, MA, PB, PE, PI, RN, SE. # 4,399 Rule Brazil 2002 only - Nov 3 0:00 1:00 - # Decree 4,844 (2003-09-24; corrected 2003-09-26) repeals DST in BA, MT, TO. # 4,844 Rule Brazil 2003 only - Oct 19 0:00 1:00 - # Decree 5,223 (2004-10-01) reestablishes DST in MT. # 5,223 Rule Brazil 2004 only - Nov 2 0:00 1:00 - # Decree 5,539 (2005-09-19), # adopted by the same states as before. Rule Brazil 2005 only - Oct 16 0:00 1:00 - # Decree 5,920 (2006-10-03), # adopted by the same states as before. Rule Brazil 2006 only - Nov 5 0:00 1:00 - Rule Brazil 2007 only - Feb 25 0:00 0 - # Decree 6,212 (2007-09-26), # adopted by the same states as before. Rule Brazil 2007 only - Oct Sun>=8 0:00 1:00 - # From Frederico A. C. Neves (2008-09-10): # According to this decree # http://www.planalto.gov.br/ccivil_03/_Ato2007-2010/2008/Decreto/D6558.htm # [t]he DST period in Brazil now on will be from the 3rd Oct Sunday to the # 3rd Feb Sunday. There is an exception on the return date when this is # the Carnival Sunday then the return date will be the next Sunday... Rule Brazil 2008 2017 - Oct Sun>=15 0:00 1:00 - Rule Brazil 2008 2011 - Feb Sun>=15 0:00 0 - # Decree 7,584 (2011-10-13) # added Bahia. Rule Brazil 2012 only - Feb Sun>=22 0:00 0 - # Decree 7,826 (2012-10-15) # removed Bahia and added Tocantins. # Decree 8,112 (2013-09-30) # removed Tocantins. Rule Brazil 2013 2014 - Feb Sun>=15 0:00 0 - Rule Brazil 2015 only - Feb Sun>=22 0:00 0 - Rule Brazil 2016 2019 - Feb Sun>=15 0:00 0 - # From Steffen Thorsen (2017-12-18): # According to many media sources, next year's DST start in Brazil will move to # the first Sunday of November # ... https://www.timeanddate.com/news/time/brazil-delays-dst-2018.html # From Steffen Thorsen (2017-12-20): # http://www.planalto.gov.br/ccivil_03/_ato2015-2018/2017/decreto/D9242.htm # From Fábio Gomes (2018-10-04): # The Brazilian president just announced a new change on this year DST. # It was scheduled to start on November 4th and it was changed to November 18th. # From Rodrigo Brüning Wessler (2018-10-15): # The Brazilian government just announced that the change in DST was # canceled.... Maybe the president Michel Temer also woke up one hour # earlier today. :) Rule Brazil 2018 only - Nov Sun>=1 0:00 1:00 - # The last ruleset listed above says that the following states observed DST: # DF, ES, GO, MG, MS, MT, PR, RJ, RS, SC, SP. # # From Steffen Thorsen (2019-04-05): # According to multiple sources the Brazilian president wants to get rid of DST. # https://gmconline.com.br/noticias/politica/bolsonaro-horario-de-verao-deve-acabar-este-ano # https://g1.globo.com/economia/noticia/2019/04/05/governo-anuncia-fim-do-horario-de-verao.ghtml # From Marcus Diniz (2019-04-25): # Brazil no longer has DST changes - decree signed today # https://g1.globo.com/politica/noticia/2019/04/25/bolsonaro-assina-decreto-que-acaba-com-o-horario-de-verao.ghtml # From Daniel Soares de Oliveira (2019-04-26): # http://www.planalto.gov.br/ccivil_03/_Ato2019-2022/2019/Decreto/D9772.htm # Zone NAME STDOFF RULES FORMAT [UNTIL] # # Fernando de Noronha (administratively part of PE) Zone America/Noronha -2:09:40 - LMT 1914 -2:00 Brazil %z 1990 Sep 17 -2:00 - %z 1999 Sep 30 -2:00 Brazil %z 2000 Oct 15 -2:00 - %z 2001 Sep 13 -2:00 Brazil %z 2002 Oct 1 -2:00 - %z # Other Atlantic islands have no permanent settlement. # These include Trindade and Martim Vaz (administratively part of ES), # Rocas Atoll (RN), and the St Peter and St Paul Archipelago (PE). # Fernando de Noronha was a separate territory from 1942-09-02 to 1989-01-01; # it also included the Penedos. # # Amapá (AP), east Pará (PA) # East Pará includes Belém, Marabá, Serra Norte, and São Félix do Xingu. # The division between east and west Pará is the river Xingu. # In the north a very small part from the river Javary (now Jari I guess, # the border with Amapá) to the Amazon, then to the Xingu. Zone America/Belem -3:13:56 - LMT 1914 -3:00 Brazil %z 1988 Sep 12 -3:00 - %z # # west Pará (PA) # West Pará includes Altamira, Óbidos, Prainha, Oriximiná, and Santarém. Zone America/Santarem -3:38:48 - LMT 1914 -4:00 Brazil %z 1988 Sep 12 -4:00 - %z 2008 Jun 24 0:00 -3:00 - %z # # Maranhão (MA), Piauí (PI), Ceará (CE), Rio Grande do Norte (RN), # Paraíba (PB) Zone America/Fortaleza -2:34:00 - LMT 1914 -3:00 Brazil %z 1990 Sep 17 -3:00 - %z 1999 Sep 30 -3:00 Brazil %z 2000 Oct 22 -3:00 - %z 2001 Sep 13 -3:00 Brazil %z 2002 Oct 1 -3:00 - %z # # Pernambuco (PE) (except Atlantic islands) Zone America/Recife -2:19:36 - LMT 1914 -3:00 Brazil %z 1990 Sep 17 -3:00 - %z 1999 Sep 30 -3:00 Brazil %z 2000 Oct 15 -3:00 - %z 2001 Sep 13 -3:00 Brazil %z 2002 Oct 1 -3:00 - %z # # Tocantins (TO) Zone America/Araguaina -3:12:48 - LMT 1914 -3:00 Brazil %z 1990 Sep 17 -3:00 - %z 1995 Sep 14 -3:00 Brazil %z 2003 Sep 24 -3:00 - %z 2012 Oct 21 -3:00 Brazil %z 2013 Sep -3:00 - %z # # Alagoas (AL), Sergipe (SE) Zone America/Maceio -2:22:52 - LMT 1914 -3:00 Brazil %z 1990 Sep 17 -3:00 - %z 1995 Oct 13 -3:00 Brazil %z 1996 Sep 4 -3:00 - %z 1999 Sep 30 -3:00 Brazil %z 2000 Oct 22 -3:00 - %z 2001 Sep 13 -3:00 Brazil %z 2002 Oct 1 -3:00 - %z # # Bahia (BA) # There are too many Salvadors elsewhere, so use America/Bahia instead # of America/Salvador. Zone America/Bahia -2:34:04 - LMT 1914 -3:00 Brazil %z 2003 Sep 24 -3:00 - %z 2011 Oct 16 -3:00 Brazil %z 2012 Oct 21 -3:00 - %z # # Goiás (GO), Distrito Federal (DF), Minas Gerais (MG), # Espírito Santo (ES), Rio de Janeiro (RJ), São Paulo (SP), Paraná (PR), # Santa Catarina (SC), Rio Grande do Sul (RS) Zone America/Sao_Paulo -3:06:28 - LMT 1914 -3:00 Brazil %z 1963 Oct 23 0:00 -3:00 1:00 %z 1964 -3:00 Brazil %z # # Mato Grosso do Sul (MS) Zone America/Campo_Grande -3:38:28 - LMT 1914 -4:00 Brazil %z # # Mato Grosso (MT) Zone America/Cuiaba -3:44:20 - LMT 1914 -4:00 Brazil %z 2003 Sep 24 -4:00 - %z 2004 Oct 1 -4:00 Brazil %z # # Rondônia (RO) Zone America/Porto_Velho -4:15:36 - LMT 1914 -4:00 Brazil %z 1988 Sep 12 -4:00 - %z # # Roraima (RR) Zone America/Boa_Vista -4:02:40 - LMT 1914 -4:00 Brazil %z 1988 Sep 12 -4:00 - %z 1999 Sep 30 -4:00 Brazil %z 2000 Oct 15 -4:00 - %z # # east Amazonas (AM): Boca do Acre, Jutaí, Manaus, Floriano Peixoto # The great circle line from Tabatinga to Porto Acre divides # east from west Amazonas. Zone America/Manaus -4:00:04 - LMT 1914 -4:00 Brazil %z 1988 Sep 12 -4:00 - %z 1993 Sep 28 -4:00 Brazil %z 1994 Sep 22 -4:00 - %z # # west Amazonas (AM): Atalaia do Norte, Boca do Maoco, Benjamin Constant, # Eirunepé, Envira, Ipixuna Zone America/Eirunepe -4:39:28 - LMT 1914 -5:00 Brazil %z 1988 Sep 12 -5:00 - %z 1993 Sep 28 -5:00 Brazil %z 1994 Sep 22 -5:00 - %z 2008 Jun 24 0:00 -4:00 - %z 2013 Nov 10 -5:00 - %z # # Acre (AC) Zone America/Rio_Branco -4:31:12 - LMT 1914 -5:00 Brazil %z 1988 Sep 12 -5:00 - %z 2008 Jun 24 0:00 -4:00 - %z 2013 Nov 10 -5:00 - %z # Chile # From Paul Eggert (2022-03-15): # Shanks & Pottenger says America/Santiago introduced standard time in # 1890 and rounds its UT offset to 70W40; guess that in practice this # was the same offset as in 1916-1919. It also says Pacific/Easter # standardized on 109W22 in 1890; assume this didn't change the clocks. # # Dates for America/Santiago from 1910 to 2004 are primarily from # the following source, cited by Oscar van Vlijmen (2006-10-08): # [1] Chile Law # http://www.webexhibits.org/daylightsaving/chile.html # This contains a copy of this official table: # Cambios en la hora oficial de Chile desde 1900 (retrieved 2008-03-30) # https://web.archive.org/web/20080330200901/http://www.horaoficial.cl/cambio.htm # [1] needs several corrections, though. # # The first set of corrections is from: # [2] History of the Official Time of Chile # http://www.horaoficial.cl/ing/horaof_ing.html (retrieved 2012-03-06). See: # https://web.archive.org/web/20120306042032/http://www.horaoficial.cl/ing/horaof_ing.html # This is an English translation of: # Historia de la hora oficial de Chile (retrieved 2012-10-24). See: # https://web.archive.org/web/20121024234627/http://www.horaoficial.cl/horaof.htm # A fancier Spanish version (requiring mouse-clicking) is at: # http://www.horaoficial.cl/historia_hora.php # Conflicts between [1] and [2] were resolved as follows: # # - [1] says the 1910 transition was Jan 1, [2] says Jan 10 and cites # Boletín No. 1, Aviso No. 1 (1910). Go with [2]. # # - [1] says SMT was -4:42:45, [2] says Chile's official time from # 1916 to 1919 was -4:42:46.3, the meridian of Chile's National # Astronomical Observatory (OAN), then located in what is now # Quinta Normal in Santiago. Go with [1], as this matches the meridian # referred to by the relevant Chilean laws to this day. # # - [1] says the 1918 transition was Sep 1, [2] says Sep 10 and cites # Boletín No. 22, Aviso No. 129/1918 (1918-08-23). Go with [2]. # # - [1] does not give times for transitions; assume they occur # at midnight mainland time, the current common practice. However, # go with [2]'s specification of 23:00 for the 1947-05-21 transition. # # Another correction to [1] is from Jesper Nørgaard Welen, who # wrote (2006-10-08), "I think that there are some obvious mistakes in # the suggested link from Oscar van Vlijmen,... for instance entry 66 # says that GMT-4 ended 1990-09-12 while entry 67 only begins GMT-3 at # 1990-09-15 (they should have been 1990-09-15 and 1990-09-16 # respectively), but anyhow it clears up some doubts too." # # Data for Pacific/Easter from 1910 through 1967 come from Shanks & # Pottenger. After that, for lack of better info assume # Pacific/Easter is always two hours behind America/Santiago; # this is known to work for DST transitions starting in 2008 and # may well be true for earlier transitions. # From Tim Parenti (2022-07-06): # For a brief period of roughly six weeks in 1946, DST was only observed on an # emergency basis in specific regions of central Chile; namely, "the national # territory between the provinces of Coquimbo and Concepción, inclusive". # This was enacted by Decree 3,891, dated 1946-07-13, and took effect # 1946-07-14 24:00, advancing these central regions to -03. # https://www.diariooficial.interior.gob.cl/versiones-anteriores/do-h/19460715/#page/1 # The decree contemplated "[t]hat this advancement of the Official Time, even # though it has been proposed for the cities of Santiago and Valparaíso only, # must be agreed with that of other cities, due to the connection of various # activities that require it, such as, for example, the operation of rail # services". It was originally set to expire after 30 days but was extended # through 1946-08-31 by Decree 4,506, dated 1946-08-13. # https://www.diariooficial.interior.gob.cl/versiones-anteriores/do-h/19460814/#page/1 # # Law Number 8,522, promulgated 1946-08-27, reunified Chilean clocks at their # new "Summer Time" of -04, reckoned as that of "the meridian of the # Astronomical Observatory of Lo Espejo, advanced by 42 minutes and 45 # seconds". Although this law specified the new Summer Time to start on 1 # September each year, a special "transitional article" started it a few days # early, as soon as the law took effect. As the law was to take force "from # the date of its publication in the 'Diario Oficial', which happened the # following day, presume the change took place in Santiago and its environs # from 24:00 -03 to 23:00 -04 on Wednesday 1946-08-28. Although this was a # no-op for wall clocks in the north and south of the country, put their formal # start to DST an hour later when they reached 24:00 -04. # https://www.diariooficial.interior.gob.cl/versiones-anteriores/do-h/19460828/#page/1 # After a brief "Winter Time" stint at -05 beginning 1947-04-01, Law Number # 8,777, promulgated 1947-05-17, established year-round -04 "from 23:00 on the # second day after it is published in the 'Diario Oficial'." It was published # on Monday 1947-05-19 and so took effect from Wednesday 1947-05-21 23:00. # https://www.diariooficial.interior.gob.cl/versiones-anteriores/do-h/19470519/#page/1 # From Eduardo Krell (1995-10-19): # The law says to switch to DST at midnight [24:00] on the second SATURDAY # of October.... The law is the same for March and October. # (1998-09-29): # Because of the drought this year, the government decided to go into # DST earlier (saturday 9/26 at 24:00). This is a one-time change only ... # (unless there's another dry season next year, I guess). # From Julio I. Pacheco Troncoso (1999-03-18): # Because of the same drought, the government decided to end DST later, # on April 3, (one-time change). # From Germán Poo-Caamaño (2008-03-03): # Due to drought, Chile extends Daylight Time in three weeks. This # is one-time change (Saturday 3/29 at 24:00 for America/Santiago # and Saturday 3/29 at 22:00 for Pacific/Easter) # The Supreme Decree is located at # http://www.shoa.cl/servicios/supremo316.pdf # # From José Miguel Garrido (2008-03-05): # http://www.shoa.cl/noticias/2008/04hora/hora.htm # From Angel Chiang (2010-03-04): # Subject: DST in Chile exceptionally extended to 3 April due to earthquake # http://www.gobiernodechile.cl/viewNoticia.aspx?idArticulo=30098 # # From Arthur David Olson (2010-03-06): # Angel Chiang's message confirmed by Julio Pacheco; Julio provided a patch. # From Glenn Eychaner (2011-03-28): # http://diario.elmercurio.com/2011/03/28/_portada/_portada/noticias/7565897A-CA86-49E6-9E03-660B21A4883E.htm?id=3D{7565897A-CA86-49E6-9E03-660B21A4883E} # In English: # Chile's clocks will go back an hour this year on the 7th of May instead # of this Saturday. They will go forward again the 3rd Saturday in # August, not in October as they have since 1968. # From Mauricio Parada (2012-02-22), translated by Glenn Eychaner (2012-02-23): # As stated in the website of the Chilean Energy Ministry # http://www.minenergia.cl/ministerio/noticias/generales/gobierno-anuncia-fechas-de-cambio-de.html # The Chilean Government has decided to postpone the entrance into winter time # (to leave DST) from March 11 2012 to April 28th 2012.... # Quote from the website communication: # # 6. For the year 2012, the dates of entry into winter time will be as follows: # a. Saturday April 28, 2012, clocks should go back 60 minutes; that is, at # 23:59:59, instead of passing to 0:00, the time should be adjusted to be 23:00 # of the same day. # b. Saturday, September 1, 2012, clocks should go forward 60 minutes; that is, # at 23:59:59, instead of passing to 0:00, the time should be adjusted to be # 01:00 on September 2. # From Steffen Thorsen (2013-02-15): # According to several news sources, Chile has extended DST this year, # they will end DST later and start DST earlier than planned. They # hope to save energy. The new end date is 2013-04-28 00:00 and new # start date is 2013-09-08 00:00.... # http://www.gob.cl/informa/2013/02/15/gobierno-anuncia-fechas-de-cambio-de-hora-para-el-ano-2013.htm # From José Miguel Garrido (2014-02-19): # Today appeared in the Diario Oficial a decree amending the time change # dates to 2014. # DST End: last Saturday of April 2014 (Sun 27 Apr 2014 03:00 UTC) # DST Start: first Saturday of September 2014 (Sun 07 Sep 2014 04:00 UTC) # From Tim Parenti (2025-03-22): # Decreto 307 of 2014 of the Ministry of the Interior and Public Security, # promulgated 2014-01-30 and published 2014-02-19: # https://www.diariooficial.interior.gob.cl/media/2014/02/19/do-20140219.pdf#page=1 # https://www.bcn.cl/leychile/navegar?idNorma=1059557 # From Eduardo Romero Urra (2015-03-03): # Today has been published officially that Chile will use the DST time # permanently until March 25 of 2017 # From Tim Parenti (2025-03-22): # Decreto 106 of 2015 of the Ministry of the Interior and Public Security, # promulgated 2015-01-27 and published 2015-03-03: # https://www.diariooficial.interior.gob.cl/media/2015/03/03/do-20150303.pdf#page=1 # https://www.bcn.cl/leychile/navegar?idNorma=1075157 # From Juan Correa (2016-03-18): # The decree regarding DST has been published in today's Official Gazette... # It does consider the second Saturday of May and August as the dates # for the transition; and it lists DST dates until 2019, but I think # this scheme will stick. # From Paul Eggert (2016-03-18): # The decree says transitions occur at 24:00; in practice this appears # to mean 24:00 mainland time, not 24:00 local time, so that Easter # Island is always two hours behind the mainland. # From Tim Parenti (2025-03-22): # Decreto 253 of 2016 of the Ministry of the Interior and Public Security, # promulgated 2016-03-16 and published 2016-03-18. # https://www.diariooficial.interior.gob.cl/media/2016/03/18/do-20160318.pdf#page=1 # https://www.bcn.cl/leychile/navegar?idNorma=1088502 # From Juan Correa (2016-12-04): # Magallanes region ... will keep DST (UTC -3) all year round.... # http://www.soychile.cl/Santiago/Sociedad/2016/12/04/433428/Bachelet-firmo-el-decreto-para-establecer-un-horario-unico-para-la-Region-de-Magallanes.aspx # From Tim Parenti (2025-03-22), via Deborah Goldsmith (2017-01-19): # Decreto 1820 of 2016 of the Ministry of the Interior and Public Security, # promulgated 2016-12-02 and published 2017-01-17: # https://www.diariooficial.interior.gob.cl/publicaciones/2017/01/17/41660/01/1169626.pdf # https://www.bcn.cl/leychile/Navegar?idNorma=1099217 # Model this as a change to standard offset effective 2016-12-04. # From Juan Correa (2018-08-13): # As of moments ago, the Ministry of Energy in Chile has announced the new # schema for DST. ... Announcement in video (in Spanish): # https://twitter.com/MinEnergia/status/1029000399129374720 # From Yonathan Dossow (2018-08-13): # The video says "first Saturday of September", we all know it means Sunday at # midnight. # From Tim Parenti (2018-08-13): # Translating the captions on the video at 0:44-0:55, "We want to announce as # Government that from 2019, Winter Time will be increased to 5 months, between # the first Saturday of April and the first Saturday of September." # At 2:08-2:20, "The Magallanes region will maintain its current time, as # decided by the citizens during 2017, but our Government will promote a # regional dialogue table to gather their opinion on this matter." # https://twitter.com/MinEnergia/status/1029009354001973248 # "We will keep the new time policy unchanged for at least the next 4 years." # So we extend the new rules on Saturdays at 24:00 mainland time indefinitely. # From Tim Parenti (2025-03-22), via Juan Correa (2019-02-04): # Decreto 1286 of 2018 of the Ministry of the Interior and Public Security, # promulgated 2018-09-21 and published 2018-11-23: # https://www.diariooficial.interior.gob.cl/publicaciones/2018/11/23/42212/01/1498738.pdf # https://www.bcn.cl/leychile/Navegar?idNorma=1125760 # From Juan Correa (2022-04-02): # I found there was a decree published last Thursday that will keep # Magallanes region to UTC -3 "indefinitely". # From Tim Parenti (2025-03-22): # Decreto 143 of 2022 of the Ministry of the Interior and Public Security, # promulgated 2022-03-29 and published 2022-03-31: # https://www.diariooficial.interior.gob.cl/publicaciones/2022/03/31/43217-B/01/2108910.pdf # https://www.bcn.cl/leychile/Navegar?idNorma=1174342 # From Juan Correa (2022-08-09): # the Internal Affairs Ministry (Ministerio del Interior) informed DST # for America/Santiago will start on midnight of September 11th; # and will end on April 1st, 2023. Magallanes region (America/Punta_Arenas) # will keep UTC -3 "indefinitely"... This is because on September 4th # we will have a voting whether to approve a new Constitution. # # From Tim Parenti (2025-03-22), via Eduardo Romero Urra (2022-08-17): # Decreto 224 of 2022 of the Ministry of the Interior and Public Security, # promulgated 2022-07-14 and published 2022-08-13: # https://www.diariooficial.interior.gob.cl/publicaciones/2022/08/13/43327/01/2172567.pdf # https://www.bcn.cl/leychile/navegar?idNorma=1179983 # # From Paul Eggert (2022-08-17): # Although the presidential decree stops at fall 2026, assume that # similar DST rules will continue thereafter. # From Paul Eggert (2025-01-15): # Diario Regional Aysén's Sebastián Martel reports that 94% of Aysén # citizens polled in November favored changing the rules from # -04/-03-with-DST to -03 all year... # https://www.diarioregionalaysen.cl/noticia/actualidad/2024/12/presentan-decision-que-gano-la-votacion-sobre-el-cambio-del-huso-horario-en-aysen # # From Yonathan Dossow (2025-03-20): # [T]oday we have more confirmation of the change. [Aysén] region will keep # UTC-3 all year... # https://www.cnnchile.com/pais/region-de-aysen-mantendra-horario-de-verano-todo-el-ano_20250320/ # https://www.latercera.com/nacional/noticia/tras-consulta-ciudadana-region-de-aysen-mantendra-el-horario-de-verano-durante-todo-el-ano/ # https://x.com/min_interior/status/1902692504270672098 # # From Tim Parenti (2025-03-22), via Eduardo Romero Urra (2025-03-20): # Decreto 93 of 2025 of the Ministry of the Interior and Public Security, # promulgated 2025-03-11 and published 2025-03-20: # https://www.diariooficial.interior.gob.cl/publicaciones/2025/03/20/44104/01/2624263.pdf # https://www.bcn.cl/leychile/Navegar?idNorma=1211955 # Model this as a change to standard offset effective 2025-03-20. # Rule NAME FROM TO - IN ON AT SAVE LETTER/S Rule Chile 1927 1931 - Sep 1 0:00 1:00 - Rule Chile 1928 1932 - Apr 1 0:00 0 - Rule Chile 1968 only - Nov 3 4:00u 1:00 - Rule Chile 1969 only - Mar 30 3:00u 0 - Rule Chile 1969 only - Nov 23 4:00u 1:00 - Rule Chile 1970 only - Mar 29 3:00u 0 - Rule Chile 1971 only - Mar 14 3:00u 0 - Rule Chile 1970 1972 - Oct Sun>=9 4:00u 1:00 - Rule Chile 1972 1986 - Mar Sun>=9 3:00u 0 - Rule Chile 1973 only - Sep 30 4:00u 1:00 - Rule Chile 1974 1987 - Oct Sun>=9 4:00u 1:00 - Rule Chile 1987 only - Apr 12 3:00u 0 - Rule Chile 1988 1990 - Mar Sun>=9 3:00u 0 - Rule Chile 1988 1989 - Oct Sun>=9 4:00u 1:00 - Rule Chile 1990 only - Sep 16 4:00u 1:00 - Rule Chile 1991 1996 - Mar Sun>=9 3:00u 0 - Rule Chile 1991 1997 - Oct Sun>=9 4:00u 1:00 - Rule Chile 1997 only - Mar 30 3:00u 0 - Rule Chile 1998 only - Mar Sun>=9 3:00u 0 - Rule Chile 1998 only - Sep 27 4:00u 1:00 - Rule Chile 1999 only - Apr 4 3:00u 0 - Rule Chile 1999 2010 - Oct Sun>=9 4:00u 1:00 - Rule Chile 2000 2007 - Mar Sun>=9 3:00u 0 - # N.B.: the end of March 29 in Chile is March 30 in Universal time, # which is used below in specifying the transition. Rule Chile 2008 only - Mar 30 3:00u 0 - Rule Chile 2009 only - Mar Sun>=9 3:00u 0 - Rule Chile 2010 only - Apr Sun>=1 3:00u 0 - Rule Chile 2011 only - May Sun>=2 3:00u 0 - Rule Chile 2011 only - Aug Sun>=16 4:00u 1:00 - Rule Chile 2012 2014 - Apr Sun>=23 3:00u 0 - Rule Chile 2012 2014 - Sep Sun>=2 4:00u 1:00 - Rule Chile 2016 2018 - May Sun>=9 3:00u 0 - Rule Chile 2016 2018 - Aug Sun>=9 4:00u 1:00 - Rule Chile 2019 max - Apr Sun>=2 3:00u 0 - Rule Chile 2019 2021 - Sep Sun>=2 4:00u 1:00 - Rule Chile 2022 only - Sep Sun>=9 4:00u 1:00 - Rule Chile 2023 max - Sep Sun>=2 4:00u 1:00 - # IATA SSIM anomalies: (1992-02) says 1992-03-14; # (1996-09) says 1998-03-08. Ignore these. # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone America/Santiago -4:42:45 - LMT 1890 -4:42:45 - SMT 1910 Jan 10 # Santiago Mean Time -5:00 - %z 1916 Jul 1 -4:42:45 - SMT 1918 Sep 10 -4:00 - %z 1919 Jul 1 -4:42:45 - SMT 1927 Sep 1 -5:00 Chile %z 1932 Sep 1 -4:00 - %z 1942 Jun 1 -5:00 - %z 1942 Aug 1 -4:00 - %z 1946 Jul 14 24:00 -4:00 1:00 %z 1946 Aug 28 24:00 # central CL -5:00 1:00 %z 1947 Mar 31 24:00 -5:00 - %z 1947 May 21 23:00 -4:00 Chile %z Zone America/Coyhaique -4:48:16 - LMT 1890 -4:42:45 - SMT 1910 Jan 10 -5:00 - %z 1916 Jul 1 -4:42:45 - SMT 1918 Sep 10 -4:00 - %z 1919 Jul 1 -4:42:45 - SMT 1927 Sep 1 -5:00 Chile %z 1932 Sep 1 -4:00 - %z 1942 Jun 1 -5:00 - %z 1942 Aug 1 -4:00 - %z 1946 Aug 28 24:00 -5:00 1:00 %z 1947 Mar 31 24:00 -5:00 - %z 1947 May 21 23:00 -4:00 Chile %z 2025 Mar 20 -3:00 - %z Zone America/Punta_Arenas -4:43:40 - LMT 1890 -4:42:45 - SMT 1910 Jan 10 -5:00 - %z 1916 Jul 1 -4:42:45 - SMT 1918 Sep 10 -4:00 - %z 1919 Jul 1 -4:42:45 - SMT 1927 Sep 1 -5:00 Chile %z 1932 Sep 1 -4:00 - %z 1942 Jun 1 -5:00 - %z 1942 Aug 1 -4:00 - %z 1946 Aug 28 24:00 -5:00 1:00 %z 1947 Mar 31 24:00 -5:00 - %z 1947 May 21 23:00 -4:00 Chile %z 2016 Dec 4 -3:00 - %z Zone Pacific/Easter -7:17:28 - LMT 1890 -7:17:28 - EMT 1932 Sep # Easter Mean Time -7:00 Chile %z 1982 Mar 14 3:00u # Easter Time -6:00 Chile %z # # Salas y Gómez Island is uninhabited. # Other Chilean locations, including Juan Fernández Is, Desventuradas Is, # and Antarctic bases, are like America/Santiago. # Antarctic base using South American rules # (See the file 'antarctica' for more.) # # Palmer, Anvers Island, since 1965 (moved 2 miles in 1968) # # From Ethan Dicks (1996-10-06): # It keeps the same time as Punta Arenas, Chile, because, just like us # and the South Pole, that's the other end of their supply line.... # I verified with someone who was there that since 1980, # Palmer has followed Chile. Prior to that, before the Falklands War, # Palmer used to be supplied from Argentina. # # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone Antarctica/Palmer 0 - -00 1965 -4:00 Arg %z 1969 Oct 5 -3:00 Arg %z 1982 May -4:00 Chile %z 2016 Dec 4 -3:00 - %z # Colombia # Milne gives 4:56:16.4 for Bogotá time in 1899. He writes, # "A variation of fifteen minutes in the public clocks of Bogota is not rare." # From Alois Treindl (2022-11-10): # End of time change in Colombia 1993 ... should be 6 February 24h ... # DECRETO 267 DE 1993 # https://www.suin-juriscol.gov.co/viewDocument.asp?ruta=Decretos/1061335 # Rule NAME FROM TO - IN ON AT SAVE LETTER/S Rule CO 1992 only - May 3 0:00 1:00 - Rule CO 1993 only - Feb 6 24:00 0 - # Zone NAME STDOFF RULES FORMAT [UNTIL] #STDOFF -4:56:16.4 Zone America/Bogota -4:56:16 - LMT 1884 Mar 13 -4:56:16 - BMT 1914 Nov 23 # Bogotá Mean Time -5:00 CO %z # Malpelo, Providencia, San Andres # no information; probably like America/Bogota # Ecuador # # Milne says the Central and South American Telegraph Company used -5:24:15. # # From Alois Treindl (2016-12-15): # https://www.elcomercio.com/actualidad/hora-sixto-1993.html # ... Whether the law applied also to Galápagos, I do not know. # From Paul Eggert (2016-12-15): # https://www.elcomercio.com/afull/modificacion-husohorario-ecuador-presidentes-decreto.html # This says President Sixto Durán Ballén signed decree No. 285, which # established DST from 1992-11-28 to 1993-02-05; it does not give transition # times. The people called it "hora de Sixto" ("Sixto hour"). The change did # not go over well; a popular song "Qué hora es" by Jaime Guevara had lyrics # that included "Amanecía en mitad de la noche, los guaguas iban a clase sin # sol" ("It was dawning in the middle of the night, the buses went to class # without sun"). Although Ballén's campaign slogan was "Ni un paso atrás" # (Not one step back), the clocks went back in 1993 and the experiment was not # repeated. For now, assume transitions were at 00:00 local time country-wide. # # Rule NAME FROM TO - IN ON AT SAVE LETTER/S Rule Ecuador 1992 only - Nov 28 0:00 1:00 - Rule Ecuador 1993 only - Feb 5 0:00 0 - # # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone America/Guayaquil -5:19:20 - LMT 1890 -5:14:00 - QMT 1931 # Quito Mean Time -5:00 Ecuador %z Zone Pacific/Galapagos -5:58:24 - LMT 1931 # Puerto Baquerizo Moreno -5:00 - %z 1986 -6:00 Ecuador %z # Falklands # From Paul Eggert (2006-03-22): # Between 1990 and 2000 inclusive, Shanks & Pottenger and the IATA agree except # the IATA gives 1996-09-08. Go with Shanks & Pottenger. # From Falkland Islands Government Office, London (2001-01-22) # via Jesper Nørgaard: # ... the clocks revert back to Local Mean Time at 2 am on Sunday 15 # April 2001 and advance one hour to summer time at 2 am on Sunday 2 # September. It is anticipated that the clocks will revert back at 2 # am on Sunday 21 April 2002 and advance to summer time at 2 am on # Sunday 1 September. # From Rives McDow (2001-02-13): # # I have communicated several times with people there, and the last # time I had communications that was helpful was in 1998. Here is # what was said then: # # "The general rule was that Stanley used daylight saving and the Camp # did not. However for various reasons many people in the Camp have # started to use daylight saving (known locally as 'Stanley Time') # There is no rule as to who uses daylight saving - it is a matter of # personal choice and so it is impossible to draw a map showing who # uses it and who does not. Any list would be out of date as soon as # it was produced. This year daylight saving ended on April 18/19th # and started again on September 12/13th. I do not know what the rule # is, but can find out if you like. We do not change at the same time # as UK or Chile." # # I did have in my notes that the rule was "Second Saturday in Sep at # 0:00 until third Saturday in Apr at 0:00". I think that this does # not agree in some cases with Shanks; is this true? # # Also, there is no mention in the list that some areas in the # Falklands do not use DST. I have found in my communications there # that these areas are on the western half of East Falkland and all of # West Falkland. Stanley is the only place that consistently observes # DST. Again, as in other places in the world, the farmers don't like # it. West Falkland is almost entirely sheep farmers. # # I know one lady there that keeps a list of which farm keeps DST and # which doesn't each year. She runs a shop in Stanley, and says that # the list changes each year. She uses it to communicate to her # customers, catching them when they are home for lunch or dinner. # From Paul Eggert (2001-03-05): # For now, we'll just record the time in Stanley, since we have no # better info. # From Steffen Thorsen (2011-04-01): # The Falkland Islands will not turn back clocks this winter, but stay on # daylight saving time. # # One source: # http://www.falklandnews.com/public/story.cfm?get=5914&source=3 # # We have gotten this confirmed by a clerk of the legislative assembly: # Normally the clocks revert to Local Mean Time (UTC/GMT -4 hours) on the # third Sunday of April at 0200hrs and advance to Summer Time (UTC/GMT -3 # hours) on the first Sunday of September at 0200hrs. # # IMPORTANT NOTE: During 2011, on a trial basis, the Falkland Islands # will not revert to local mean time, but clocks will remain on Summer # time (UTC/GMT - 3 hours) throughout the whole of 2011. Any long term # change to local time following the trial period will be notified. # # From Andrew Newman (2012-02-24) # A letter from Justin McPhee, Chief Executive, # Cable & Wireless Falkland Islands (dated 2012-02-22) # states... # The current Atlantic/Stanley entry under South America expects the # clocks to go back to standard Falklands Time (FKT) on the 15th April. # The database entry states that in 2011 Stanley was staying on fixed # summer time on a trial basis only. FIG need to contact IANA and/or # the maintainers of the database to inform them we're adopting # the same policy this year and suggest recommendations for future years. # # For now we will assume permanent -03 for the Falklands # until advised differently (to apply for 2012 and beyond, after the 2011 # experiment was apparently successful.) # Rule NAME FROM TO - IN ON AT SAVE LETTER/S Rule Falk 1937 1938 - Sep lastSun 0:00 1:00 - Rule Falk 1938 1942 - Mar Sun>=19 0:00 0 - Rule Falk 1939 only - Oct 1 0:00 1:00 - Rule Falk 1940 1942 - Sep lastSun 0:00 1:00 - Rule Falk 1943 only - Jan 1 0:00 0 - Rule Falk 1983 only - Sep lastSun 0:00 1:00 - Rule Falk 1984 1985 - Apr lastSun 0:00 0 - Rule Falk 1984 only - Sep 16 0:00 1:00 - Rule Falk 1985 2000 - Sep Sun>=9 0:00 1:00 - Rule Falk 1986 2000 - Apr Sun>=16 0:00 0 - Rule Falk 2001 2010 - Apr Sun>=15 2:00 0 - Rule Falk 2001 2010 - Sep Sun>=1 2:00 1:00 - # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone Atlantic/Stanley -3:51:24 - LMT 1890 -3:51:24 - SMT 1912 Mar 12 # Stanley Mean Time -4:00 Falk %z 1983 May -3:00 Falk %z 1985 Sep 15 -4:00 Falk %z 2010 Sep 5 2:00 -3:00 - %z # French Guiana # For the 1911/1912 establishment of standard time in French possessions, see: # Société Française de Physique, Recueil de constantes physiques (1913), # page 752, 18b. # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone America/Cayenne -3:29:20 - LMT 1911 Jul 1 -4:00 - %z 1967 Oct -3:00 - %z # Guyana # From P Chan (2020-11-27): # https://books.google.com/books?id=5-5CAQAAMAAJ&pg=SA1-PA547 # The Official Gazette of British Guiana. (New Series.) Vol. XL. July to # December, 1915, p 1547, lists as several notes: # "Local Mean Time 3 hours 52 mins. 39 secs. slow of Greenwich Mean Time # (Georgetown.) From 1st August, 1911, British Guiana Standard Mean Time 4 # hours slow of Greenwich Mean Time, by notice in Official Gazette on 1st July, # 1911. From 1st March, 1915, British Guiana Standard Mean Time 3 hours 45 # mins. 0 secs. slow of Greenwich Mean Time, by notice in Official Gazette on # 23rd January, 1915." # # https://parliament.gov.gy/documents/acts/10923-act_no._27_of_1975_-_interpretation_and_general_clauses_(amendment)_act_1975.pdf # Interpretation and general clauses (Amendment) Act 1975 (Act No. 27 of 1975) # [dated 1975-07-31] # "This Act...shall come into operation on 1st August, 1975." # "...where any expression of time occurs...the time referred to shall signify # the standard time of Guyana which shall be three hours behind Greenwich Mean # Time." # # Circular No. 10/1992 dated 1992-03-20 # https://dps.gov.gy/wp-content/uploads/2018/12/1992-03-20-Circular-010.pdf # "...cabinet has decided that with effect from Sunday 29th March, 1992, Guyana # Standard Time would be re-established at 01:00 hours by adjusting the hands # of the clock back to 24:00 hours." # Legislated in the Interpretation and general clauses (Amendment) Act 1992 # (Act No. 6 of 1992) [passed 1992-03-27, published 1992-04-18] # https://parliament.gov.gy/documents/acts/5885-6_of_1992_interpretation_and_general_clauses_(amendment)_act_1992.pdf # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone America/Guyana -3:52:39 - LMT 1911 Aug 1 # Georgetown -4:00 - %z 1915 Mar 1 -3:45 - %z 1975 Aug 1 -3:00 - %z 1992 Mar 29 1:00 -4:00 - %z # Paraguay # # From Paul Eggert (2006-03-22): # Shanks & Pottenger say that spring transitions are 01:00 -> 02:00, # and autumn transitions are 00:00 -> 23:00. Go with pre-1999 # editions of Shanks, and with the IATA, who say transitions occur at 00:00. # # From Waldemar Villamayor-Venialbo (2013-09-20): # No time of the day is established for the adjustment, so people normally # adjust their clocks at 0 hour of the given dates. # # Rule NAME FROM TO - IN ON AT SAVE LETTER/S Rule Para 1975 1988 - Oct 1 0:00 1:00 - Rule Para 1975 1978 - Mar 1 0:00 0 - Rule Para 1979 1991 - Apr 1 0:00 0 - Rule Para 1989 only - Oct 22 0:00 1:00 - Rule Para 1990 only - Oct 1 0:00 1:00 - Rule Para 1991 only - Oct 6 0:00 1:00 - Rule Para 1992 only - Mar 1 0:00 0 - Rule Para 1992 only - Oct 5 0:00 1:00 - Rule Para 1993 only - Mar 31 0:00 0 - Rule Para 1993 1995 - Oct 1 0:00 1:00 - Rule Para 1994 1995 - Feb lastSun 0:00 0 - Rule Para 1996 only - Mar 1 0:00 0 - # IATA SSIM (2000-02) says 1999-10-10; ignore this for now. # From Steffen Thorsen (2000-10-02): # I have three independent reports that Paraguay changed to DST this Sunday # (10-01). # # Translated by Gwillim Law (2001-02-27) from # Noticias, a daily paper in Asunción, Paraguay (2000-10-01): # http://www.diarionoticias.com.py/011000/nacional/naciona1.htm # Starting at 0:00 today, the clock will be set forward 60 minutes, in # fulfillment of Decree No. 7,273 of the Executive Power.... The time change # system has been operating for several years. Formerly there was a separate # decree each year; the new law has the same effect, but permanently. Every # year, the time will change on the first Sunday of October; likewise, the # clock will be set back on the first Sunday of March. # Rule Para 1996 2001 - Oct Sun>=1 0:00 1:00 - # IATA SSIM (1997-09) says Mar 1; go with Shanks & Pottenger. Rule Para 1997 only - Feb lastSun 0:00 0 - # Shanks & Pottenger say 1999-02-28; IATA SSIM (1999-02) says 1999-02-27, but # (1999-09) reports no date; go with above sources and Gerd Knops (2001-02-27). Rule Para 1998 2001 - Mar Sun>=1 0:00 0 - # From Rives McDow (2002-02-28): # A decree was issued in Paraguay (No. 16350) on 2002-02-26 that changed the # dst method to be from the first Sunday in September to the first Sunday in # April. Rule Para 2002 2004 - Apr Sun>=1 0:00 0 - Rule Para 2002 2003 - Sep Sun>=1 0:00 1:00 - # # From Jesper Nørgaard Welen (2005-01-02): # There are several sources that claim that Paraguay made # a timezone rule change in autumn 2004. # From Steffen Thorsen (2005-01-05): # Decree 1,867 (2004-03-05) # From Carlos Raúl Perasso via Jesper Nørgaard Welen (2006-10-13) # http://www.presidencia.gov.py/decretos/D1867.pdf Rule Para 2004 2009 - Oct Sun>=15 0:00 1:00 - Rule Para 2005 2009 - Mar Sun>=8 0:00 0 - # From Carlos Raúl Perasso (2010-02-18): # By decree number 3958 issued yesterday # http://www.presidencia.gov.py/v1/wp-content/uploads/2010/02/decreto3958.pdf # Paraguay changes its DST schedule, postponing the March rule to April and # modifying the October date. The decree reads: # ... # Art. 1. It is hereby established that from the second Sunday of the month of # April of this year (2010), the official time is to be set back 60 minutes, # and that on the first Sunday of the month of October, it is to be set # forward 60 minutes, in all the territory of the Paraguayan Republic. # ... Rule Para 2010 2024 - Oct Sun>=1 0:00 1:00 - Rule Para 2010 2012 - Apr Sun>=8 0:00 0 - # # From Steffen Thorsen (2013-03-07): # Paraguay will end DST on 2013-03-24 00:00.... # http://www.ande.gov.py/interna.php?id=1075 # # From Carlos Raúl Perasso (2013-03-15): # The change in Paraguay is now final. Decree number 10780 # http://www.presidencia.gov.py/uploads/pdf/presidencia-3b86ff4b691c79d4f5927ca964922ec74772ce857c02ca054a52a37b49afc7fb.pdf # From Carlos Raúl Perasso (2014-02-28): # Decree 1264 can be found at: # http://www.presidencia.gov.py/archivos/documentos/DECRETO1264_ey9r8zai.pdf # # From Paul Eggert (2023-07-26): # Transition dates are now set by Law No. 7115, not by presidential decree. # https://www.abc.com.py/politica/2023/07/12/promulgacion-el-cambio-de-hora-sera-por-ley/ # From Carlos Raúl Perasso (2023-07-27): # http://silpy.congreso.gov.py/descarga/ley-144138 Rule Para 2013 2024 - Mar Sun>=22 0:00 0 - # # From Heitor David Pinto (2024-09-24): # Today the Congress of Paraguay passed a bill to observe UTC-3 permanently.... # The text of the bill says that it would enter into force on the first # Sunday in October 2024, the same date currently scheduled to start DST.... # https://silpy.congreso.gov.py/web/expediente/132531 # (2024-10-14): # The president approved the law on 11 October 2024, # and it was officially published on 14 October 2024. # https://www.gacetaoficial.gov.py/index/detalle_publicacion/89723 # The text of the law says that it enters into force on the first # Sunday in October 2024 (6 October 2024). But the constitution # prohibits retroactive effect, and the civil code says that laws # enter into force on the day after their publication or on the day # that they specify, and it also says that they don't have retroactive # effect. So I think that the time change on 6 October 2024 should # still be considered as DST according to the previous law, and # permanently UTC-3 from 15 October 2024 according to the new law.... # https://www.constituteproject.org/constitution/Paraguay_2011 # https://www.oas.org/dil/esp/codigo_civil_paraguay.pdf # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone America/Asuncion -3:50:40 - LMT 1890 -3:50:40 - AMT 1931 Oct 10 # Asunción Mean Time -4:00 - %z 1972 Oct -3:00 - %z 1974 Apr -4:00 Para %z 2024 Oct 15 -3:00 - %z # Peru # # From Evelyn C. Leeper via Mark Brader (2003-10-26) # : # When we were in Peru in 1985-1986, they apparently switched over # sometime between December 29 and January 3 while we were on the Amazon. # # From Paul Eggert (2006-03-22): # Shanks & Pottenger don't have this transition. Assume 1986 was like 1987. # Rule NAME FROM TO - IN ON AT SAVE LETTER/S Rule Peru 1938 only - Jan 1 0:00 1:00 - Rule Peru 1938 only - Apr 1 0:00 0 - Rule Peru 1938 1939 - Sep lastSun 0:00 1:00 - Rule Peru 1939 1940 - Mar Sun>=24 0:00 0 - Rule Peru 1986 1987 - Jan 1 0:00 1:00 - Rule Peru 1986 1987 - Apr 1 0:00 0 - Rule Peru 1990 only - Jan 1 0:00 1:00 - Rule Peru 1990 only - Apr 1 0:00 0 - # IATA is ambiguous for 1993/1995; go with Shanks & Pottenger. Rule Peru 1994 only - Jan 1 0:00 1:00 - Rule Peru 1994 only - Apr 1 0:00 0 - # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone America/Lima -5:08:12 - LMT 1890 -5:08:36 - LMT 1908 Jul 28 # Lima Mean Time? -5:00 Peru %z # South Georgia # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone Atlantic/South_Georgia -2:26:08 - LMT 1890 # Grytviken -2:00 - %z # South Sandwich Is # uninhabited; scientific personnel have wintered # Suriname # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone America/Paramaribo -3:40:40 - LMT 1911 -3:40:52 - PMT 1935 # Paramaribo Mean Time -3:40:36 - PMT 1945 Oct # The capital moved? -3:30 - %z 1984 Oct -3:00 - %z # Uruguay # From Paul Eggert (1993-11-18): # Uruguay wins the prize for the strangest peacetime manipulation of the rules. # # From Tim Parenti (2018-02-20), per Jeremie Bonjour (2018-01-31) and Michael # Deckers (2018-02-20): # ... At least they kept good records... # # http://www.armada.mil.uy/ContenidosPDFs/sohma/web/almanaque/almanaque_2018.pdf#page=36 # Page 36 of Almanaque 2018, published by the Oceanography, Hydrography, and # Meteorology Service of the Uruguayan Navy, seems to give many transitions # with greater clarity than we've had before. It directly references many laws # and decrees which are, in turn, referenced below. They can be viewed in the # public archives of the Diario Oficial (in Spanish) at # http://www.impo.com.uy/diariooficial/ # # Ley No. 3920 of 1908-06-10 placed the determination of legal time under the # auspices of the National Institute for the Prediction of Time. It is unclear # exactly what offset was used during this period, though Ley No. 7200 of # 1920-04-23 used the Observatory of the National Meteorological Institute in # Montevideo (34° 54' 33" S, 56° 12' 45" W) as its reference meridian, # retarding legal time by 15 minutes 9 seconds from 1920-04-30 24:00, # resulting in UT-04. Assume the corresponding LMT of UT-03:44:51 (given on # page 725 of the Proceedings of the Second Pan-American Scientific Congress, # 1915-1916) was in use, and merely became official from 1908-06-10. # https://www.impo.com.uy/diariooficial/1908/06/18/12 # https://www.impo.com.uy/diariooficial/1920/04/27/9 # # Ley No. 7594 of 1923-06-28 specified legal time as Observatory time advanced # by 44 minutes 51 seconds (UT-03) "from 30 September to 31 March", and by 14 # minutes 51 seconds (UT-03:30) "the rest of the year"; a message from the # National Council of Administration the same day, published directly below the # law in the Diario Oficial, specified the first transition to be 1923-09-30 # 24:00. This effectively established standard time at UT-03:30 with 30 # minutes DST. Assume transitions at 24:00 on the specified days until Ley No. # 7919 of 1926-03-05 ended this arrangement, repealing all "laws and other # provisions which oppose" it, resulting in year-round UT-03:30; a Resolución # of 1926-03-11 puts the final transition at 1926-03-31 24:00, the same as it # would have been under the previous law. # https://www.impo.com.uy/diariooficial/1923/07/02/2 # https://www.impo.com.uy/diariooficial/1926/03/10/2 # https://www.impo.com.uy/diariooficial/1926/03/18/2 # # Rule NAME FROM TO - IN ON AT SAVE LETTER/S Rule Uruguay 1923 1925 - Oct 1 0:00 0:30 - Rule Uruguay 1924 1926 - Apr 1 0:00 0 - # From Tim Parenti (2018-02-15): # http://www.impo.com.uy/diariooficial/1933/10/27/6 # # It appears Ley No. 9122 of 1933 was never published as such in the Diario # Oficial, but instead appeared as Document 26 in the Diario on Friday # 1933-10-27 as a decree made Monday 1933-10-23 and filed under the Ministry of # National Defense. It reinstituted a DST of 30 minutes (to UT-03) "from the # last Sunday of October...until the last Saturday of March." In accordance # with this provision, the first transition was explicitly specified in Article # 2 of the decree as Saturday 1933-10-28 at 24:00; that is, Sunday 1933-10-29 # at 00:00. Assume transitions at 00:00 Sunday throughout. # # Departing from the matter-of-fact nature of previous timekeeping laws, the # 1933 decree "consider[s] the advantages of...the advance of legal time": # # "Whereas: The measure adopted by almost all nations at the time of the last # World War still persists in North America and Europe, precisely because of # the economic, hygienic, and social advantages derived from such an # emergency measure... # # Whereas: The advance of the legal time during the summer seasons, by # displacing social activity near sunrise, favors the citizen populations # and especially the society that creates and works..." # # It further specified that "necessary measures" be taken to ensure that # "public spectacles finish, in general, before [01:00]." Rule Uruguay 1933 1938 - Oct lastSun 0:00 0:30 - Rule Uruguay 1934 1941 - Mar lastSat 24:00 0 - # From Tim Parenti (2018-02-15): # Most of the Rules below, and their contemporaneous Zone lines, have been # updated simply to match the Almanaque 2018. Although the document does not # list exact transition times, midnight transitions were already present in our # data here for all transitions through 2004-09, and this is both consistent # with prior transitions and verified in several decrees marked below between # 1939-09 and 2004-09, wherein the relevant text was typically of the form: # # "From 0 hours on [date], the legal time of the entire Republic will be... # # In accordance with [the preceding], on [previous date] at 24 hours, all # clocks throughout the Republic will be [advanced/retarded] by..." # # It is possible that there is greater specificity to be found for the Rules # below, but it is buried in no fewer than 40 different decrees individually # referenced by the Almanaque for the period from 1939-09 to 2014-09. # Four-fifths of these were promulgated less than two weeks before taking # effect; more than half within a week and none more than 5 weeks. Only the # handful with comments below have been checked with any thoroughness. Rule Uruguay 1939 only - Oct 1 0:00 0:30 - Rule Uruguay 1940 only - Oct 27 0:00 0:30 - # From Tim Parenti (2018-02-15): # Decreto 1145 of the Ministry of National Defense, dated 1941-07-26, specified # UT-03 from Friday 1941-08-01 00:00, citing an "urgent...need to save fuel". # http://www.impo.com.uy/diariooficial/1941/08/04/1 Rule Uruguay 1941 only - Aug 1 0:00 0:30 - # From Tim Parenti (2018-02-15): # Decreto 1866 of the Ministry of National Defense, dated 1942-12-09, specified # further advancement (to UT-02:30) from Sunday 1942-12-13 24:00. Since clocks # never went back to UT-03:30 thereafter, this is modeled as advancing standard # time by 30 minutes to UT-03, while retaining 30 minutes of DST. # http://www.impo.com.uy/diariooficial/1942/12/16/3 Rule Uruguay 1942 only - Dec 14 0:00 0:30 - Rule Uruguay 1943 only - Mar 14 0:00 0 - Rule Uruguay 1959 only - May 24 0:00 0:30 - Rule Uruguay 1959 only - Nov 15 0:00 0 - Rule Uruguay 1960 only - Jan 17 0:00 1:00 - Rule Uruguay 1960 only - Mar 6 0:00 0 - Rule Uruguay 1965 only - Apr 4 0:00 1:00 - Rule Uruguay 1965 only - Sep 26 0:00 0 - # From Tim Parenti (2018-02-15): # Decreto 321/968 of 1968-05-25, citing emergency drought measures decreed the # day before, brought clocks forward 30 minutes from Monday 1968-05-27 00:00. # http://www.impo.com.uy/diariooficial/1968/05/30/5 Rule Uruguay 1968 only - May 27 0:00 0:30 - Rule Uruguay 1968 only - Dec 1 0:00 0 - # From Tim Parenti (2018-02-15): # Decreto 188/970 of 1970-04-23 instituted restrictions on electricity # consumption "as a consequence of the current rainfall regime in the country". # Articles 13 and 14 advanced clocks by an hour from Saturday 1970-04-25 00:00. # http://www.impo.com.uy/diariooficial/1970/04/29/4 Rule Uruguay 1970 only - Apr 25 0:00 1:00 - Rule Uruguay 1970 only - Jun 14 0:00 0 - Rule Uruguay 1972 only - Apr 23 0:00 1:00 - Rule Uruguay 1972 only - Jul 16 0:00 0 - # From Tim Parenti (2018-02-15): # Decreto 29/974 of 1974-01-11, citing "the international rise in the price of # oil", advanced clocks by 90 minutes (to UT-01:30). Decreto 163/974 of # 1974-03-04 returned 60 of those minutes (to UT-02:30), and the remaining 30 # minutes followed in Decreto 679/974 of 1974-08-29. # http://www.impo.com.uy/diariooficial/1974/01/22/11 # http://www.impo.com.uy/diariooficial/1974/03/14/3 # http://www.impo.com.uy/diariooficial/1974/09/04/6 Rule Uruguay 1974 only - Jan 13 0:00 1:30 - Rule Uruguay 1974 only - Mar 10 0:00 0:30 - Rule Uruguay 1974 only - Sep 1 0:00 0 - Rule Uruguay 1974 only - Dec 22 0:00 1:00 - Rule Uruguay 1975 only - Mar 30 0:00 0 - Rule Uruguay 1976 only - Dec 19 0:00 1:00 - Rule Uruguay 1977 only - Mar 6 0:00 0 - Rule Uruguay 1977 only - Dec 4 0:00 1:00 - Rule Uruguay 1978 1979 - Mar Sun>=1 0:00 0 - Rule Uruguay 1978 only - Dec 17 0:00 1:00 - Rule Uruguay 1979 only - Apr 29 0:00 1:00 - Rule Uruguay 1980 only - Mar 16 0:00 0 - # From Tim Parenti (2018-02-15): # Decreto 725/987 of 1987-12-04 cited "better use of national tourist # attractions" to advance clocks one hour from Monday 1987-12-14 00:00. # http://www.impo.com.uy/diariooficial/1988/01/25/1 Rule Uruguay 1987 only - Dec 14 0:00 1:00 - Rule Uruguay 1988 only - Feb 28 0:00 0 - Rule Uruguay 1988 only - Dec 11 0:00 1:00 - Rule Uruguay 1989 only - Mar 5 0:00 0 - Rule Uruguay 1989 only - Oct 29 0:00 1:00 - Rule Uruguay 1990 only - Feb 25 0:00 0 - # From Tim Parenti (2018-02-15), per Paul Eggert (1999-11-04): # IATA agrees as below for 1990-10 through 1993-02. Per Almanaque 2018, the # 1992/1993 season appears to be the first in over half a century where DST # both began and ended pursuant to the same decree. Rule Uruguay 1990 1991 - Oct Sun>=21 0:00 1:00 - Rule Uruguay 1991 1992 - Mar Sun>=1 0:00 0 - Rule Uruguay 1992 only - Oct 18 0:00 1:00 - Rule Uruguay 1993 only - Feb 28 0:00 0 - # From Eduardo Cota (2004-09-20): # The Uruguayan government has decreed a change in the local time.... # From Tim Parenti (2018-02-15): # Decreto 328/004 of 2004-09-15. # http://www.impo.com.uy/diariooficial/2004/09/23/documentos.pdf#page=1 Rule Uruguay 2004 only - Sep 19 0:00 1:00 - # From Steffen Thorsen (2005-03-11): # Uruguay's DST was scheduled to end on Sunday, 2005-03-13, but in order to # save energy ... it was postponed two weeks.... # From Tim Parenti (2018-02-15): # This 2005 postponement is not in Almanaque 2018. Go with the contemporaneous # reporting, which is confirmed by Decreto 107/005 of 2005-03-10 amending # Decreto 328/004: # http://www.impo.com.uy/diariooficial/2005/03/15/documentos.pdf#page=1 # The original decree specified a transition of 2005-03-12 24:00, but the new # one specified 2005-03-27 02:00. Rule Uruguay 2005 only - Mar 27 2:00 0 - # From Eduardo Cota (2005-09-27): # ...from 2005-10-09 at 02:00 local time, until 2006-03-12 at 02:00 local time, # official time in Uruguay will be at GMT -2. # From Tim Parenti (2018-02-15): # Decreto 318/005 of 2005-09-19. # http://www.impo.com.uy/diariooficial/2005/09/23/documentos.pdf#page=1 Rule Uruguay 2005 only - Oct 9 2:00 1:00 - Rule Uruguay 2006 2015 - Mar Sun>=8 2:00 0 - # From Tim Parenti (2018-02-15), per Jesper Nørgaard Welen (2006-09-06): # Decreto 311/006 of 2006-09-04 established regular DST from the first Sunday # of October at 02:00 through the second Sunday of March at 02:00. Almanaque # 2018 appears to have a few typoed dates through this period; ignore them. # http://www.impo.com.uy/diariooficial/2006/09/08/documentos.pdf#page=1 Rule Uruguay 2006 2014 - Oct Sun>=1 2:00 1:00 - # From Steffen Thorsen (2015-06-30): # ... it looks like they will not be using DST the coming summer: # http://www.elobservador.com.uy/gobierno-resolvio-que-no-habra-cambio-horario-verano-n656787 # http://www.republica.com.uy/este-ano-no-se-modificara-el-huso-horario-en-uruguay/523760/ # From Paul Eggert (2015-06-30): # Apparently restaurateurs complained that DST caused people to go to the beach # instead of out to dinner. # From Pablo Camargo (2015-07-13): # http://archivo.presidencia.gub.uy/sci/decretos/2015/06/cons_min_201.pdf # From Tim Parenti (2018-02-15): # Decreto 178/015 of 2015-06-29; repeals Decreto 311/006. # This Zone can be simplified once we assume zic %z. Zone America/Montevideo -3:44:51 - LMT 1908 Jun 10 -3:44:51 - MMT 1920 May 1 # Montevideo MT -4:00 - %z 1923 Oct 1 -3:30 Uruguay %z 1942 Dec 14 -3:00 Uruguay %z 1960 -3:00 Uruguay %z 1968 -3:00 Uruguay %z 1970 -3:00 Uruguay %z 1974 -3:00 Uruguay %z 1974 Mar 10 -3:00 Uruguay %z 1974 Dec 22 -3:00 Uruguay %z # Venezuela # # From Paul Eggert (2015-07-28): # For the 1965 transition see Gaceta Oficial No. 27.619 (1964-12-15), p 205.533 # http://www.pgr.gob.ve/dmdocuments/1964/27619.pdf # # From John Stainforth (2007-11-28): # ... the change for Venezuela originally expected for 2007-12-31 has # been brought forward to 2007-12-09. The official announcement was # published today in the "Gaceta Oficial de la República Bolivariana # de Venezuela, número 38.819" (official document for all laws or # resolution publication) # http://www.globovision.com/news.php?nid=72208 # From Alexander Krivenyshev (2016-04-15): # https://actualidad.rt.com/actualidad/204758-venezuela-modificar-huso-horario-sequia-elnino # # From Paul Eggert (2016-04-15): # Clocks advance 30 minutes on 2016-05-01 at 02:30.... # "'Venezuela's new time-zone: hours without light, hours without water, # hours of presidential broadcasts, hours of lines,' quipped comedian # Jean Mary Curró ...". See: Cawthorne A, Kai D. Venezuela scraps # half-hour time difference set by Chavez. Reuters 2016-04-15 14:50 -0400 # https://www.reuters.com/article/us-venezuela-timezone-idUSKCN0XC2BE # # From Matt Johnson (2016-04-20): # ... published in the official Gazette [2016-04-18], here: # http://historico.tsj.gob.ve/gaceta_ext/abril/1842016/E-1842016-4551.pdf # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone America/Caracas -4:27:44 - LMT 1890 -4:27:40 - CMT 1912 Feb 12 # Caracas Mean Time? -4:30 - %z 1965 Jan 1 0:00 -4:00 - %z 2007 Dec 9 3:00 -4:30 - %z 2016 May 1 2:30 -4:00 - %z pr0m1th3as-datatypes-9c9a8d3/inst/tzdata/version000066400000000000000000000000061522766574100217250ustar00rootroot000000000000002026c pr0m1th3as-datatypes-9c9a8d3/inst/tzdata/windowsZones.xml000066400000000000000000001535671522766574100235750ustar00rootroot00000000000000 pr0m1th3as-datatypes-9c9a8d3/inst/tzdata/zone.tab000066400000000000000000000445751522766574100220030ustar00rootroot00000000000000# tzdb timezone descriptions (deprecated version) # # This file is in the public domain, so clarified as of # 2009-05-17 by Arthur David Olson. # # From Paul Eggert (2021-09-20): # This file is intended as a backward-compatibility aid for older programs. # New programs should use zone1970.tab. This file is like zone1970.tab (see # zone1970.tab's comments), but with the following additional restrictions: # # 1. This file contains only ASCII characters. # 2. The first data column contains exactly one country code. # # Because of (2), each row stands for an area that is the intersection # of a region identified by a country code and of a timezone where civil # clocks have agreed since 1970; this is a narrower definition than # that of zone1970.tab. # # Unlike zone1970.tab, a row's third column can be a Link from # 'backward' instead of a Zone. # # This table is intended as an aid for users, to help them select timezones # appropriate for their practical needs. It is not intended to take or # endorse any position on legal or territorial claims. # #country- #code coordinates TZ comments AD +4230+00131 Europe/Andorra AE +2518+05518 Asia/Dubai AF +3431+06912 Asia/Kabul AG +1703-06148 America/Antigua AI +1812-06304 America/Anguilla AL +4120+01950 Europe/Tirane AM +4011+04430 Asia/Yerevan AO -0848+01314 Africa/Luanda AQ -7750+16636 Antarctica/McMurdo New Zealand time - McMurdo, South Pole AQ -6617+11031 Antarctica/Casey Casey AQ -6835+07758 Antarctica/Davis Davis AQ -6640+14001 Antarctica/DumontDUrville Dumont-d'Urville AQ -6736+06253 Antarctica/Mawson Mawson AQ -6448-06406 Antarctica/Palmer Palmer AQ -6734-06808 Antarctica/Rothera Rothera AQ -690022+0393524 Antarctica/Syowa Syowa AQ -720041+0023206 Antarctica/Troll Troll AQ -7824+10654 Antarctica/Vostok Vostok AR -3436-05827 America/Argentina/Buenos_Aires Buenos Aires (BA, CF) AR -3124-06411 America/Argentina/Cordoba Argentina (most areas: CB, CC, CN, ER, FM, MN, SE, SF) AR -2447-06525 America/Argentina/Salta Salta (SA, LP, NQ, RN) AR -2411-06518 America/Argentina/Jujuy Jujuy (JY) AR -2649-06513 America/Argentina/Tucuman Tucuman (TM) AR -2828-06547 America/Argentina/Catamarca Catamarca (CT), Chubut (CH) AR -2926-06651 America/Argentina/La_Rioja La Rioja (LR) AR -3132-06831 America/Argentina/San_Juan San Juan (SJ) AR -3253-06849 America/Argentina/Mendoza Mendoza (MZ) AR -3319-06621 America/Argentina/San_Luis San Luis (SL) AR -5138-06913 America/Argentina/Rio_Gallegos Santa Cruz (SC) AR -5448-06818 America/Argentina/Ushuaia Tierra del Fuego (TF) AS -1416-17042 Pacific/Pago_Pago AT +4813+01620 Europe/Vienna AU -3133+15905 Australia/Lord_Howe Lord Howe Island AU -5430+15857 Antarctica/Macquarie Macquarie Island AU -4253+14719 Australia/Hobart Tasmania AU -3749+14458 Australia/Melbourne Victoria AU -3352+15113 Australia/Sydney New South Wales (most areas) AU -3157+14127 Australia/Broken_Hill New South Wales (Yancowinna) AU -2728+15302 Australia/Brisbane Queensland (most areas) AU -2016+14900 Australia/Lindeman Queensland (Whitsunday Islands) AU -3455+13835 Australia/Adelaide South Australia AU -1228+13050 Australia/Darwin Northern Territory AU -3157+11551 Australia/Perth Western Australia (most areas) AU -3143+12852 Australia/Eucla Western Australia (Eucla) AW +1230-06958 America/Aruba AX +6006+01957 Europe/Mariehamn AZ +4023+04951 Asia/Baku BA +4352+01825 Europe/Sarajevo BB +1306-05937 America/Barbados BD +2343+09025 Asia/Dhaka BE +5050+00420 Europe/Brussels BF +1222-00131 Africa/Ouagadougou BG +4241+02319 Europe/Sofia BH +2623+05035 Asia/Bahrain BI -0323+02922 Africa/Bujumbura BJ +0629+00237 Africa/Porto-Novo BL +1753-06251 America/St_Barthelemy BM +3217-06446 Atlantic/Bermuda BN +0456+11455 Asia/Brunei BO -1630-06809 America/La_Paz BQ +120903-0681636 America/Kralendijk BR -0351-03225 America/Noronha Atlantic islands BR -0127-04829 America/Belem Para (east), Amapa BR -0343-03830 America/Fortaleza Brazil (northeast: MA, PI, CE, RN, PB) BR -0803-03454 America/Recife Pernambuco BR -0712-04812 America/Araguaina Tocantins BR -0940-03543 America/Maceio Alagoas, Sergipe BR -1259-03831 America/Bahia Bahia BR -2332-04637 America/Sao_Paulo Brazil (southeast: GO, DF, MG, ES, RJ, SP, PR, SC, RS) BR -2027-05437 America/Campo_Grande Mato Grosso do Sul BR -1535-05605 America/Cuiaba Mato Grosso BR -0226-05452 America/Santarem Para (west) BR -0846-06354 America/Porto_Velho Rondonia BR +0249-06040 America/Boa_Vista Roraima BR -0308-06001 America/Manaus Amazonas (east) BR -0640-06952 America/Eirunepe Amazonas (west) BR -0958-06748 America/Rio_Branco Acre BS +2505-07721 America/Nassau BT +2728+08939 Asia/Thimphu BW -2439+02555 Africa/Gaborone BY +5354+02734 Europe/Minsk BZ +1730-08812 America/Belize CA +4734-05243 America/St_Johns Newfoundland, Labrador (SE) CA +4439-06336 America/Halifax Atlantic - NS (most areas), PE CA +4612-05957 America/Glace_Bay Atlantic - NS (Cape Breton) CA +4606-06447 America/Moncton Atlantic - New Brunswick CA +5320-06025 America/Goose_Bay Atlantic - Labrador (most areas) CA +5125-05707 America/Blanc-Sablon AST - QC (Lower North Shore) CA +4339-07923 America/Toronto Eastern - ON & QC (most areas) CA +6344-06828 America/Iqaluit Eastern - NU (most areas) CA +484531-0913718 America/Atikokan EST - ON (Atikokan), NU (Coral H) CA +4953-09709 America/Winnipeg Central - ON (west), Manitoba CA +744144-0944945 America/Resolute Central - NU (Resolute) CA +624900-0920459 America/Rankin_Inlet Central - NU (central) CA +5024-10439 America/Regina CST - SK (most areas) CA +5017-10750 America/Swift_Current CST - SK (midwest) CA +5333-11328 America/Edmonton CST - AB, BC(E), NT(E), SK(W) CA +690650-1050310 America/Cambridge_Bay Mountain - NU (west) CA +682059-1334300 America/Inuvik Mountain - NT (west) CA +4916-12307 America/Vancouver MST - BC (most areas) CA +4906-11631 America/Creston MST - BC (Creston) CA +5546-12014 America/Dawson_Creek MST - BC (Dawson Cr, Ft St John) CA +5848-12242 America/Fort_Nelson MST - BC (Ft Nelson) CA +6043-13503 America/Whitehorse MST - Yukon (east) CA +6404-13925 America/Dawson MST - Yukon (west) CC -1210+09655 Indian/Cocos CD -0418+01518 Africa/Kinshasa Dem. Rep. of Congo (west) CD -1140+02728 Africa/Lubumbashi Dem. Rep. of Congo (east) CF +0422+01835 Africa/Bangui CG -0416+01517 Africa/Brazzaville CH +4723+00832 Europe/Zurich CI +0519-00402 Africa/Abidjan CK -2114-15946 Pacific/Rarotonga CL -3327-07040 America/Santiago most of Chile CL -4534-07204 America/Coyhaique Aysen Region CL -5309-07055 America/Punta_Arenas Magallanes Region CL -2709-10926 Pacific/Easter Easter Island CM +0403+00942 Africa/Douala CN +3114+12128 Asia/Shanghai Beijing Time CN +4348+08735 Asia/Urumqi Xinjiang Time CO +0436-07405 America/Bogota CR +0956-08405 America/Costa_Rica CU +2308-08222 America/Havana CV +1455-02331 Atlantic/Cape_Verde CW +1211-06900 America/Curacao CX -1025+10543 Indian/Christmas CY +3510+03322 Asia/Nicosia most of Cyprus CY +3507+03357 Asia/Famagusta Northern Cyprus CZ +5005+01426 Europe/Prague DE +5230+01322 Europe/Berlin most of Germany DE +4742+00841 Europe/Busingen Busingen DJ +1136+04309 Africa/Djibouti DK +5540+01235 Europe/Copenhagen DM +1518-06124 America/Dominica DO +1828-06954 America/Santo_Domingo DZ +3647+00303 Africa/Algiers EC -0210-07950 America/Guayaquil Ecuador (mainland) EC -0054-08936 Pacific/Galapagos Galapagos Islands EE +5925+02445 Europe/Tallinn EG +3003+03115 Africa/Cairo EH +2709-01312 Africa/El_Aaiun ER +1520+03853 Africa/Asmara ES +4024-00341 Europe/Madrid Spain (mainland) ES +3553-00519 Africa/Ceuta Ceuta, Melilla ES +2806-01524 Atlantic/Canary Canary Islands ET +0902+03842 Africa/Addis_Ababa FI +6010+02458 Europe/Helsinki FJ -1808+17825 Pacific/Fiji FK -5142-05751 Atlantic/Stanley FM +0725+15147 Pacific/Chuuk Chuuk/Truk, Yap FM +0658+15813 Pacific/Pohnpei Pohnpei/Ponape FM +0519+16259 Pacific/Kosrae Kosrae FO +6201-00646 Atlantic/Faroe FR +4852+00220 Europe/Paris GA +0023+00927 Africa/Libreville GB +513030-0000731 Europe/London GD +1203-06145 America/Grenada GE +4143+04449 Asia/Tbilisi GF +0456-05220 America/Cayenne GG +492717-0023210 Europe/Guernsey GH +0533-00013 Africa/Accra GI +3608-00521 Europe/Gibraltar GL +6411-05144 America/Nuuk most of Greenland GL +7646-01840 America/Danmarkshavn National Park (east coast) GL +7029-02158 America/Scoresbysund Scoresbysund/Ittoqqortoormiit GL +7634-06847 America/Thule Thule/Pituffik GM +1328-01639 Africa/Banjul GN +0931-01343 Africa/Conakry GP +1614-06132 America/Guadeloupe GQ +0345+00847 Africa/Malabo GR +3758+02343 Europe/Athens GS -5416-03632 Atlantic/South_Georgia GT +1438-09031 America/Guatemala GU +1328+14445 Pacific/Guam GW +1151-01535 Africa/Bissau GY +0648-05810 America/Guyana HK +2217+11409 Asia/Hong_Kong HN +1406-08713 America/Tegucigalpa HR +4548+01558 Europe/Zagreb HT +1832-07220 America/Port-au-Prince HU +4730+01905 Europe/Budapest ID -0610+10648 Asia/Jakarta Java, Sumatra ID -0002+10920 Asia/Pontianak Borneo (west, central) ID -0507+11924 Asia/Makassar Borneo (east, south), Sulawesi/Celebes, Bali, Nusa Tengarra, Timor (west) ID -0232+14042 Asia/Jayapura New Guinea (West Papua / Irian Jaya), Malukus/Moluccas IE +5320-00615 Europe/Dublin IL +314650+0351326 Asia/Jerusalem IM +5409-00428 Europe/Isle_of_Man IN +2232+08822 Asia/Kolkata IO -0720+07225 Indian/Chagos IQ +3321+04425 Asia/Baghdad IR +3540+05126 Asia/Tehran IS +6409-02151 Atlantic/Reykjavik IT +4154+01229 Europe/Rome JE +491101-0020624 Europe/Jersey JM +175805-0764736 America/Jamaica JO +3157+03556 Asia/Amman JP +353916+1394441 Asia/Tokyo KE -0117+03649 Africa/Nairobi KG +4254+07436 Asia/Bishkek KH +1133+10455 Asia/Phnom_Penh KI +0125+17300 Pacific/Tarawa Gilbert Islands KI -0247-17143 Pacific/Kanton Phoenix Islands KI +0152-15720 Pacific/Kiritimati Line Islands KM -1141+04316 Indian/Comoro KN +1718-06243 America/St_Kitts KP +3901+12545 Asia/Pyongyang KR +3733+12658 Asia/Seoul KW +2920+04759 Asia/Kuwait KY +1918-08123 America/Cayman KZ +4315+07657 Asia/Almaty most of Kazakhstan KZ +4448+06528 Asia/Qyzylorda Qyzylorda/Kyzylorda/Kzyl-Orda KZ +5312+06337 Asia/Qostanay Qostanay/Kostanay/Kustanay KZ +5017+05710 Asia/Aqtobe Aqtobe/Aktobe KZ +4431+05016 Asia/Aqtau Mangghystau/Mankistau KZ +4707+05156 Asia/Atyrau Atyrau/Atirau/Gur'yev KZ +5113+05121 Asia/Oral West Kazakhstan LA +1758+10236 Asia/Vientiane LB +3353+03530 Asia/Beirut LC +1401-06100 America/St_Lucia LI +4709+00931 Europe/Vaduz LK +0656+07951 Asia/Colombo LR +0618-01047 Africa/Monrovia LS -2928+02730 Africa/Maseru LT +5441+02519 Europe/Vilnius LU +4936+00609 Europe/Luxembourg LV +5657+02406 Europe/Riga LY +3254+01311 Africa/Tripoli MA +3339-00735 Africa/Casablanca MC +4342+00723 Europe/Monaco MD +4700+02850 Europe/Chisinau ME +4226+01916 Europe/Podgorica MF +1804-06305 America/Marigot MG -1855+04731 Indian/Antananarivo MH +0709+17112 Pacific/Majuro most of Marshall Islands MH +0905+16720 Pacific/Kwajalein Kwajalein MK +4159+02126 Europe/Skopje ML +1239-00800 Africa/Bamako MM +1647+09610 Asia/Yangon MN +4755+10653 Asia/Ulaanbaatar most of Mongolia MN +4801+09139 Asia/Hovd Bayan-Olgii, Hovd, Uvs MO +221150+1133230 Asia/Macau MP +1512+14545 Pacific/Saipan MQ +1436-06105 America/Martinique MR +1806-01557 Africa/Nouakchott MS +1643-06213 America/Montserrat MT +3554+01431 Europe/Malta MU -2010+05730 Indian/Mauritius MV +0410+07330 Indian/Maldives MW -1547+03500 Africa/Blantyre MX +1924-09909 America/Mexico_City Central Mexico MX +2105-08646 America/Cancun Quintana Roo MX +2058-08937 America/Merida Campeche, Yucatan MX +2540-10019 America/Monterrey Durango; Coahuila, Nuevo Leon, Tamaulipas (most areas) MX +2550-09730 America/Matamoros Coahuila, Nuevo Leon, Tamaulipas (US border) MX +2838-10605 America/Chihuahua Chihuahua (most areas) MX +3144-10629 America/Ciudad_Juarez Chihuahua (US border - west) MX +2934-10425 America/Ojinaga Chihuahua (US border - east) MX +2313-10625 America/Mazatlan Baja California Sur, Nayarit (most areas), Sinaloa MX +2048-10515 America/Bahia_Banderas Bahia de Banderas MX +2904-11058 America/Hermosillo Sonora MX +3232-11701 America/Tijuana Baja California MY +0310+10142 Asia/Kuala_Lumpur Malaysia (peninsula) MY +0133+11020 Asia/Kuching Sabah, Sarawak MZ -2558+03235 Africa/Maputo NA -2234+01706 Africa/Windhoek NC -2216+16627 Pacific/Noumea NE +1331+00207 Africa/Niamey NF -2903+16758 Pacific/Norfolk NG +0627+00324 Africa/Lagos NI +1209-08617 America/Managua NL +5222+00454 Europe/Amsterdam NO +5955+01045 Europe/Oslo NP +2743+08519 Asia/Kathmandu NR -0031+16655 Pacific/Nauru NU -1901-16955 Pacific/Niue NZ -3652+17446 Pacific/Auckland most of New Zealand NZ -4357-17633 Pacific/Chatham Chatham Islands OM +2336+05835 Asia/Muscat PA +0858-07932 America/Panama PE -1203-07703 America/Lima PF -1732-14934 Pacific/Tahiti Society Islands PF -0900-13930 Pacific/Marquesas Marquesas Islands PF -2308-13457 Pacific/Gambier Gambier Islands PG -0930+14710 Pacific/Port_Moresby most of Papua New Guinea PG -0613+15534 Pacific/Bougainville Bougainville PH +143512+1205804 Asia/Manila PK +2452+06703 Asia/Karachi PL +5215+02100 Europe/Warsaw PM +4703-05620 America/Miquelon PN -2504-13005 Pacific/Pitcairn PR +182806-0660622 America/Puerto_Rico PS +3130+03428 Asia/Gaza Gaza Strip PS +313200+0350542 Asia/Hebron West Bank PT +3843-00908 Europe/Lisbon Portugal (mainland) PT +3238-01654 Atlantic/Madeira Madeira Islands PT +3744-02540 Atlantic/Azores Azores PW +0720+13429 Pacific/Palau PY -2516-05740 America/Asuncion QA +2517+05132 Asia/Qatar RE -2052+05528 Indian/Reunion RO +4426+02606 Europe/Bucharest RS +4450+02030 Europe/Belgrade RU +5443+02030 Europe/Kaliningrad MSK-01 - Kaliningrad RU +554521+0373704 Europe/Moscow MSK+00 - Moscow area # The obsolescent zone.tab format cannot represent Europe/Simferopol well. # Put it in RU section and list as UA. See "territorial claims" above. # Programs should use zone1970.tab instead; see above. UA +4457+03406 Europe/Simferopol Crimea RU +5836+04939 Europe/Kirov MSK+00 - Kirov RU +4844+04425 Europe/Volgograd MSK+00 - Volgograd RU +4621+04803 Europe/Astrakhan MSK+01 - Astrakhan RU +5134+04602 Europe/Saratov MSK+01 - Saratov RU +5420+04824 Europe/Ulyanovsk MSK+01 - Ulyanovsk RU +5312+05009 Europe/Samara MSK+01 - Samara, Udmurtia RU +5651+06036 Asia/Yekaterinburg MSK+02 - Urals RU +5500+07324 Asia/Omsk MSK+03 - Omsk RU +5502+08255 Asia/Novosibirsk MSK+04 - Novosibirsk RU +5322+08345 Asia/Barnaul MSK+04 - Altai RU +5630+08458 Asia/Tomsk MSK+04 - Tomsk RU +5345+08707 Asia/Novokuznetsk MSK+04 - Kemerovo RU +5601+09250 Asia/Krasnoyarsk MSK+04 - Krasnoyarsk area RU +5216+10420 Asia/Irkutsk MSK+05 - Irkutsk, Buryatia RU +5203+11328 Asia/Chita MSK+06 - Zabaykalsky RU +6200+12940 Asia/Yakutsk MSK+06 - Lena River RU +623923+1353314 Asia/Khandyga MSK+06 - Tomponsky, Ust-Maysky RU +4310+13156 Asia/Vladivostok MSK+07 - Amur River RU +643337+1431336 Asia/Ust-Nera MSK+07 - Oymyakonsky RU +5934+15048 Asia/Magadan MSK+08 - Magadan RU +4658+14242 Asia/Sakhalin MSK+08 - Sakhalin Island RU +6728+15343 Asia/Srednekolymsk MSK+08 - Sakha (E), N Kuril Is RU +5301+15839 Asia/Kamchatka MSK+09 - Kamchatka RU +6445+17729 Asia/Anadyr MSK+09 - Bering Sea RW -0157+03004 Africa/Kigali SA +2438+04643 Asia/Riyadh SB -0932+16012 Pacific/Guadalcanal SC -0440+05528 Indian/Mahe SD +1536+03232 Africa/Khartoum SE +5920+01803 Europe/Stockholm SG +0117+10351 Asia/Singapore SH -1555-00542 Atlantic/St_Helena SI +4603+01431 Europe/Ljubljana SJ +7800+01600 Arctic/Longyearbyen SK +4809+01707 Europe/Bratislava SL +0830-01315 Africa/Freetown SM +4355+01228 Europe/San_Marino SN +1440-01726 Africa/Dakar SO +0204+04522 Africa/Mogadishu SR +0550-05510 America/Paramaribo SS +0451+03137 Africa/Juba ST +0020+00644 Africa/Sao_Tome SV +1342-08912 America/El_Salvador SX +180305-0630250 America/Lower_Princes SY +3330+03618 Asia/Damascus SZ -2618+03106 Africa/Mbabane TC +2128-07108 America/Grand_Turk TD +1207+01503 Africa/Ndjamena TF -492110+0701303 Indian/Kerguelen TG +0608+00113 Africa/Lome TH +1345+10031 Asia/Bangkok TJ +3835+06848 Asia/Dushanbe TK -0922-17114 Pacific/Fakaofo TL -0833+12535 Asia/Dili TM +3757+05823 Asia/Ashgabat TN +3648+01011 Africa/Tunis TO -210800-1751200 Pacific/Tongatapu TR +4101+02858 Europe/Istanbul TT +1039-06131 America/Port_of_Spain TV -0831+17913 Pacific/Funafuti TW +2503+12130 Asia/Taipei TZ -0648+03917 Africa/Dar_es_Salaam UA +5026+03031 Europe/Kyiv most of Ukraine UG +0019+03225 Africa/Kampala UM +2813-17722 Pacific/Midway Midway Islands UM +1917+16637 Pacific/Wake Wake Island US +404251-0740023 America/New_York Eastern (most areas) US +421953-0830245 America/Detroit Eastern - MI (most areas) US +381515-0854534 America/Kentucky/Louisville Eastern - KY (Louisville area) US +364947-0845057 America/Kentucky/Monticello Eastern - KY (Wayne) US +394606-0860929 America/Indiana/Indianapolis Eastern - IN (most areas) US +384038-0873143 America/Indiana/Vincennes Eastern - IN (Da, Du, K, Mn) US +410305-0863611 America/Indiana/Winamac Eastern - IN (Pulaski) US +382232-0862041 America/Indiana/Marengo Eastern - IN (Crawford) US +382931-0871643 America/Indiana/Petersburg Eastern - IN (Pike) US +384452-0850402 America/Indiana/Vevay Eastern - IN (Switzerland) US +415100-0873900 America/Chicago Central (most areas) US +375711-0864541 America/Indiana/Tell_City Central - IN (Perry) US +411745-0863730 America/Indiana/Knox Central - IN (Starke) US +450628-0873651 America/Menominee Central - MI (Wisconsin border) US +470659-1011757 America/North_Dakota/Center Central - ND (Oliver) US +465042-1012439 America/North_Dakota/New_Salem Central - ND (Morton rural) US +471551-1014640 America/North_Dakota/Beulah Central - ND (Mercer) US +394421-1045903 America/Denver Mountain (most areas) US +433649-1161209 America/Boise Mountain - ID (south), OR (east) US +332654-1120424 America/Phoenix MST - AZ (except Navajo) US +340308-1181434 America/Los_Angeles Pacific US +611305-1495401 America/Anchorage Alaska (most areas) US +581807-1342511 America/Juneau Alaska - Juneau area US +571035-1351807 America/Sitka Alaska - Sitka area US +550737-1313435 America/Metlakatla Alaska - Annette Island US +593249-1394338 America/Yakutat Alaska - Yakutat US +643004-1652423 America/Nome Alaska (west) US +515248-1763929 America/Adak Alaska - western Aleutians US +211825-1575130 Pacific/Honolulu Hawaii UY -345433-0561245 America/Montevideo UZ +3940+06648 Asia/Samarkand Uzbekistan (west) UZ +4120+06918 Asia/Tashkent Uzbekistan (east) VA +415408+0122711 Europe/Vatican VC +1309-06114 America/St_Vincent VE +1030-06656 America/Caracas VG +1827-06437 America/Tortola VI +1821-06456 America/St_Thomas VN +1045+10640 Asia/Ho_Chi_Minh VU -1740+16825 Pacific/Efate WF -1318-17610 Pacific/Wallis WS -1350-17144 Pacific/Apia YE +1245+04512 Asia/Aden YT -1247+04514 Indian/Mayotte ZA -2615+02800 Africa/Johannesburg ZM -1525+02817 Africa/Lusaka ZW -1750+03103 Africa/Harare pr0m1th3as-datatypes-9c9a8d3/inst/tzdata/zone1970.tab000066400000000000000000000422741522766574100223160ustar00rootroot00000000000000# tzdb timezone descriptions # # This file is in the public domain. # # From Paul Eggert (2025-05-15): # This file contains a table where each row stands for a timezone where # civil timestamps have agreed since 1970. Columns are separated by # a single tab. Lines beginning with ‘#’ are comments. All text uses # UTF-8 encoding. The columns of the table are as follows: # # 1. The countries that overlap the timezone, as a comma-separated list # of ISO 3166 2-character country codes. # 2. Latitude and longitude of the timezone’s principal location # in ISO 6709 sign-degrees-minutes-seconds format, # either ±DDMM±DDDMM or ±DDMMSS±DDDMMSS, # first latitude (+ is north), then longitude (+ is east). # 3. Timezone name used in value of TZ environment variable. # Please see the theory.html file for how these names are chosen. # If multiple timezones overlap a country, each has a row in the # table, with each column 1 containing the country code. # 4. Comments; present if and only if countries have multiple timezones, # and useful only for those countries. For example, the comments # for the row with countries CH,DE,LI and name Europe/Zurich # are useful only for DE, since CH and LI have no other timezones. # # If a timezone covers multiple countries, the most-populous city is used, # and that country is listed first in column 1; any other countries # are listed alphabetically by country code. The table is sorted # first by country code, then (if possible) by an order within the # country that (1) makes some geographical sense, and (2) puts the # most populous timezones first, where that does not contradict (1). # # This table is intended as an aid for users, to help them select timezones # appropriate for their practical needs. It is not intended to take or # endorse any position on legal or territorial claims. # #country- #codes coordinates TZ comments AD +4230+00131 Europe/Andorra AE,OM,RE,SC,TF +2518+05518 Asia/Dubai Crozet AF +3431+06912 Asia/Kabul AL +4120+01950 Europe/Tirane AM +4011+04430 Asia/Yerevan AQ -6617+11031 Antarctica/Casey Casey AQ -6835+07758 Antarctica/Davis Davis AQ -6736+06253 Antarctica/Mawson Mawson AQ -6448-06406 Antarctica/Palmer Palmer AQ -6734-06808 Antarctica/Rothera Rothera AQ -720041+0023206 Antarctica/Troll Troll AQ -7824+10654 Antarctica/Vostok Vostok AR -3436-05827 America/Argentina/Buenos_Aires Buenos Aires (BA, CF) AR -3124-06411 America/Argentina/Cordoba most areas: CB, CC, CN, ER, FM, MN, SE, SF AR -2447-06525 America/Argentina/Salta Salta (SA, LP, NQ, RN) AR -2411-06518 America/Argentina/Jujuy Jujuy (JY) AR -2649-06513 America/Argentina/Tucuman Tucumán (TM) AR -2828-06547 America/Argentina/Catamarca Catamarca (CT), Chubut (CH) AR -2926-06651 America/Argentina/La_Rioja La Rioja (LR) AR -3132-06831 America/Argentina/San_Juan San Juan (SJ) AR -3253-06849 America/Argentina/Mendoza Mendoza (MZ) AR -3319-06621 America/Argentina/San_Luis San Luis (SL) AR -5138-06913 America/Argentina/Rio_Gallegos Santa Cruz (SC) AR -5448-06818 America/Argentina/Ushuaia Tierra del Fuego (TF) AS,UM -1416-17042 Pacific/Pago_Pago Midway AT +4813+01620 Europe/Vienna AU -3133+15905 Australia/Lord_Howe Lord Howe Island AU -5430+15857 Antarctica/Macquarie Macquarie Island AU -4253+14719 Australia/Hobart Tasmania AU -3749+14458 Australia/Melbourne Victoria AU -3352+15113 Australia/Sydney New South Wales (most areas) AU -3157+14127 Australia/Broken_Hill New South Wales (Yancowinna) AU -2728+15302 Australia/Brisbane Queensland (most areas) AU -2016+14900 Australia/Lindeman Queensland (Whitsunday Islands) AU -3455+13835 Australia/Adelaide South Australia AU -1228+13050 Australia/Darwin Northern Territory AU -3157+11551 Australia/Perth Western Australia (most areas) AU -3143+12852 Australia/Eucla Western Australia (Eucla) AZ +4023+04951 Asia/Baku BB +1306-05937 America/Barbados BD +2343+09025 Asia/Dhaka BE,LU,NL +5050+00420 Europe/Brussels BG +4241+02319 Europe/Sofia BM +3217-06446 Atlantic/Bermuda BO -1630-06809 America/La_Paz BR -0351-03225 America/Noronha Atlantic islands BR -0127-04829 America/Belem Pará (east), Amapá BR -0343-03830 America/Fortaleza Brazil (northeast: MA, PI, CE, RN, PB) BR -0803-03454 America/Recife Pernambuco BR -0712-04812 America/Araguaina Tocantins BR -0940-03543 America/Maceio Alagoas, Sergipe BR -1259-03831 America/Bahia Bahia BR -2332-04637 America/Sao_Paulo Brazil (southeast: GO, DF, MG, ES, RJ, SP, PR, SC, RS) BR -2027-05437 America/Campo_Grande Mato Grosso do Sul BR -1535-05605 America/Cuiaba Mato Grosso BR -0226-05452 America/Santarem Pará (west) BR -0846-06354 America/Porto_Velho Rondônia BR +0249-06040 America/Boa_Vista Roraima BR -0308-06001 America/Manaus Amazonas (east) BR -0640-06952 America/Eirunepe Amazonas (west) BR -0958-06748 America/Rio_Branco Acre BT +2728+08939 Asia/Thimphu BY +5354+02734 Europe/Minsk BZ +1730-08812 America/Belize CA +4734-05243 America/St_Johns Newfoundland, Labrador (SE) CA +4439-06336 America/Halifax Atlantic - NS (most areas), PE CA +4612-05957 America/Glace_Bay Atlantic - NS (Cape Breton) CA +4606-06447 America/Moncton Atlantic - New Brunswick CA +5320-06025 America/Goose_Bay Atlantic - Labrador (most areas) CA,BS +4339-07923 America/Toronto Eastern - ON & QC (most areas) CA +6344-06828 America/Iqaluit Eastern - NU (most areas) CA +4953-09709 America/Winnipeg Central - ON (west), Manitoba CA +744144-0944945 America/Resolute Central - NU (Resolute) CA +624900-0920459 America/Rankin_Inlet Central - NU (central) CA +5024-10439 America/Regina CST - SK (most areas) CA +5017-10750 America/Swift_Current CST - SK (midwest) CA +5333-11328 America/Edmonton CST - AB, BC(E), NT(E), SK(W) CA +690650-1050310 America/Cambridge_Bay Mountain - NU (west) CA +682059-1334300 America/Inuvik Mountain - NT (west) CA +4916-12307 America/Vancouver MST - BC (most areas) CA +5546-12014 America/Dawson_Creek MST - BC (Dawson Cr, Ft St John) CA +5848-12242 America/Fort_Nelson MST - BC (Ft Nelson) CA +6043-13503 America/Whitehorse MST - Yukon (east) CA +6404-13925 America/Dawson MST - Yukon (west) CH,DE,LI +4723+00832 Europe/Zurich Büsingen CI,BF,GH,GM,GN,IS,ML,MR,SH,SL,SN,TG +0519-00402 Africa/Abidjan CK -2114-15946 Pacific/Rarotonga CL -3327-07040 America/Santiago most of Chile CL -4534-07204 America/Coyhaique Aysén Region CL -5309-07055 America/Punta_Arenas Magallanes Region CL -2709-10926 Pacific/Easter Easter Island CN +3114+12128 Asia/Shanghai Beijing Time CN +4348+08735 Asia/Urumqi Xinjiang Time CO +0436-07405 America/Bogota CR +0956-08405 America/Costa_Rica CU +2308-08222 America/Havana CV +1455-02331 Atlantic/Cape_Verde CY +3510+03322 Asia/Nicosia most of Cyprus CY +3507+03357 Asia/Famagusta Northern Cyprus CZ,SK +5005+01426 Europe/Prague DE,DK,NO,SE,SJ +5230+01322 Europe/Berlin most of Germany DO +1828-06954 America/Santo_Domingo DZ +3647+00303 Africa/Algiers EC -0210-07950 America/Guayaquil Ecuador (mainland) EC -0054-08936 Pacific/Galapagos Galápagos Islands EE +5925+02445 Europe/Tallinn EG +3003+03115 Africa/Cairo EH +2709-01312 Africa/El_Aaiun ES +4024-00341 Europe/Madrid Spain (mainland) ES +3553-00519 Africa/Ceuta Ceuta, Melilla ES +2806-01524 Atlantic/Canary Canary Islands FI,AX +6010+02458 Europe/Helsinki FJ -1808+17825 Pacific/Fiji FK -5142-05751 Atlantic/Stanley FM +0519+16259 Pacific/Kosrae Kosrae FO +6201-00646 Atlantic/Faroe FR,MC +4852+00220 Europe/Paris GB,GG,IM,JE +513030-0000731 Europe/London GE +4143+04449 Asia/Tbilisi GF +0456-05220 America/Cayenne GI +3608-00521 Europe/Gibraltar GL +6411-05144 America/Nuuk most of Greenland GL +7646-01840 America/Danmarkshavn National Park (east coast) GL +7029-02158 America/Scoresbysund Scoresbysund/Ittoqqortoormiit GL +7634-06847 America/Thule Thule/Pituffik GR +3758+02343 Europe/Athens GS -5416-03632 Atlantic/South_Georgia GT +1438-09031 America/Guatemala GU,MP +1328+14445 Pacific/Guam GW +1151-01535 Africa/Bissau GY +0648-05810 America/Guyana HK +2217+11409 Asia/Hong_Kong HN +1406-08713 America/Tegucigalpa HT +1832-07220 America/Port-au-Prince HU +4730+01905 Europe/Budapest ID -0610+10648 Asia/Jakarta Java, Sumatra ID -0002+10920 Asia/Pontianak Borneo (west, central) ID -0507+11924 Asia/Makassar Borneo (east, south), Sulawesi/Celebes, Bali, Nusa Tengarra, Timor (west) ID -0232+14042 Asia/Jayapura New Guinea (West Papua / Irian Jaya), Malukus/Moluccas IE +5320-00615 Europe/Dublin IL +314650+0351326 Asia/Jerusalem IN +2232+08822 Asia/Kolkata IO -0720+07225 Indian/Chagos IQ +3321+04425 Asia/Baghdad IR +3540+05126 Asia/Tehran IT,SM,VA +4154+01229 Europe/Rome JM +175805-0764736 America/Jamaica JO +3157+03556 Asia/Amman JP,AU +353916+1394441 Asia/Tokyo Eyre Bird Observatory KE,DJ,ER,ET,KM,MG,SO,TZ,UG,YT -0117+03649 Africa/Nairobi KG +4254+07436 Asia/Bishkek KI,MH,TV,UM,WF +0125+17300 Pacific/Tarawa Gilberts, Marshalls, Wake KI -0247-17143 Pacific/Kanton Phoenix Islands KI +0152-15720 Pacific/Kiritimati Line Islands KP +3901+12545 Asia/Pyongyang KR +3733+12658 Asia/Seoul KZ +4315+07657 Asia/Almaty most of Kazakhstan KZ +4448+06528 Asia/Qyzylorda Qyzylorda/Kyzylorda/Kzyl-Orda KZ +5312+06337 Asia/Qostanay Qostanay/Kostanay/Kustanay KZ +5017+05710 Asia/Aqtobe Aqtöbe/Aktobe KZ +4431+05016 Asia/Aqtau Mangghystaū/Mankistau KZ +4707+05156 Asia/Atyrau Atyraū/Atirau/Gur’yev KZ +5113+05121 Asia/Oral West Kazakhstan LB +3353+03530 Asia/Beirut LK +0656+07951 Asia/Colombo LR +0618-01047 Africa/Monrovia LT +5441+02519 Europe/Vilnius LV +5657+02406 Europe/Riga LY +3254+01311 Africa/Tripoli MA +3339-00735 Africa/Casablanca MD +4700+02850 Europe/Chisinau MH +0905+16720 Pacific/Kwajalein Kwajalein MM,CC +1647+09610 Asia/Yangon MN +4755+10653 Asia/Ulaanbaatar most of Mongolia MN +4801+09139 Asia/Hovd Bayan-Ölgii, Hovd, Uvs MO +221150+1133230 Asia/Macau MQ +1436-06105 America/Martinique MT +3554+01431 Europe/Malta MU -2010+05730 Indian/Mauritius MV,TF +0410+07330 Indian/Maldives Kerguelen, St Paul I, Amsterdam I MX +1924-09909 America/Mexico_City Central Mexico MX +2105-08646 America/Cancun Quintana Roo MX +2058-08937 America/Merida Campeche, Yucatán MX +2540-10019 America/Monterrey Durango; Coahuila, Nuevo León, Tamaulipas (most areas) MX +2550-09730 America/Matamoros Coahuila, Nuevo León, Tamaulipas (US border) MX +2838-10605 America/Chihuahua Chihuahua (most areas) MX +3144-10629 America/Ciudad_Juarez Chihuahua (US border - west) MX +2934-10425 America/Ojinaga Chihuahua (US border - east) MX +2313-10625 America/Mazatlan Baja California Sur, Nayarit (most areas), Sinaloa MX +2048-10515 America/Bahia_Banderas Bahía de Banderas MX +2904-11058 America/Hermosillo Sonora MX +3232-11701 America/Tijuana Baja California MY,BN +0133+11020 Asia/Kuching Sabah, Sarawak MZ,BI,BW,CD,MW,RW,ZM,ZW -2558+03235 Africa/Maputo Central Africa Time NA -2234+01706 Africa/Windhoek NC -2216+16627 Pacific/Noumea NF -2903+16758 Pacific/Norfolk NG,AO,BJ,CD,CF,CG,CM,GA,GQ,NE +0627+00324 Africa/Lagos West Africa Time NI +1209-08617 America/Managua NP +2743+08519 Asia/Kathmandu NR -0031+16655 Pacific/Nauru NU -1901-16955 Pacific/Niue NZ,AQ -3652+17446 Pacific/Auckland New Zealand time NZ -4357-17633 Pacific/Chatham Chatham Islands PA,CA,KY +0858-07932 America/Panama EST - ON (Atikokan), NU (Coral H) PE -1203-07703 America/Lima PF -1732-14934 Pacific/Tahiti Society Islands PF -0900-13930 Pacific/Marquesas Marquesas Islands PF -2308-13457 Pacific/Gambier Gambier Islands PG,AQ,FM -0930+14710 Pacific/Port_Moresby Papua New Guinea (most areas), Chuuk, Yap, Dumont d’Urville PG -0613+15534 Pacific/Bougainville Bougainville PH +143512+1205804 Asia/Manila PK +2452+06703 Asia/Karachi PL +5215+02100 Europe/Warsaw PM +4703-05620 America/Miquelon PN -2504-13005 Pacific/Pitcairn PR,AG,CA,AI,AW,BL,BQ,CW,DM,GD,GP,KN,LC,MF,MS,SX,TT,VC,VG,VI +182806-0660622 America/Puerto_Rico AST - QC (Lower North Shore) PS +3130+03428 Asia/Gaza Gaza Strip PS +313200+0350542 Asia/Hebron West Bank PT +3843-00908 Europe/Lisbon Portugal (mainland) PT +3238-01654 Atlantic/Madeira Madeira Islands PT +3744-02540 Atlantic/Azores Azores PW +0720+13429 Pacific/Palau PY -2516-05740 America/Asuncion QA,BH +2517+05132 Asia/Qatar RO +4426+02606 Europe/Bucharest RS,BA,HR,ME,MK,SI +4450+02030 Europe/Belgrade RU +5443+02030 Europe/Kaliningrad MSK-01 - Kaliningrad RU +554521+0373704 Europe/Moscow MSK+00 - Moscow area # Mention RU and UA alphabetically. See “territorial claims” above. RU,UA +4457+03406 Europe/Simferopol Crimea RU +5836+04939 Europe/Kirov MSK+00 - Kirov RU +4844+04425 Europe/Volgograd MSK+00 - Volgograd RU +4621+04803 Europe/Astrakhan MSK+01 - Astrakhan RU +5134+04602 Europe/Saratov MSK+01 - Saratov RU +5420+04824 Europe/Ulyanovsk MSK+01 - Ulyanovsk RU +5312+05009 Europe/Samara MSK+01 - Samara, Udmurtia RU +5651+06036 Asia/Yekaterinburg MSK+02 - Urals RU +5500+07324 Asia/Omsk MSK+03 - Omsk RU +5502+08255 Asia/Novosibirsk MSK+04 - Novosibirsk RU +5322+08345 Asia/Barnaul MSK+04 - Altai RU +5630+08458 Asia/Tomsk MSK+04 - Tomsk RU +5345+08707 Asia/Novokuznetsk MSK+04 - Kemerovo RU +5601+09250 Asia/Krasnoyarsk MSK+04 - Krasnoyarsk area RU +5216+10420 Asia/Irkutsk MSK+05 - Irkutsk, Buryatia RU +5203+11328 Asia/Chita MSK+06 - Zabaykalsky RU +6200+12940 Asia/Yakutsk MSK+06 - Lena River RU +623923+1353314 Asia/Khandyga MSK+06 - Tomponsky, Ust-Maysky RU +4310+13156 Asia/Vladivostok MSK+07 - Amur River RU +643337+1431336 Asia/Ust-Nera MSK+07 - Oymyakonsky RU +5934+15048 Asia/Magadan MSK+08 - Magadan RU +4658+14242 Asia/Sakhalin MSK+08 - Sakhalin Island RU +6728+15343 Asia/Srednekolymsk MSK+08 - Sakha (E), N Kuril Is RU +5301+15839 Asia/Kamchatka MSK+09 - Kamchatka RU +6445+17729 Asia/Anadyr MSK+09 - Bering Sea SA,AQ,KW,YE +2438+04643 Asia/Riyadh Syowa SB,FM -0932+16012 Pacific/Guadalcanal Pohnpei SD +1536+03232 Africa/Khartoum SG,AQ,MY +0117+10351 Asia/Singapore peninsular Malaysia, Concordia SR +0550-05510 America/Paramaribo SS +0451+03137 Africa/Juba ST +0020+00644 Africa/Sao_Tome SV +1342-08912 America/El_Salvador SY +3330+03618 Asia/Damascus TC +2128-07108 America/Grand_Turk TD +1207+01503 Africa/Ndjamena TH,CX,KH,LA,VN +1345+10031 Asia/Bangkok north Vietnam TJ +3835+06848 Asia/Dushanbe TK -0922-17114 Pacific/Fakaofo TL -0833+12535 Asia/Dili TM +3757+05823 Asia/Ashgabat TN +3648+01011 Africa/Tunis TO -210800-1751200 Pacific/Tongatapu TR +4101+02858 Europe/Istanbul TW +2503+12130 Asia/Taipei UA +5026+03031 Europe/Kyiv most of Ukraine US +404251-0740023 America/New_York Eastern (most areas) US +421953-0830245 America/Detroit Eastern - MI (most areas) US +381515-0854534 America/Kentucky/Louisville Eastern - KY (Louisville area) US +364947-0845057 America/Kentucky/Monticello Eastern - KY (Wayne) US +394606-0860929 America/Indiana/Indianapolis Eastern - IN (most areas) US +384038-0873143 America/Indiana/Vincennes Eastern - IN (Da, Du, K, Mn) US +410305-0863611 America/Indiana/Winamac Eastern - IN (Pulaski) US +382232-0862041 America/Indiana/Marengo Eastern - IN (Crawford) US +382931-0871643 America/Indiana/Petersburg Eastern - IN (Pike) US +384452-0850402 America/Indiana/Vevay Eastern - IN (Switzerland) US +415100-0873900 America/Chicago Central (most areas) US +375711-0864541 America/Indiana/Tell_City Central - IN (Perry) US +411745-0863730 America/Indiana/Knox Central - IN (Starke) US +450628-0873651 America/Menominee Central - MI (Wisconsin border) US +470659-1011757 America/North_Dakota/Center Central - ND (Oliver) US +465042-1012439 America/North_Dakota/New_Salem Central - ND (Morton rural) US +471551-1014640 America/North_Dakota/Beulah Central - ND (Mercer) US +394421-1045903 America/Denver Mountain (most areas) US +433649-1161209 America/Boise Mountain - ID (south), OR (east) US,CA +332654-1120424 America/Phoenix MST - AZ (most areas), Creston BC US +340308-1181434 America/Los_Angeles Pacific US +611305-1495401 America/Anchorage Alaska (most areas) US +581807-1342511 America/Juneau Alaska - Juneau area US +571035-1351807 America/Sitka Alaska - Sitka area US +550737-1313435 America/Metlakatla Alaska - Annette Island US +593249-1394338 America/Yakutat Alaska - Yakutat US +643004-1652423 America/Nome Alaska (west) US +515248-1763929 America/Adak Alaska - western Aleutians US +211825-1575130 Pacific/Honolulu Hawaii UY -345433-0561245 America/Montevideo UZ +3940+06648 Asia/Samarkand Uzbekistan (west) UZ +4120+06918 Asia/Tashkent Uzbekistan (east) VE +1030-06656 America/Caracas VN +1045+10640 Asia/Ho_Chi_Minh south Vietnam VU -1740+16825 Pacific/Efate WS -1350-17144 Pacific/Apia ZA,LS,SZ -2615+02800 Africa/Johannesburg # # The next section contains experimental tab-separated comments for # use by user agents like tzselect that identify continents and oceans. # # For example, the comment ‘#@AQAntarctica/’ means the country code # AQ is in the continent Antarctica regardless of the Zone name, # so Pacific/Auckland should be listed under Antarctica as well as # under the Pacific because its line’s country codes include AQ. # # If more than one country code is affected each is listed separated # by commas, e.g., ‘#@IS,SHAtlantic/’. If a country code is in # more than one continent or ocean, each is listed separated by # commas, e.g., the second column of ‘#@CY,TRAsia/,Europe/’. # # These experimental comments are present only for country codes where # the continent or ocean is not already obvious from the Zone name. # For example, there is no such comment for RU since it already # corresponds to Zone names starting with both ‘Europe/’ and ‘Asia/’. # #@AQ Antarctica/ #@IS,SH Atlantic/ #@CY,TR Asia/,Europe/ #@SJ Arctic/ #@CC,CX,KM,MG,YT Indian/ pr0m1th3as-datatypes-9c9a8d3/inst/tzdata/zonenow.tab000066400000000000000000000200701522766574100225070ustar00rootroot00000000000000# tzdb timezone descriptions, for users who do not care about old timestamps # # This file is in the public domain. # # From Paul Eggert (2023-12-18): # This file contains a table where each row stands for a timezone # where civil timestamps are predicted to agree from now on. # This file is like zone1970.tab (see zone1970.tab’s comments), # but with the following changes: # # 1. Each timezone corresponds to a set of clocks that are planned # to agree from now on. This is a larger set of clocks than in # zone1970.tab, where each timezone’s clocks must agree from 1970 on. # 2. The first column is irrelevant and ignored. # 3. The table is sorted in a different way: # first by standard time UTC offset; # then, if DST is used, by daylight saving UTC offset; # then by time zone abbreviation. # 4. Every timezone has a nonempty comments column, with wording # distinguishing the timezone only from other timezones with the # same UTC offset at some point during the year. # # The format of this table is experimental, and may change in future versions. # # This table is intended as an aid for users, to help them select timezones # appropriate for their practical needs. It is not intended to take or # endorse any position on legal or territorial claims. # #XX coordinates TZ comments # # -11 - SST XX -1416-17042 Pacific/Pago_Pago Midway; Samoa (SST) # # -11 XX -1901-16955 Pacific/Niue Niue # # -10 - HST XX +211825-1575130 Pacific/Honolulu Hawaii (HST) # # -10 XX -1732-14934 Pacific/Tahiti Tahiti; Cook Islands # # -10/-09 - HST / HDT (North America DST) XX +515248-1763929 America/Adak western Aleutians in Alaska (HST/HDT) # # -09:30 XX -0900-13930 Pacific/Marquesas Marquesas # # -09 XX -2308-13457 Pacific/Gambier Gambier # # -09/-08 - AKST/AKDT (North America DST) XX +611305-1495401 America/Anchorage most of Alaska (AKST/AKDT) # # -08 XX -2504-13005 Pacific/Pitcairn Pitcairn # # -08/-07 - PST/PDT (North America DST) XX +340308-1181434 America/Los_Angeles Pacific (PST/PDT) - US; Mexico near US border # # -08/-07 - PST/PDT (North America DST) until 2026-11-01 02:00; then MST XX +4916-12307 America/Vancouver Mountain Standard (MST) - British Columbia (most areas) # # -07 - MST XX +332654-1120424 America/Phoenix Mountain Standard (MST) - Arizona; western Mexico; Yukon # # -07/-06 - MST/MDT (North America DST) XX +394421-1045903 America/Denver Mountain (MST/MDT) - US; Mexico near US border; northern Canada # # -07/-06 - MST/MDT (North America DST) until 2026-11-01 02:00; then CST XX +5333-11328 America/Edmonton Central Standard (CST) - Alberta and some neighbors # # -06 XX -0054-08936 Pacific/Galapagos Galápagos # # -06 - CST XX +1924-09909 America/Mexico_City Central Standard (CST) - Saskatchewan; central Mexico; Central America # # -06/-05 (Chile DST) XX -2709-10926 Pacific/Easter Easter Island # # -06/-05 - CST/CDT (North America DST) XX +415100-0873900 America/Chicago Central (CST/CDT) - US & Canada; Mexico near US border # # -05 XX -1203-07703 America/Lima eastern South America # # -05 - EST XX +175805-0764736 America/Jamaica Eastern Standard (EST) - Caymans; Jamaica; eastern Mexico; Panama # # -05/-04 - CST/CDT (Cuba DST) XX +2308-08222 America/Havana Cuba # # -05/-04 - EST/EDT (North America DST) XX +404251-0740023 America/New_York Eastern (EST/EDT) - US & Canada # # -04 XX +1030-06656 America/Caracas western South America # # -04 - AST XX +1828-06954 America/Santo_Domingo Atlantic Standard (AST) - eastern Caribbean # # -04/-03 (Chile DST) XX -3327-07040 America/Santiago most of Chile # # -04/-03 - AST/ADT (North America DST) XX +4439-06336 America/Halifax Atlantic (AST/ADT) - Canada; Bermuda # # -03:30/-02:30 - NST/NDT (North America DST) XX +4734-05243 America/St_Johns Newfoundland (NST/NDT) # # -03 XX -2332-04637 America/Sao_Paulo eastern and southern South America # # -03/-02 (North America DST) XX +4703-05620 America/Miquelon St Pierre & Miquelon # # -02 XX -0351-03225 America/Noronha Fernando de Noronha; South Georgia # # -02/-01 (EU DST) XX +6411-05144 America/Nuuk most of Greenland # # -01 XX +1455-02331 Atlantic/Cape_Verde Cape Verde # # -01/+00 (EU DST) XX +3744-02540 Atlantic/Azores Azores # # +00 - GMT XX +0519-00402 Africa/Abidjan far western Africa; Iceland (GMT) # # +00/+01 - GMT/BST (EU DST) XX +513030-0000731 Europe/London United Kingdom (GMT/BST) # # +00/+01 - WET/WEST (EU DST) XX +3843-00908 Europe/Lisbon western Europe (WET/WEST) # # +00/+02 - Troll DST XX -720041+0023206 Antarctica/Troll Troll Station in Antarctica # # +01 - CET XX +3647+00303 Africa/Algiers Algeria, Tunisia (CET) # # +01 - WAT XX +0627+00324 Africa/Lagos western Africa (WAT) # # +01/+00 - IST/GMT (EU DST in reverse) XX +5320-00615 Europe/Dublin Ireland (IST/GMT) # # +01/+00 - (Morocco DST) XX +3339-00735 Africa/Casablanca Morocco # # +01/+02 - CET/CEST (EU DST) XX +4852+00220 Europe/Paris central Europe (CET/CEST) # # +02 - CAT XX -2558+03235 Africa/Maputo central Africa (CAT) # # +02 - EET XX +3254+01311 Africa/Tripoli Libya; Kaliningrad (EET) # # +02 - SAST XX -2615+02800 Africa/Johannesburg southern Africa (SAST) # # +02/+03 - EET/EEST (EU DST) XX +3758+02343 Europe/Athens eastern Europe (EET/EEST) # # +02/+03 - EET/EEST (Egypt DST) XX +3003+03115 Africa/Cairo Egypt # # +02/+03 - EET/EEST (Lebanon DST) XX +3353+03530 Asia/Beirut Lebanon # # +02/+03 - EET/EEST (Palestine DST) XX +3130+03428 Asia/Gaza Palestine # # +02/+03 - IST/IDT (Israel DST) XX +314650+0351326 Asia/Jerusalem Israel # # +03 XX +4101+02858 Europe/Istanbul Near East; Belarus # # +03 - EAT XX -0117+03649 Africa/Nairobi eastern Africa (EAT) # # +03 - MSK XX +554521+0373704 Europe/Moscow Moscow (MSK) # # +03:30 XX +3540+05126 Asia/Tehran Iran # # +04 XX +2518+05518 Asia/Dubai Russia; Caucasus; Persian Gulf; Seychelles; Réunion # # +04:30 XX +3431+06912 Asia/Kabul Afghanistan # # +05 XX +4120+06918 Asia/Tashkent Russia; Kazakhstan; Tajikistan; Turkmenistan; Uzbekistan; Maldives # # +05 - PKT XX +2452+06703 Asia/Karachi Pakistan (PKT) # # +05:30 XX +0656+07951 Asia/Colombo Sri Lanka # # +05:30 - IST XX +2232+08822 Asia/Kolkata India (IST) # # +05:45 XX +2743+08519 Asia/Kathmandu Nepal # # +06 XX +2343+09025 Asia/Dhaka Russia; Kyrgyzstan; Bhutan; Bangladesh; Chagos # # +06:30 XX +1647+09610 Asia/Yangon Myanmar; Cocos # # +07 XX +1345+10031 Asia/Bangkok Russia; Indochina; Christmas Island # # +07 - WIB XX -0610+10648 Asia/Jakarta Indonesia (WIB) # # +08 XX +0117+10351 Asia/Singapore Russia; Brunei; Malaysia; Singapore; Concordia # # +08 - AWST XX -3157+11551 Australia/Perth Western Australia (AWST) # # +08 - CST XX +3114+12128 Asia/Shanghai China (CST) # # +08 - HKT XX +2217+11409 Asia/Hong_Kong Hong Kong (HKT) # # +08 - PHT XX +143512+1205804 Asia/Manila Philippines (PHT) # # +08 - WITA XX -0507+11924 Asia/Makassar Indonesia (WITA) # # +08:45 XX -3143+12852 Australia/Eucla Eucla # # +09 XX +5203+11328 Asia/Chita Russia; Palau; East Timor # # +09 - JST XX +353916+1394441 Asia/Tokyo Japan (JST); Eyre Bird Observatory # # +09 - KST XX +3733+12658 Asia/Seoul Korea (KST) # # +09 - WIT XX -0232+14042 Asia/Jayapura Indonesia (WIT) # # +09:30 - ACST XX -1228+13050 Australia/Darwin Northern Territory (ACST) # # +09:30/+10:30 - ACST/ACDT (Australia DST) XX -3455+13835 Australia/Adelaide South Australia (ACST/ACDT) # # +10 XX +4310+13156 Asia/Vladivostok Russia; Yap; Chuuk; Papua New Guinea; Dumont d’Urville # # +10 - AEST XX -2728+15302 Australia/Brisbane Queensland (AEST) # # +10 - ChST XX +1328+14445 Pacific/Guam Mariana Islands (ChST) # # +10/+11 - AEST/AEDT (Australia DST) XX -3352+15113 Australia/Sydney southeast Australia (AEST/AEDT) # # +10:30/+11 XX -3133+15905 Australia/Lord_Howe Lord Howe Island # # +11 XX -0613+15534 Pacific/Bougainville Russia; Kosrae; Bougainville; Solomons # # +11/+12 (Australia DST) XX -2903+16758 Pacific/Norfolk Norfolk Island # # +12 XX +5301+15839 Asia/Kamchatka Russia; Tuvalu; Fiji; etc. # # +12/+13 (New Zealand DST) XX -3652+17446 Pacific/Auckland New Zealand (NZST/NZDT) # # +12:45/+13:45 (Chatham DST) XX -4357-17633 Pacific/Chatham Chatham Islands # # +13 XX -210800-1751200 Pacific/Tongatapu Kanton; Tokelau; Samoa (western); Tonga # # +14 XX +0152-15720 Pacific/Kiritimati Kiritimati pr0m1th3as-datatypes-9c9a8d3/inst/vartype.m000066400000000000000000000107161522766574100207070ustar00rootroot00000000000000## Copyright (C) 2024-2026 Andreas Bertsatos ## ## This file is part of the datatypes package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . classdef vartype ## -*- texinfo -*- ## @deftp {datatypes} vartype ## ## Subscript into a table by variable type. ## ## A utility class that facilitates subscripting table variables according to ## their data type. ## ## @seealso{table} ## @end deftp properties (SetAccess = private, Hidden) ## Type of data type endproperties methods (Hidden) ## Custom display function display (this) in_name = inputname (1); if (! isempty (in_name)) fprintf ("%s =\n", in_name); endif disp (this); endfunction ## Custom display function disp (this) fprintf ("\n table vartype subscript:\n\n"); fprintf (" Select table variables matching the type '%s'\n\n", ... this.type); endfunction endmethods methods (Access = public) ## -*- texinfo -*- ## @deftypefn {vartype} {@var{S} =} vartype (@var{type}) ## ## Create a subscript into table by variable type. ## ## @code{@var{S} = vartype (@var{type})} creates a subscript to select table ## variables of a specified type. The input argument, @var{type}, must be a ## character vector or a string scalar that specifies any type that is ## accepted by the @code{isa} function, such as @code{numeric}, ## @code{logical}, @code{integer}, @code{string}, @code{categorical}, etc. ## It can also be @code{cellstr} to select variables that contain cell ## arrays of character vectors or @code{numeric} to select variables with ## numeric values. ## ## @end deftypefn function this = vartype (type) if (! ((isvector (type) && ischar (type)) || isa (type, "string"))) error (["vartype: TYPE must be either a character", ... " vector or a string scalar."]); endif this.type = char (cellstr (type)); endfunction ## -*- texinfo -*- ## @deftypefn {vartype} {@var{TF} =} varMatch (@var{obj}, @var{value}) ## ## Match a @qcode{vartype} object to a variable value. ## ## @code{@var{TF} = varMatch (@var{obj}, @var{value})} compares the class ## type of the variable in @var{value} with the predefined class type in the ## @qcode{vartype} object. If they are equal, @var{TF} is @qcode{true}, ## otherwise @var{TF} is @qcode{false}. ## ## @end deftypefn function TF = varMatch (this, varVal) if (isequal (this.type, 'cellstr')) TF = iscellstr (varVal); elseif (isequal (this.type, 'numeric')) TF = isnumeric (varVal); else TF = isa (varVal, this.type); endif endfunction endmethods endclassdef ## Test output %!test %! S = vartype ('cellstr'); %! assert_equal (isa (S, "vartype"), true); %!test %! S = vartype ('cellstr'); %! assert_equal (S.varMatch ({2343}), false); %! assert_equal (S.varMatch ({'as'}), true); %!test %! S = vartype ('string'); %! assert_equal (S.varMatch (string ('as')), true); %! assert_equal (S.varMatch ({'as'}), false); %!test %! S = vartype ('single'); %! assert_equal (S.varMatch (34.5), false); %! assert_equal (S.varMatch (single (34.5)), true); %!test %! S = vartype ('numeric'); %! assert_equal (S.varMatch (int8 (34)), true); %! assert_equal (S.varMatch (single (34.5)), true); %!test %! S = vartype ('duration'); %! assert_equal (S.varMatch (int8 (34)), false); %! assert_equal (S.varMatch (calweeks (3)), false); %! assert_equal (S.varMatch (hours (12)), true); %!test %! S = vartype ('calendarDuration'); %! assert_equal (S.varMatch ('char'), false); %! assert_equal (S.varMatch (calweeks (3)), true); %! assert_equal (S.varMatch (hours (12)), false); ## Test input validation %!error ... %! vartype (3) pr0m1th3as-datatypes-9c9a8d3/inst/xlsx2struct.m000066400000000000000000000111011522766574100215270ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the datatypes package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {datatypes} {@var{s} =} xlsx2struct (@var{filename}) ## ## Read every sheet of an Excel workbook into a scalar structure. ## ## @code{@var{s} = xlsx2struct (@var{filename})} reads each sheet of the Office ## Open XML workbook named by @var{filename} (@qcode{.xlsx} or @qcode{.xlsm}) ## into a @code{table} and returns a scalar structure with one field per sheet, ## in sheet order. Each sheet is read as by @code{readtable} (variable names ## from the first row, types detected automatically); it is the inverse of ## @code{struct2xlsx}. ## ## A sheet name that is not a valid structure field name is canonicalised with ## @code{matlab.lang.makeValidName} (and made unique if two names collide); when ## the field name differs from the sheet name the original is stored on that ## field's table as the @qcode{'ActualSheetName'} custom property, so a ## subsequent @code{struct2xlsx} restores the exact sheet name. ## ## @seealso{struct2xlsx, readtable, writetable, ods2struct} ## @end deftypefn function s = xlsx2struct (filename) if (nargin != 1) print_usage (); endif if (! (ischar (filename) || iscellstr (filename) || isa (filename, 'string'))) error (strcat ("xlsx2struct: FILENAME must be a character vector,", ... " cellstr, or string.")); endif file = char (cellstr (filename)); ## Enumerate the sheet names (the first output doubles as an error probe). [data, ~, ~, names] = __xlsx2table__ (file); if (ischar (data)) error ("xlsx2struct: %s", data); endif s = struct (); usedFields = {}; for k = 1:numel (names) sn = names{k}; R = readtable (file, 'Sheet', sn); fn = matlab.lang.makeValidName (sn); base = fn; j = 1; while (any (strcmp (fn, usedFields))) fn = sprintf ('%s_%d', base, j); j += 1; endwhile usedFields{end+1} = fn; if (! strcmp (fn, sn)) R = addprop (R, 'ActualSheetName', 'table'); R.Properties.CustomProperties.ActualSheetName = sn; endif s.(fn) = R; endfor endfunction %!demo %! ## `xlsx2struct` is the inverse of `struct2xlsx`: it reads every worksheet of %! ## an Excel workbook into a scalar struct, one field per sheet, in sheet order. %! %! wb.Patients = table ({'Li'; 'Diaz'}, [38; 40], 'VariableNames', {'Name', 'Age'}); %! wb.Visits = table ([1; 2; 3], 'VariableNames', {'Visit'}); %! filename = fullfile (tempdir (), 'clinic.xlsx'); %! struct2xlsx (filename, wb); %! %! s = xlsx2struct (filename); %! s.Patients %! %! delete (filename); %!test # round-trip a multi-sheet workbook written by struct2xlsx %! s.alpha = table ([1; 2; 3], {'a'; 'b'; 'c'}, 'VariableNames', {'x', 'y'}); %! s.beta = table ([10.5; 20.5], 'VariableNames', {'v'}); %! fn = [tempname() '.xlsx']; %! unwind_protect %! struct2xlsx (fn, s); %! r = xlsx2struct (fn); %! assert_equal (fieldnames (r), {'alpha'; 'beta'}); %! assert_equal (r.alpha.x, [1; 2; 3]); %! assert_equal (r.beta.v, [10.5; 20.5]); %! unwind_protect_cleanup %! delete (fn); %! end_unwind_protect %!test # an odd sheet name canonicalises and stashes ActualSheetName %! T = table ([7; 8], 'VariableNames', {'v'}); %! T = addprop (T, 'ActualSheetName', 'table'); %! T.Properties.CustomProperties.ActualSheetName = 'My Sheet'; %! s.only = T; %! fn = [tempname() '.xlsx']; %! unwind_protect %! struct2xlsx (fn, s); %! r = xlsx2struct (fn); %! f = fieldnames (r); %! assert_equal (numel (f), 1); %! assert_equal (isvarname (f{1}), true); %! assert_equal (r.(f{1}).Properties.CustomProperties.ActualSheetName, ... %! 'My Sheet'); %! assert_equal (r.(f{1}).v, [7; 8]); %! unwind_protect_cleanup %! delete (fn); %! end_unwind_protect %!error ... %! xlsx2struct (42) %!error ... %! xlsx2struct ([tempname() '.xlsx']) pr0m1th3as-datatypes-9c9a8d3/inst/years.m000066400000000000000000000041261522766574100203360ustar00rootroot00000000000000## Copyright (C) 2024-2026 Andreas Bertsatos ## ## This file is part of the datatypes package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {datatypes} {@var{D} =} years (@var{X}) ## ## Fixed-time duration in years. ## ## @code{@var{D} = years (@var{X})} returns a @qcode{duration} array ## representing fixed-time duration years equivalent to the values in @var{X}, ## which must be a numeric array. A fixed-length year is equal to 365.2425 ## days. ## ## @code{years} is also available as a method for @qcode{duration} arrays, in ## which case it performs the opposite conversion. ## ## @seealso{duration, days, hours, minutes, seconds, milliseconds, ## duration.years} ## @end deftypefn function out = years (x) if (nargin == 0) x = 1; elseif (! isnumeric (x)) error ("years: input array must be numeric."); elseif (! isreal (x)) error ("years: input array must be real."); endif out = duration (double (x) * 24 * 365.2425, 0, 0, 'Format', 'y'); endfunction %!demo %! ## `years` builds a *fixed-length* year of 365.2425 days — a constant span, not %! ## a calendar year. %! %! years (1) %! years (0.5) %!test %! X = magic (3); %! D = years (X); %! assert_equal (size (D), size (X)); %!test %! D = years ([1, 2, 3]); %! assert_equal (years (D), [1, 2, 3]); %!test %! D = years (); %! assert_equal (years (D), 1); %!error years ("asd"); %!error years (1+i); pr0m1th3as-datatypes-9c9a8d3/src/000077500000000000000000000000001522766574100166445ustar00rootroot00000000000000pr0m1th3as-datatypes-9c9a8d3/src/Makefile000066400000000000000000000030601522766574100203030ustar00rootroot00000000000000# Makefile for compiling required oct files TARGET_OS ?= $(OS) ifeq ($(TARGET_OS),Windows_NT) LIBS = -lole32 else LIBS = endif MKOCTFILE ?= mkoctfile LDFLAGS += $(LIBS) TZFLAGS = -DHAS_REMOTE_API=0 # Make glibc define __USE_LARGEFILE64 so miniz selects its fopen64/ftello64/ # fseeko64 branch instead of the plain fopen/ftello path. This silences miniz's # "may not support large files" #pragma message and enables 64-bit file offsets. MINIZFLAGS = -D_LARGEFILE64_SOURCE OCTFILES = __datetime__.oct \ __table2csv__.oct \ __csv2table__.oct \ __table2ods__.oct \ __ods2table__.oct \ __table2xlsx__.oct \ __xlsx2table__.oct \ __ckeyHash__.oct \ __nkeyHash__.oct .PHONY: all all: $(OCTFILES) __datetime__.oct: __datetime__.cc tz.cpp $(MKOCTFILE) '-I ./date' __datetime__.cc tz.cpp $(TZFLAGS) $(LDFLAGS) __table2ods__.oct: __table2ods__.cc ods/pugixml.cpp ods/miniz.c $(MKOCTFILE) -I./ods $(MINIZFLAGS) __table2ods__.cc ods/pugixml.cpp ods/miniz.c $(LDFLAGS) __ods2table__.oct: __ods2table__.cc ods/pugixml.cpp ods/miniz.c $(MKOCTFILE) -I./ods $(MINIZFLAGS) __ods2table__.cc ods/pugixml.cpp ods/miniz.c $(LDFLAGS) __table2xlsx__.oct: __table2xlsx__.cc ods/miniz.c $(MKOCTFILE) -I./ods $(MINIZFLAGS) __table2xlsx__.cc ods/miniz.c $(LDFLAGS) __xlsx2table__.oct: __xlsx2table__.cc ods/pugixml.cpp ods/miniz.c $(MKOCTFILE) -I./ods $(MINIZFLAGS) __xlsx2table__.cc ods/pugixml.cpp ods/miniz.c $(LDFLAGS) %.oct: %.cc $(MKOCTFILE) $< $(LDFLAGS) .PHONY: clean clean: rm -f $(OCTFILES) pr0m1th3as-datatypes-9c9a8d3/src/__ckeyHash__.cc000066400000000000000000000050121522766574100215040ustar00rootroot00000000000000/* Copyright (C) 2025-2026 Andreas Bertsatos This file is part of the datatypes package for GNU Octave. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, see . */ #define FNV1A64_PRIME 0x00000100000001b3 #include #include using namespace std; static inline constexpr uint64_t fnv1a64 (const char* buf, size_t len, uint64_t out) { for (size_t i = 0; i < len; i++) { // default char signedness depends on architecture, for signed for // backwards compatibility const unsigned char b = buf[i]; out = (out ^ b) * FNV1A64_PRIME; } return out; } namespace /* tests */ { constexpr std::array testData{0, 1, char(0x80), 0}; static_assert(0 == fnv1a64(testData.data(), 0, 0)); static_assert(0x123 == fnv1a64(testData.data(), 0, 0x123)); static_assert(0 == fnv1a64(testData.data(), 1, 0)); static_assert(FNV1A64_PRIME == fnv1a64(testData.data(), 1, 1)); static_assert(FNV1A64_PRIME == fnv1a64(testData.data(), 2, 0)); } DEFUN_DLD (__ckeyHash__, args, nargout, "-*- texinfo -*-\n\ @deftypefn {} {@var{uint64} =} __ckeyHash__ (@var{str})\n\ @deftypefnx {} {@var{uint64} =} __ckeyHash__ (@var{str}, @var{FNV1A64_BASE})\n\ \n\ \n\ Fowler–Noll–Vo hash key for a character vector. \n\ \n\ This is a helper function for @qcode{keyHash} methods of `datatypes`' classes. \ Do NOT use this function directly. \n\ \n\ @end deftypefn") { octave_uint64 base; // Validate input if (args.length () < 1) { error ("__ckeyHash__: too few input arguments."); } if (! (args(0).is_string ())) { error ("__ckeyHash__: STR must be a character vector."); } // Get or assign a base value if (args.length() > 1) { base = args(1).uint64_scalar_value (); } else { base = 0xcbf29ce484222325; // default FNV1A64_BASE } string str = args(0).string_value (); octave_uint64 out = fnv1a64 (str.c_str (), str.length (), base); octave_value_list retval (nargout); retval(0) = out; return retval; } pr0m1th3as-datatypes-9c9a8d3/src/__csv2table__.cc000066400000000000000000000144531522766574100216430ustar00rootroot00000000000000/* Copyright (C) 2025-2026 Andreas Bertsatos This file is part of the datatypes package for GNU Octave. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, see . */ #include #include #include #include #include #include #include #include #include #include #include using namespace std; DEFUN_DLD (__csv2table__, args, nargout, "-*- texinfo -*-\n \ @deftypefn {datatypes} {@var{C} =} __csv2table__ (@var{file})\n\ \n\ \n\ Barebone function for loading a CSV file to a cell array.\n\ \n\ This is a helper IO function for the @qcode{csv2table} function. Do NOT \ call it directly. \n\ \n\ @end deftypefn") { octave_value_list retval(nargout); // Check input arguments if (args.length() != 1 && args.length() != 2) { error ("__csv2table__: one or two input arguments are required."); } // Get input arguments string file = args(0).string_value(); // Optional field delimiter (a single character); defaults to a comma. string _sep = ","; if (args.length() == 2) { string d = args(1).string_value(); if (d.length() != 1) { error ("__csv2table__: DELIMITER must be a single character."); } _sep = d; } // Open CSV file ifstream fd(file.c_str()); if (! fd.is_open()) { retval(0) = "cannot open file '" + file + "' for reading."; return retval; } // Initialize necessary variable; char sep = _sep[0]; string _prot = "\""; char prot = _prot[0]; string line, word; bool inside = false; int cols = 0; int rows = 1; // Get number of columns from parsing the first line getline (fd, line); for (int c = 0, len = line.length(); c <= len; c++) { if (c == len || ((line[c] == sep || line[c] == 10) && ! inside)) { cols++; } else if ((inside) && line[c] == prot && (c + 1 < len && line[c + 1] == prot)) { ++c; } else if (line[c] == prot) { inside = ! inside; } } // Get number of row from parsing the remaining file while (getline(fd, line)) { rows++; } fd.clear(); // Rewind fd.seekg (0, ios::beg); if (! fd.good()) { retval(0) = "cannot read '" + file + "'."; return retval; } //Initialize cell array Cell C(rows, cols); for (int c = 0; c < cols; c++) { for (int r = 0; r < rows; r++) { C(r,c) = ""; } } // Parse (again) each line of the file bool line_too_long = false; for (int row = 0; row < rows; row++) { getline (fd, line); word = ""; inside = false; int col = 0; bool oinside = false; for (int k = 0, len = line.length(); k <= len; k++) { if ((k == len || line[k] == sep) && (! inside)) { // Check number of columns if (! line_too_long && col == cols) { line_too_long = true; warning ("__csv2table__: line(s) found with more fields than in headerline"); break; } // Check for last char to be 13 (CR) and remove if found if (word.length () && word[word.length () - 1] == char(13)) { word.resize (word.size () - 1); } // Check if scalar const char *word_str = word.c_str (); char *err; double val = strtod (word_str, &err); // The "NA" token (Octave's missing value, as written by 'table2csv') // is not recognized by strtod; map it to NA explicitly. bool is_na = (! oinside) && (word == "NA"); // A fully consumed, unquoted token is numeric. bool is_num = (word != "") && (! oinside) && (err == word_str + word.length ()); // Store into the cell; check if it is in address argument range if (col < cols) { if (is_na) { C(row, col) = octave_value (octave::numeric_limits::NA ()); } else if (! is_num) { C(row, col) = octave_value (word); } else { // A plain integer of 16 or more digits cannot be held exactly by // a double, so parse it without loss into a 64-bit integer. size_t start = (word[0] == '+' || word[0] == '-') ? 1 : 0; bool is_int = (word.length () > start); size_t ndig = 0; for (size_t i = start; i < word.length (); i++) { if (word[i] < '0' || word[i] > '9') { is_int = false; break; } ndig++; } if (is_int && ndig >= 16) { errno = 0; if (word[0] == '-') { long long llv = strtoll (word_str, &err, 10); C(row, col) = (errno == 0) ? octave_value (octave_int64 (llv)) : octave_value (val); } else { unsigned long long ullv = strtoull (word_str, &err, 10); C(row, col) = (errno == 0) ? octave_value (octave_uint64 (ullv)) : octave_value (val); } } else { C(row, col) = octave_value (val); } } } col++; word = ""; oinside = false; } else if ((inside) && line[k] == prot && (k + 1 < len && line[k+1] == prot)) { // Inside a string word += prot; ++k; } else if (line[k] == prot) { // Switch in/out of string oinside = inside; inside = ! inside; } else { word += line[k]; } } } // Close file fd.close(); retval(0) = C; return retval; } pr0m1th3as-datatypes-9c9a8d3/src/__datetime__.cc000066400000000000000000001063321522766574100215500ustar00rootroot00000000000000/* Copyright (C) 2024-2026 Andreas Bertsatos This file is part of the datatypes package for GNU Octave. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, see . */ #include #include #include #include #include #include #include "date/tz.h" using namespace std; using namespace date; auto double2milli (double time_sec) { using ds = chrono::duration; local_time time{ds{time_sec}}; auto tp = round(time); return tp; } auto double2micro (double time_sec) { using ds = chrono::duration; local_time time{ds{time_sec}}; auto tp = round(time); return tp; } auto double2nano (double time_sec) { using ds = chrono::duration; local_time time{ds{time_sec}}; auto tp = round(time); return tp; } auto from_to_tz_milli (double time_sec, string from_tzone, string to_tzone) { auto tp = double2milli (time_sec); auto from = make_zoned (from_tzone, tp); auto to = make_zoned (to_tzone, from.get_sys_time ()); return to; } auto from_to_tz_micro (double time_sec, string from_tzone, string to_tzone) { auto tp = double2micro (time_sec); auto from = make_zoned (from_tzone, tp); auto to = make_zoned (to_tzone, from.get_sys_time ()); return to; } auto from_to_tz_nano (double time_sec, string from_tzone, string to_tzone) { auto tp = double2nano (time_sec); auto from = make_zoned (from_tzone, tp); auto to = make_zoned (to_tzone, from.get_sys_time ()); return to; } RowVector seconds2vector (double time_sec, string precision) { RowVector OUT(6); auto tp = double2micro (time_sec); auto day_tp = chrono::floor(tp); hh_mm_ss time_tp{tp - day_tp}; year_month_day date_tp{day_tp}; OUT(0) = (int)date_tp.year(); OUT(1) = (unsigned int)date_tp.month(); OUT(2) = (unsigned int)date_tp.day(); OUT(3) = time_tp.hours().count(); OUT(4) = time_tp.minutes().count(); OUT(5) = (double)time_tp.seconds().count() + (double)time_tp.subseconds().count() / 1000000; if (precision == "milliseconds") { OUT(5) = round (OUT(5) * 1000) / 1000; } return OUT; } template RowVector tz2vector (const ZonedType& to, string precision) { RowVector OUT(6); auto t_local = to.get_local_time(); auto today_local = chrono::floor(t_local); hh_mm_ss time_local{t_local - today_local}; year_month_day date_local{today_local}; OUT(0) = (int)date_local.year(); OUT(1) = (unsigned int)date_local.month(); OUT(2) = (unsigned int)date_local.day(); OUT(3) = time_local.hours().count(); OUT(4) = time_local.minutes().count(); if (precision == "milliseconds") { OUT(5) = (double)time_local.seconds().count() + (double)time_local.subseconds().count() / 1000; } else { OUT(5) = (double)time_local.seconds().count() + (double)time_local.subseconds().count() / 1000000; } return OUT; } // Convert a single set of (possibly non-canonical) date/time components to a // UTC sys_time, interpreting the wall-clock values in 'timezone'. This mirrors // the aggregation/rollover math used by the component-normalisation path below // and is shared by the 'ConvertTo','posixtime' serial mode, so both stay in // lockstep. Callers must screen NaN/Inf beforehand. sys_time components2sys (double Yv, double Mv, double Dv, double hv, double mv, double sv, double xv, string timezone, string precision) { // Aggregate hours, minutes, seconds, and milliseconds into seconds, // calculate extra days to add later and map remaining hours, minutes, and // seconds to a local_time variable. double time_sec = hv * 3600 + mv * 60 + sv + xv / 1000; int extra_days = (int)time_sec / 86400; // Subtract one day for negative time, because it goes missing from the // conversion in 'seconds2vector' below. if (time_sec < 0) { extra_days -= 1; } time_sec = remainder (time_sec, 86400); RowVector HMS = seconds2vector (time_sec, precision); int tmp_h = (int)HMS(3); int tmp_m = (int)HMS(4); int tmp_s = (int)HMS(5); double pr = 1000000; if (precision == "milliseconds") { pr = 1000; } double tmp_frac_sec = HMS(5) - tmp_s; int tmp_micro = (int)(round (tmp_frac_sec * pr)); // Fix years / months int tmp_Y = (int)Yv + ((int)Mv / 12); int tmp_M = (int)Mv % 12; int tmp_D = (int)Dv + (int)extra_days; // Add/subtract months and days accordingly year_month_day ymd = year(tmp_Y)/(int)0/(int)0; if (tmp_M < 0) { ymd -= months{-tmp_M}; } else { ymd += months{tmp_M}; } if (tmp_D < 0) { ymd = sys_days{ymd} - days{-tmp_D}; } else { ymd = sys_days{ymd} + days{tmp_D}; } // Add time to date and interpret the wall-clock value in 'timezone' auto datetime = local_days{ymd} + chrono::hours{tmp_h} + chrono::minutes{tmp_m} + chrono::seconds{tmp_s} + chrono::microseconds{tmp_micro}; auto in = make_zoned (timezone, datetime); return chrono::time_point_cast (in.get_sys_time ()); } auto timezone_precision (double time_sec, string timezone, string precision) { auto tz = make_zoned (current_zone (), chrono::system_clock::now ()); if (precision == "milliseconds") { auto tp = double2milli (time_sec); tz = make_zoned (timezone, tp); } else if (precision == "microseconds") { auto tp = double2micro (time_sec); tz = make_zoned (timezone, tp); } else { auto tp = double2nano (time_sec); using duration_type = std::chrono::duration>; // microseconds tz = make_zoned (timezone, std::chrono::time_point_cast(tp)); } return tz; } template RowVector timezone2vector (const ZonedType& to) { RowVector OUT(6); auto t_local = to.get_local_time(); auto day_tp = chrono::floor(t_local); hh_mm_ss time_tp{t_local - day_tp}; year_month_day date_tp{day_tp}; OUT(0) = (int)date_tp.year(); OUT(1) = (unsigned int)date_tp.month(); OUT(2) = (unsigned int)date_tp.day(); OUT(3) = time_tp.hours().count(); OUT(4) = time_tp.minutes().count(); OUT(5) = (double)time_tp.seconds().count() + (double)time_tp.subseconds().count() / 1000000000; return OUT; } NDArray expand_input (dim_vector sz, octave_value args) { NDArray OUT(sz, 0); if (args.is_scalar_type ()) { for (int i = 0; i < sz.numel (); i++) { OUT(i) = args.scalar_value (); } } else { NDArray tmp = args.array_value (); for (int i = 0; i < sz.numel (); i++) { OUT(i) = tmp(i); } } return OUT; } double check_nan_inf (RowVector IN) { int n = IN.numel (); double OUT = 0; bool is_nan = false; bool isPinf = false; bool isNinf = false; for (int i = 0; i < n; i++) { if (isnan (IN(i))) { is_nan = true; } else if (! is_nan && ! isNinf && isinf (IN(i)) && IN(i) > 0) { isPinf = true; } else if (! is_nan && ! isPinf && isinf (IN(i)) && IN(i) < 0) { isNinf = true; } else if (! is_nan && isNinf && isinf (IN(i)) && IN(i) > 0) { is_nan = true; } else if (! is_nan && isPinf && isinf (IN(i)) && IN(i) < 0) { is_nan = true; } } if (is_nan) { OUT = NAN; } else if (isPinf) { OUT = INFINITY; } else if (isNinf) { OUT = -INFINITY; } return OUT; } DEFUN_DLD(__datetime__, args, nargout, "-*- texinfo -*-\n\ @deftypefn {datatypes} {[@var{Y}, @var{MO}, @var{D}, @var{H}, @var{MI}, @var{S}]} __datetime__ (@dots{})\n\ @deftypefnx {datatypes} {[@var{Y}, @var{MO}, @var{D}, @var{H}, @var{MI}, @var{S}, @var{errmsg}]} __datetime__ (@dots{})\n\ @deftypefnx {datatypes} {[@dots{}] =} __datetime__ (@qcode{'now'})\n\ @deftypefnx {datatypes} {[@dots{}] =} __datetime__ (@qcode{'today'})\n\ @deftypefnx {datatypes} {[@dots{}] =} __datetime__ (@qcode{'tomorrow'})\n\ @deftypefnx {datatypes} {[@dots{}] =} __datetime__ (@qcode{'yesterday'})\n\ @deftypefnx {datatypes} {[@dots{}] =} __datetime__ (@var{Y}, @var{MO}, @var{D})\n\ @deftypefnx {datatypes} {[@dots{}] =} __datetime__ (@var{Y}, @var{MO}, @var{D}, @var{H}, @var{MI}, @var{S})\n\ @deftypefnx {datatypes} {[@dots{}] =} __datetime__ (@var{Y}, @var{MO}, @var{D}, @var{H}, @var{MI}, @var{S}, @var{MS})\n\ @deftypefnx {datatypes} {[@dots{}] =} __datetime__ (@var{X}, @qcode{'ConvertFrom'}, @var{dateType})\n\ @deftypefnx {datatypes} {[@dots{}] =} __datetime__ (@dots{}, @qcode{'Precision'}, @var{precision})\n\ @deftypefnx {datatypes} {[@dots{}] =} __datetime__ (@dots{}, @qcode{'TimeZone'}, @var{tzone}, @qcode{'toTimeZone'}, @var{totzone})\n\ \n\ \n\ Base function for datetime class. \n\ \n\n\ @end deftypefn") { // The 'ConvertTo' serial mode returns a single output; every other mode // requires either 6 or 7 output arguments. Detect the mode up front so the // output-count guard below can exempt it. bool toSerial = false; for (int i = 0; i + 1 < args.length (); i++) { if (args(i).is_string () && args(i).string_value () == "ConvertTo") { toSerial = true; } } // Either 6 or 7 output arguments are required if (nargout > 7) { error ("__datetime__: too many output arguments."); } if (nargout < 6 && ! toSerial) { error ("__datetime__: too few output arguments."); } // Prepare input output arguments int nargin = args.length (); octave_value_list retval(nargout); for (int i = 0; i < nargout; i++) { retval(i) = 0; } // Add defaults string timezone; try { timezone = current_zone () -> name (); } catch (const exception& e) { octave_stdout << "__datetime__: TZDB error: " << e.what() << "\n"; octave_stdout << "Falling back to UTC.\n"; timezone = "UTC"; } string to_tzone = timezone; string precision = "milliseconds"; bool doLeapSec = false; bool doConvert = false; string convertFrom = ""; string convertTo = ""; // Parse paired arguments here while (nargin > 2 && args(nargin - 2).is_string ()) { if (args(nargin - 2).string_value () == "ConvertFrom") { if (args(nargin - 1).is_string ()) { convertFrom = args(nargin - 1).string_value (); doConvert = true; } else { if (nargout == 7) { retval(6) = "invalid type for 'ConvertFrom'."; return retval; } else { error ("__datetime__: invalid type for 'ConvertFrom'."); } } } else if (args(nargin - 2).string_value () == "ConvertTo") { if (args(nargin - 1).is_string ()) { convertTo = args(nargin - 1).string_value (); } else { if (nargout == 7) { retval(6) = "invalid type for 'ConvertTo'."; return retval; } else { error ("__datetime__: invalid type for 'ConvertTo'."); } } } else if (args(nargin - 2).string_value () == "Precision") { if (args(nargin - 1).is_string ()) { precision = args(nargin - 1).string_value (); } else { if (nargout == 7) { retval(6) = "invalid type for 'Precision'."; return retval; } else { error ("__datetime__: invalid type for 'Precision'."); } } } else if (args(nargin - 2).string_value () == "TimeZone") { if (args(nargin - 1).is_string ()) { if (args(nargin - 1).string_value () == "UTCLeapSeconds") { timezone = "UTC"; doLeapSec = true; } else { timezone = args(nargin - 1).string_value (); } } else { if (nargout == 7) { retval(6) = "invalid type for 'TimeZone'."; return retval; } else { error ("__datetime__: invalid type for 'TimeZone'."); } } } else if (args(nargin - 2).string_value () == "toTimeZone") { if (args(nargin - 1).is_string ()) { if (args(nargin - 1).string_value () == "UTCLeapSeconds") { to_tzone = "UTC"; doLeapSec = true; } else { to_tzone = args(nargin - 1).string_value (); } } else { if (nargout == 7) { retval(6) = "invalid type for 'toTimeZone'."; return retval; } else { error ("__datetime__: invalid type for 'toTimeZone'."); } } } //else if (args(nargin - 2).string_value () == "Format") {} //else if (args(nargin - 2).string_value () == "InputFormat") {} //else if (args(nargin - 2).string_value () == "Locale") {} //else if (args(nargin - 2).string_value () == "PivotYear") {} else { if (nargout == 7) { retval(6) = "unrecognized optional paired argument."; return retval; } else { error ("__datetime__: unrecognized optional paired argument."); } } nargin = nargin - 2; } // Check for valid timezone input arguments try { auto tmp = make_zoned(timezone, chrono::system_clock::now()); } catch (exception) { if (nargout == 7) { retval(6) = "unrecognized timezone: '" + timezone + "'"; return retval; } else { error ("__datetime__: invalid string value for 'TimeZone'."); } } try { auto tmp = make_zoned(to_tzone, chrono::system_clock::now()); } catch (exception) { if (nargout == 7) { retval(6) = "unrecognized timezone: '" + to_tzone + "'"; return retval; } else { error ("__datetime__: invalid string value for 'toTimeZone'."); } } // Handle relativeDay (only one argument left) if (args(0).is_string ()) { Matrix Y(1,1); Matrix M(1,1); Matrix D(1,1); Matrix h(1,1); Matrix m(1,1); Matrix s(1,1); auto today = chrono::system_clock::now (); if (args(0).string_value () == "now") { auto tz = make_zoned(timezone, today); RowVector OUT = timezone2vector (tz); Y(0) = OUT(0); M(0) = OUT(1); D(0) = OUT(2); h(0) = OUT(3); m(0) = OUT(4); if (precision == "milliseconds") { s(0) = round (OUT(5) * 1000) / 1000; } else if (precision == "microseconds") { s(0) = round (OUT(5) * 1000000) / 1000000; } else { s(0) = OUT(5); } } else if (args(0).string_value () == "today") { auto tz = make_zoned(timezone, floor(today)); RowVector OUT = timezone2vector (tz); Y(0) = OUT(0); M(0) = OUT(1); D(0) = OUT(2); h(0) = 0; m(0) = 0; s(0) = 0; } else if (args(0).string_value () == "yesterday") { auto tz = make_zoned(timezone, floor(today) - days{1}); RowVector OUT = timezone2vector (tz); Y(0) = OUT(0); M(0) = OUT(1); D(0) = OUT(2); h(0) = 0; m(0) = 0; s(0) = 0; } else if (args(0).string_value () == "tomorrow") { auto tz = make_zoned(timezone, floor(today) + days{1}); RowVector OUT = timezone2vector (tz); Y(0) = OUT(0); M(0) = OUT(1); D(0) = OUT(2); h(0) = 0; m(0) = 0; s(0) = 0; } retval(0) = Y; retval(1) = M; retval(2) = D; retval(3) = h; retval(4) = m; retval(5) = s; return retval; } // Handle convertFrom (only one argument left) if (doConvert) { if (nargin > 1) { string errmsg = "only a single numeric array is allowed"; errmsg += " when using the 'ConvertFrom' parameter."; if (nargout == 7) { retval(6) = errmsg; return retval; } else { errmsg = "__datetime__: " + errmsg; error ("%s", errmsg.c_str ()); } } // Initialize output argument double time_sec; int n = args(0).numel (); Matrix Y(n,1); // Years (numeric) Matrix M(n,1); // Months (numeric) Matrix D(n,1); // Days (numeric) Matrix h(n,1); // Hours (numeric) Matrix m(n,1); // Minutes (numeric) Matrix s(n,1); // Seconds (numeric) // Fix datetimes from input argument to seconds // according to the requested date/time representation // Precision is limited to microseconds allowing for maximum range // between [-32768-01-01, 32767-12-31]. Set to 'milliseconds' just // rounds to nearest millisecond, while 'nanoseconds' is ignored. ColumnVector Dnum = args(0).column_vector_value (); if (convertFrom == "datenum") { for (int i = 0; i < n; i++) { if (isnan (Dnum(i))) { Y(i) = NAN; M(i) = NAN; D(i) = NAN; h(i) = NAN; m(i) = NAN; s(i) = NAN; } else if (isinf (Dnum(i)) && Dnum(i) > 0) { Y(i) = INFINITY; M(i) = INFINITY; D(i) = INFINITY; h(i) = INFINITY; m(i) = INFINITY; s(i) = INFINITY; } else if (isinf (Dnum(i)) && Dnum(i) < 0) { Y(i) = -INFINITY; M(i) = -INFINITY; D(i) = -INFINITY; h(i) = -INFINITY; m(i) = -INFINITY; s(i) = -INFINITY; } else { time_sec = (Dnum(i) - 719529) * 86400; // to seconds RowVector OUT = seconds2vector (time_sec, precision); Y(i) = OUT(0); M(i) = OUT(1); D(i) = OUT(2); h(i) = OUT(3); m(i) = OUT(4); s(i) = OUT(5); } } } else if (convertFrom == "excel") { for (int i = 0; i < n; i++) { if (isnan (Dnum(i))) { Y(i) = NAN; M(i) = NAN; D(i) = NAN; h(i) = NAN; m(i) = NAN; s(i) = NAN; } else if (isinf (Dnum(i)) && Dnum(i) > 0) { Y(i) = INFINITY; M(i) = INFINITY; D(i) = INFINITY; h(i) = INFINITY; m(i) = INFINITY; s(i) = INFINITY; } else if (isinf (Dnum(i)) && Dnum(i) < 0) { Y(i) = -INFINITY; M(i) = -INFINITY; D(i) = -INFINITY; h(i) = -INFINITY; m(i) = -INFINITY; s(i) = -INFINITY; } else { if (Dnum(i) <= 60) { time_sec = (Dnum(i) - 25568) * 86400; // to seconds } else { time_sec = (Dnum(i) - 25569) * 86400; // to seconds } RowVector OUT = seconds2vector (time_sec, precision); Y(i) = OUT(0); M(i) = OUT(1); D(i) = OUT(2); h(i) = OUT(3); m(i) = OUT(4); s(i) = OUT(5); } } } else if (convertFrom == "posixtime") { for (int i = 0; i < n; i++) { if (isnan (Dnum(i))) { Y(i) = NAN; M(i) = NAN; D(i) = NAN; h(i) = NAN; m(i) = NAN; s(i) = NAN; } else if (isinf (Dnum(i)) && Dnum(i) > 0) { Y(i) = INFINITY; M(i) = INFINITY; D(i) = INFINITY; h(i) = INFINITY; m(i) = INFINITY; s(i) = INFINITY; } else if (isinf (Dnum(i)) && Dnum(i) < 0) { Y(i) = -INFINITY; M(i) = -INFINITY; D(i) = -INFINITY; h(i) = -INFINITY; m(i) = -INFINITY; s(i) = -INFINITY; } else { time_sec = Dnum(i); // already in seconds RowVector OUT = seconds2vector (time_sec, precision); Y(i) = OUT(0); M(i) = OUT(1); D(i) = OUT(2); h(i) = OUT(3); m(i) = OUT(4); s(i) = OUT(5); } } } else if (convertFrom == "epochtime") { if (doLeapSec) { for (int i = 0; i < n; i++) { if (isnan (Dnum(i))) { Y(i) = NAN; M(i) = NAN; D(i) = NAN; h(i) = NAN; m(i) = NAN; s(i) = NAN; } else if (isinf (Dnum(i)) && Dnum(i) > 0) { Y(i) = INFINITY; M(i) = INFINITY; D(i) = INFINITY; h(i) = INFINITY; m(i) = INFINITY; s(i) = INFINITY; } else if (isinf (Dnum(i)) && Dnum(i) < 0) { Y(i) = -INFINITY; M(i) = -INFINITY; D(i) = -INFINITY; h(i) = -INFINITY; m(i) = -INFINITY; s(i) = -INFINITY; } else { time_sec = Dnum(i); // already in seconds // This is a workaround, since I don't know how to properly account // for leap seconds in a fashion that these are added to the given // representation instead of being substructed auto dt = chrono::duration_cast (chrono::duration{time_sec}); auto tp0 = double2micro(0); auto from0 = make_zoned (timezone, tp0); auto from_utc = clock_cast(from0.get_sys_time()); auto to = make_zoned (timezone, clock_cast (from_utc + dt)); auto tp = double2micro(time_sec); auto ti = make_zoned (timezone, tp); auto out = make_zoned (timezone, ti.get_sys_time () + (ti.get_sys_time () - to.get_sys_time ())); RowVector OUT = tz2vector (out, precision); Y(i) = OUT(0); M(i) = OUT(1); D(i) = OUT(2); h(i) = OUT(3); m(i) = OUT(4); s(i) = OUT(5); } } } else { for (int i = 0; i < n; i++) { if (isnan (Dnum(i))) { Y(i) = NAN; M(i) = NAN; D(i) = NAN; h(i) = NAN; m(i) = NAN; s(i) = NAN; } else if (isinf (Dnum(i)) && Dnum(i) > 0) { Y(i) = INFINITY; M(i) = INFINITY; D(i) = INFINITY; h(i) = INFINITY; m(i) = INFINITY; s(i) = INFINITY; } else if (isinf (Dnum(i)) && Dnum(i) < 0) { Y(i) = -INFINITY; M(i) = -INFINITY; D(i) = -INFINITY; h(i) = -INFINITY; m(i) = -INFINITY; s(i) = -INFINITY; } else { time_sec = Dnum(i); // already in seconds RowVector OUT = seconds2vector (time_sec, precision); Y(i) = OUT(0); M(i) = OUT(1); D(i) = OUT(2); h(i) = OUT(3); m(i) = OUT(4); s(i) = OUT(5); } } } } else { string errmsg = "unsupported option for the 'ConvertFrom' parameter."; if (nargout == 7) { retval(6) = errmsg; return retval; } else { errmsg = "__datetime__: " + errmsg; error ("%s", errmsg.c_str ()); } } // Reshape output arguments retval(0) = Y.reshape (args(0).dims ()); retval(1) = M.reshape (args(0).dims ()); retval(2) = D.reshape (args(0).dims ()); retval(3) = h.reshape (args(0).dims ()); retval(4) = m.reshape (args(0).dims ()); retval(5) = s.reshape (args(0).dims ()); return retval; } // Handle single numeric matrix with either 3 or 6 columns if (nargin == 1) { int n = args(0).rows (); ColumnVector Y(n); // Years (numeric) ColumnVector M(n); // Months (numeric) ColumnVector D(n); // Days (numeric) ColumnVector h(n); // Hours (numeric) ColumnVector m(n); // Minutes (numeric) ColumnVector s(n); // Seconds (numeric) if (args(0).ndims () != 2) { string errmsg = "single numeric data input must be a matrix"; errmsg += " unless the 'ConvertFrom' parameter is used."; if (nargout == 7) { retval(6) = errmsg; return retval; } else { errmsg = "__datetime__: " + errmsg; error ("%s", errmsg.c_str ()); } } if (args(0).columns () != 3 && args(0).columns () != 6) { string errmsg = "single numeric matrix must have three or six"; errmsg += " columns unless the 'ConvertFrom' parameter is used."; if (nargout == 7) { retval(6) = errmsg; return retval; } else { errmsg = "__datetime__: " + errmsg; error ("%s", errmsg.c_str ()); } } if (args(0).columns () == 3) { Matrix YMD = args(0).matrix_value (); for (int i = 0; i < n; i++) { if (round (YMD(i,0)) != YMD(i,0) || round (YMD(i,1)) != YMD(i,1) || round (YMD(i,2)) != YMD(i,2)) { string errmsg = "Year, Month, and Day components"; errmsg += " must be integer values."; if (nargout == 7) { retval(6) = errmsg; return retval; } else { errmsg = "__datetime__: " + errmsg; error ("%s", errmsg.c_str ()); } } } for (int i = 0; i < n; i++) { RowVector tmp(3); tmp(0) = YMD(i,0); tmp(1) = YMD(i,1); tmp(2) = YMD(i,2); double out = check_nan_inf (tmp); if (isnan (out)) { Y(i) = NAN; M(i) = NAN; D(i) = NAN; h(i) = NAN; m(i) = NAN; s(i) = NAN; } else if (isinf (out)) { Y(i) = out; M(i) = out; D(i) = out; h(i) = out; m(i) = out; s(i) = out; } else { // Fix years / months int tmp_Y = (int)YMD(i,0) + ((int)YMD(i,1) / 12); int tmp_M = (int)YMD(i,1) % 12; int tmp_D = (int)YMD(i,2); // Add/subtract months and days accordingly year_month_day ymd = year(tmp_Y)/(int)0/(int)0; if (tmp_M < 0) { ymd -= months{-tmp_M}; } else { ymd += months{tmp_M}; } if (tmp_D < 0) { ymd = sys_days{ymd} - days{-tmp_D}; } else { ymd = sys_days{ymd} + days{tmp_D}; } Y(i) = (int)ymd.year(); M(i) = (unsigned int)ymd.month(); D(i) = (unsigned int)ymd.day(); h(i) = 0; m(i) = 0; s(i) = 0; } } } else if (args(0).columns () == 6) { Matrix YMDhms = args(0).matrix_value (); for (int i = 0; i < n; i++) { if (round (YMDhms(i,0)) != YMDhms(i,0) || round (YMDhms(i,1)) != YMDhms(i,1) || round (YMDhms(i,2)) != YMDhms(i,2) || round (YMDhms(i,3)) != YMDhms(i,3) || round (YMDhms(i,4)) != YMDhms(i,4)) { string errmsg = "Year, Month, Day, Hour, and Minute"; errmsg += " components must be integer values."; if (nargout == 7) { retval(6) = errmsg; return retval; } else { errmsg = "__datetime__: " + errmsg; error ("%s", errmsg.c_str ()); } } } for (int i = 0; i < n; i++) { RowVector tmp(6); tmp(0) = YMDhms(i,0); tmp(1) = YMDhms(i,1); tmp(2) = YMDhms(i,2); tmp(3) = YMDhms(i,3); tmp(4) = YMDhms(i,4); tmp(5) = YMDhms(i,5); double out = check_nan_inf (tmp); if (isnan (out)) { Y(i) = NAN; M(i) = NAN; D(i) = NAN; h(i) = NAN; m(i) = NAN; s(i) = NAN; } else if (isinf (out)) { Y(i) = out; M(i) = out; D(i) = out; h(i) = out; m(i) = out; s(i) = out; } else { // Aggregate hours, minutes, and seconds into seconds, calculate extra // days for later and retrieve remaining hours, minutes, and seconds double time_sec = YMDhms(i,3) * 3600 + YMDhms(i,4) * 60 + YMDhms(i,5); int extra_days = (int)time_sec / 86400; time_sec = remainder (time_sec, 86400); RowVector OUT = seconds2vector (time_sec, precision); h(i) = OUT(3); m(i) = OUT(4); s(i) = OUT(5); // Fix years / months int tmp_Y = (int)YMDhms(i,0) + ((int)YMDhms(i,1) / 12); int tmp_M = (int)YMDhms(i,1) % 12; int tmp_D = (int)YMDhms(i,2) + (int)extra_days; // Add/subtract months and days accordingly year_month_day ymd = year(tmp_Y)/(int)0/(int)0; if (tmp_M < 0) { ymd -= months{-tmp_M}; } else { ymd += months{tmp_M}; } if (tmp_D < 0) { ymd = sys_days{ymd} - days{-tmp_D}; } else { ymd = sys_days{ymd} + days{tmp_D}; } Y(i) = (int)ymd.year(); M(i) = (unsigned int)ymd.month(); D(i) = (unsigned int)ymd.day(); } } } retval(0) = Y; retval(1) = M; retval(2) = D; retval(3) = h; retval(4) = m; retval(5) = s; return retval; } // Handle 3, 6, or 7 input data arguments if (nargin == 3 || nargin == 6 || nargin == 7) { // Check all input data arguments are numeric for (int i = 1; i < nargin; i++) { if (! args(i).isnumeric ()) { string errmsg = "input data arguments must be numeric."; if (nargout == 7) { retval(6) = errmsg; return retval; } else { errmsg = "__datetime__: " + errmsg; error ("%s", errmsg.c_str ()); } } } // Determine the common size: scalar arguments broadcast to the size of // the non-scalar arguments, which must all share a common size. dim_vector sz (1, 1); bool sized = false; for (int i = 0; i < nargin; i++) { if (args(i).is_scalar_type ()) { continue; } dim_vector sz1 = args(i).dims (); if (! sized) { sz = sz1; sized = true; } else if (sz1 != sz) { string errmsg = "numeric data input arguments"; errmsg += " must be of common size or scalars."; if (nargout == 7) { retval(6) = errmsg; return retval; } else { errmsg = "__datetime__: " + errmsg; error ("%s", errmsg.c_str ()); } } } // Initialize output vectors with input data NDArray Y = expand_input (sz, args(0)); // Years (numeric) NDArray M = expand_input (sz, args(1)); // Months (numeric) NDArray D = expand_input (sz, args(2)); // Days (numeric) NDArray h(sz, 0); // Hours (numeric) NDArray m(sz, 0); // Minutes (numeric) NDArray s(sz, 0); // Seconds (numeric) NDArray x(sz, 0); // Milliseconds (internal use) if (nargin > 3) { h = expand_input (sz, args(3)); m = expand_input (sz, args(4)); s = expand_input (sz, args(5)); } if (nargin > 6) { x = expand_input (sz, args(6)); } // Beyond this point, all input data have common size // 'ConvertTo','posixtime' returns POSIX seconds (double) instead of the six // canonical components. The wall-clock components are interpreted in // 'timezone' (pass 'TimeZone','UTC' for unzoned datetimes so the serial is // free of any system-zone DST offset), Not-A-Time maps to NaN, and infinite // datetimes preserve their sign. if (convertTo == "posixtime") { NDArray S(sz, 0); for (int i = 0; i < sz.numel (); i++) { RowVector tmp(7); tmp(0) = Y(i); tmp(1) = M(i); tmp(2) = D(i); tmp(3) = h(i); tmp(4) = m(i); tmp(5) = s(i); tmp(6) = x(i); double chk = check_nan_inf (tmp); if (isnan (chk)) { S(i) = NAN; } else if (isinf (chk)) { S(i) = chk; } else { auto sys = components2sys (Y(i), M(i), D(i), h(i), m(i), s(i), x(i), timezone, precision); S(i) = (double) sys.time_since_epoch ().count () / 1000000.0; } } retval(0) = S; return retval; } for (int i = 0; i < sz.numel (); i++) { RowVector tmp(7); tmp(0) = Y(i); tmp(1) = M(i); tmp(2) = D(i); tmp(3) = h(i); tmp(4) = m(i); tmp(5) = s(i); tmp(6) = x(i); double out = check_nan_inf (tmp); if (isnan (out)) { Y(i) = NAN; M(i) = NAN; D(i) = NAN; h(i) = NAN; m(i) = NAN; s(i) = NAN; x(i) = NAN; } else if (isinf (out)) { Y(i) = out; M(i) = out; D(i) = out; h(i) = out; m(i) = out; s(i) = out; x(i) = out; } else { // Aggregate hours, minutes, seconds, and milliseconds into seconds, // calculate extra days to add later and map remaining hours, minutes, // and seconds to a local_time variable double time_sec = h(i) * 3600 + m(i) * 60 + s(i) + x(i) / 1000; int extra_days = (int)time_sec / 86400; // Subtract one day for negative time, because it goes missing // from the conversion in 'seconds2vector' below. if (time_sec < 0) { extra_days -= 1; } time_sec = remainder (time_sec, 86400); RowVector HMS = seconds2vector (time_sec, precision); int tmp_h = (int)HMS(3); int tmp_m = (int)HMS(4); int tmp_s = (int)HMS(5); double pr = 1000000; if (precision == "milliseconds") { pr = 1000; } double tmp_frac_sec = HMS(5) - tmp_s; int tmp_micro = (int)(round (tmp_frac_sec * pr)); // Fix years / months int tmp_Y = (int)Y(i) + ((int)M(i) / 12); int tmp_M = (int)M(i) % 12; int tmp_D = (int)D(i) + (int)extra_days; // Add/subtract months and days accordingly year_month_day ymd = year(tmp_Y)/(int)0/(int)0; if (tmp_M < 0) { ymd -= months{-tmp_M}; } else { ymd += months{tmp_M}; } if (tmp_D < 0) { ymd = sys_days{ymd} - days{-tmp_D}; } else { ymd = sys_days{ymd} + days{tmp_D}; } // Add time to date auto datetime = local_days{ymd} + chrono::hours{tmp_h} + chrono::minutes{tmp_m} + chrono::seconds{tmp_s} + chrono::microseconds{tmp_micro}; // Make timezone conversion auto in = make_zoned (timezone, datetime); auto out = make_zoned (to_tzone, in.get_sys_time ()); RowVector OUT = tz2vector (out, precision); Y(i) = OUT(0); M(i) = OUT(1); D(i) = OUT(2); h(i) = OUT(3); m(i) = OUT(4); s(i) = OUT(5); } } // Return output arguments retval(0) = Y; retval(1) = M; retval(2) = D; retval(3) = h; retval(4) = m; retval(5) = s; return retval; } else { string errmsg = "numeric input data arguments must be"; errmsg += " three, six, or seven separate arrays."; if (nargout == 7) { retval(6) = errmsg; return retval; } else { errmsg = "__datetime__: " + errmsg; error ("%s", errmsg.c_str ()); } } // Should never reach this point! Exit safely, just in case. return retval; } pr0m1th3as-datatypes-9c9a8d3/src/__nkeyHash__.cc000066400000000000000000000076021522766574100215260ustar00rootroot00000000000000/* Copyright (C) 2025-2026 Andreas Bertsatos This file is part of the datatypes package for GNU Octave. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, see . */ #define FNV1A64_PRIME 0x00000100000001b3 #include #include #include using namespace std; inline bool isLittleEndian() { unsigned int value = 0x01020304; unsigned char bytes[4] = {}; std::memcpy (bytes, &value, sizeof (value)); return bytes[0] == 0x04; } static inline constexpr uint64_t fnv1a64 (const char* buf, size_t len, uint64_t out) { for (size_t i = 0; i < len; i++) { // default char signedness depends on architecture, for signed for // backwards compatibility const signed char b = buf[i]; out = (out ^ b) * FNV1A64_PRIME; } return out; } static inline constexpr uint64_t fnv1a64 (const char* buf, size_t len, size_t nbytes, uint64_t out) { for (size_t i = 0; i < len; i++) { // reverse bytes for big endian systems size_t dv = i / nbytes; size_t md = i % nbytes; size_t ii = dv * nbytes + nbytes - md - 1; const signed char b = buf[ii]; out = (out ^ b) * FNV1A64_PRIME; } return out; } namespace /* tests */ { constexpr std::array testData{0, 1, char(0x80), 0}; static_assert(0 == fnv1a64(testData.data(), 0, 0)); static_assert(0x123 == fnv1a64(testData.data(), 0, 0x123)); static_assert(0 == fnv1a64(testData.data(), 1, 0)); static_assert(FNV1A64_PRIME == fnv1a64(testData.data(), 1, 1)); static_assert(FNV1A64_PRIME == fnv1a64(testData.data(), 2, 0)); } DEFUN_DLD (__nkeyHash__, args, nargout, "-*- texinfo -*-\n\ @deftypefn {} {@var{uint64} =} __nkeyHash__ (@var{x})\n\ @deftypefnx {} {@var{uint64} =} __nkeyHash__ (@var{x}, @var{FNV1A64_BASE})\n\ \n\ \n\ Fowler–Noll–Vo hash key for a numeric vector. \n\ \n\ This is a helper function for @qcode{keyHash} methods of `datatypes`' classes. \ Do NOT use this function directly. \n\ \n\ @end deftypefn") { octave_uint64 base; // Validate input if (args.length () < 1) { error ("__nkeyHash__: too few input arguments."); } if (! (args(0).isnumeric () || args(0).islogical ())) { error ("__nkeyHash__: X must be either numeric or logical."); } if (args(0).is_range ()) { error ("__nkeyHash__: X cannot be a range."); } // Get or assign a base value if (args.length () > 1) { base = args(1).uint64_scalar_value (); } else { base = 0xcbf29ce484222325; // default FNV1A64_BASE } // Cast numeric input to const char uint32_t len = args(0).byte_size (); const void *in = args(0).mex_get_data (); const char *buf = static_cast(in); // Generate the hash key octave_value_list retval (nargout); if (isLittleEndian ()) { octave_uint64 out = fnv1a64 (buf, len, base); retval(0) = out; } else { uint32_t nbytes = 1; if (args(0).is_int16_type () || args(0).is_uint16_type ()) { nbytes = 2; } else if (args(0).is_int32_type () || args(0).is_uint32_type () || args(0).is_single_type ()) { nbytes = 4; } else if (args(0).is_int64_type () || args(0).is_uint64_type () || args(0).is_double_type ()) { nbytes = 8; } octave_uint64 out = fnv1a64 (buf, len, nbytes, base); retval(0) = out; } return retval; } pr0m1th3as-datatypes-9c9a8d3/src/__ods2table__.cc000066400000000000000000000330631522766574100216330ustar00rootroot00000000000000/* Copyright (C) 2026 Andreas Bertsatos This file is part of the datatypes package for GNU Octave. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, see . */ #include #include #include #include #include #include #include #include #include #include #include "pugixml.hpp" #include "miniz.h" using namespace std; // A compressed '.ods' begins with the ZIP local-file-header magic "PK\x03\x04". static bool is_zip_file (const string &file) { ifstream f (file.c_str (), ios::binary); char m[4] = {0}; f.read (m, 4); return (m[0] == 'P' && m[1] == 'K' && m[2] == 3 && m[3] == 4); } // Parse a numeric string the same way __csv2table__ does: a plain integer of // 16 or more digits is stored losslessly as a 64-bit integer, everything else // becomes a double. static octave_value parse_float (const string &word) { const char *word_str = word.c_str (); char *err; double val = strtod (word_str, &err); size_t start = (! word.empty () && (word[0] == '+' || word[0] == '-')) ? 1 : 0; bool is_int = (word.length () > start); size_t ndig = 0; for (size_t i = start; i < word.length (); i++) { if (word[i] < '0' || word[i] > '9') { is_int = false; break; } ndig++; } if (is_int && ndig >= 16) { errno = 0; if (word[0] == '-') { long long llv = strtoll (word_str, &err, 10); return (errno == 0) ? octave_value (octave_int64 (llv)) : octave_value (val); } else { unsigned long long ullv = strtoull (word_str, &err, 10); return (errno == 0) ? octave_value (octave_uint64 (ullv)) : octave_value (val); } } return octave_value (val); } // Concatenated text of a cell's children. static string cell_text (const pugi::xml_node &cell) { string out; for (pugi::xml_node p = cell.child ("text:p"); p; p = p.next_sibling ("text:p")) { if (! out.empty ()) out += "\n"; out += p.text ().as_string (); } return out; } // Parse one into a value grid and a parallel per-cell value-type // grid. 'typed' selects data-sheet decoding (float/boolean/date/time/string, // with missing cells left empty); when false every cell is returned as text // (used for the metadata sheet). Row/column repeat counts are honoured so the // grid stays rectangular. static void read_sheet (const pugi::xml_node &table, Cell &data, Cell &vtype, bool typed, bool trim) { // First pass: collect rows as vectors of (value-type, raw-string) and find // the maximum column count. vector>> grid; size_t maxcols = 0; // When 'trim' is set (foreign spreadsheets with no metadata sheet), trailing // empty cells -- blank cells padded out with large 'number-columns-repeated' // counts -- and trailing empty rows are dropped so the grid spans only the // sheet's used bounding box; leading and interior empties are preserved as // blank cells, keeping absolute A1 coordinates intact for the caller's // 'Range' handling. When 'trim' is clear (our house files, whose metadata // sheet makes the grid authoritative), every cell is materialised verbatim -- // a trailing row of all-missing values is real data and must be kept. long pending_rows = 0; // empty rows not yet flushed (may be trailing) for (pugi::xml_node row = table.child ("table:table-row"); row; row = row.next_sibling ("table:table-row")) { long rrep = row.attribute ("table:number-rows-repeated").as_int (1); if (rrep < 1) rrep = 1; vector> cols; long pending_cols = 0; // empty cells not yet flushed (may be trailing) for (pugi::xml_node cell = row.child ("table:table-cell"); cell; cell = cell.next_sibling ("table:table-cell")) { long crep = cell.attribute ("table:number-columns-repeated").as_int (1); if (crep < 1) crep = 1; string vt = cell.attribute ("office:value-type").as_string (); string raw; if (vt == "float" || vt == "percentage" || vt == "currency") raw = cell.attribute ("office:value").as_string (); else if (vt == "boolean") raw = cell.attribute ("office:boolean-value").as_string (); else if (vt == "date") raw = cell.attribute ("office:date-value").as_string (); else if (vt == "time") raw = cell.attribute ("office:time-value").as_string (); else // string / empty raw = cell_text (cell); if (trim && vt.empty () && raw.empty ()) // blank: defer (may be trailing) { pending_cols += crep; } else // content cell: flush deferred blanks first { for (long k = 0; k < pending_cols; k++) cols.push_back (make_pair (string (), string ())); pending_cols = 0; for (long k = 0; k < crep; k++) cols.push_back (make_pair (vt, raw)); } } // A row with no content cells is empty; defer it in case it is trailing. if (trim && cols.empty ()) { pending_rows += rrep; } else { for (long k = 0; k < pending_rows; k++) grid.push_back (vector> ()); pending_rows = 0; if (cols.size () > maxcols) maxcols = cols.size (); for (long k = 0; k < rrep; k++) grid.push_back (cols); } } size_t nrows = grid.size (); data = Cell (nrows, maxcols); vtype = Cell (nrows, maxcols); for (size_t r = 0; r < nrows; r++) { for (size_t c = 0; c < maxcols; c++) { string vt = (c < grid[r].size ()) ? grid[r][c].first : string (); string raw = (c < grid[r].size ()) ? grid[r][c].second : string (); vtype(r, c) = vt; if (! typed) // metadata sheet: text only { data(r, c) = raw; } else if (vt == "float" || vt == "percentage" || vt == "currency") { data(r, c) = parse_float (raw); } else if (vt == "boolean") { data(r, c) = octave_value (raw == "true"); } else if (vt.empty ()) // missing / blank cell { data(r, c) = Matrix (0, 0); } else // date / time / string -> raw text { data(r, c) = raw; } } } } // If the metadata grid is sectioned (a multi-sheet workbook written by // 'struct2ods' precedes each table's metadata block with a "## Sheet: " // marker row), return only the section whose marker matches 'sel'. An // unsectioned grid (single-sheet house file, no markers) is returned // unchanged; a sectioned grid with no matching marker yields an empty grid. static Cell meta_section (const Cell &meta, const string &sel) { octave_idx_type nr = meta.rows (); octave_idx_type nc = meta.columns (); const string pfx = "## Sheet: "; vector marks; vector mnames; for (octave_idx_type r = 0; r < nr; r++) { if (meta(r, 0).is_string ()) { string s = meta(r, 0).string_value (); if (s.size () >= pfx.size () && s.compare (0, pfx.size (), pfx) == 0) { marks.push_back (r); mnames.push_back (s.substr (pfx.size ())); } } } if (marks.empty ()) return meta; // unsectioned: legacy single-sheet file for (size_t i = 0; i < marks.size (); i++) { if (mnames[i] == sel) { octave_idx_type r0 = marks[i] + 1; octave_idx_type r1 = (i + 1 < marks.size ()) ? marks[i + 1] : nr; // The shared metadata grid is padded to a common width; drop this // section's trailing all-empty columns so its width matches the sheet's // own variable count (otherwise the padding reads as extra variables). octave_idx_type used = 0; for (octave_idx_type c = 0; c < nc; c++) { bool any = false; for (octave_idx_type r = r0; r < r1 && ! any; r++) if (! meta(r, c).isempty ()) any = true; if (any) used = c + 1; } Cell out (r1 - r0, used); for (octave_idx_type r = r0; r < r1; r++) for (octave_idx_type c = 0; c < used; c++) out(r - r0, c) = meta(r, c); return out; } } return Cell (); // sectioned but no section for this sheet } DEFUN_DLD (__ods2table__, args, nargout, "-*- texinfo -*-\n \ @deftypefn {datatypes} {[@var{data}, @var{vtype}, @var{meta}] =} \ __ods2table__ (@var{file})\n\ @deftypefnx {datatypes} {[@var{data}, @var{vtype}, @var{meta}] =} \ __ods2table__ (@var{file}, @var{sheet})\n\ \n\ \n\ Barebone function for reading a flat ODS (@qcode{.fods}) file.\n\ \n\ This is a helper IO function for the @qcode{ods2table} function. Do NOT call \ it directly. \n\ \n\ @end deftypefn") { octave_value_list retval (4); // Keep the value-type, metadata, and sheet-name outputs defined even on the // error paths, so a caller's multi-output call never sees an undefined // return element. retval(1) = Cell (); retval(2) = Cell (); retval(3) = Cell (); if (args.length () < 1 || args.length () > 2) error ("__ods2table__: one or two input arguments are required."); string file = args(0).string_value (); // Optional sheet selector: a name (character vector) or a 1-based index over // the data sheets (the hidden '__datatypes_meta__' sheet is never counted). string want_name; long want_index = 0; bool by_name = false, by_index = false; if (args.length () == 2 && ! args(1).isempty ()) { if (args(1).is_string ()) { want_name = args(1).string_value (); by_name = true; } else { want_index = static_cast (args(1).scalar_value ()); by_index = true; } } // A compressed '.ods' is a ZIP whose content.xml holds the spreadsheet; a // flat '.fods' is the XML document itself. pugi::xml_document doc; pugi::xml_parse_result res; if (is_zip_file (file)) { mz_zip_archive zip; memset (&zip, 0, sizeof (zip)); if (! mz_zip_reader_init_file (&zip, file.c_str (), 0)) { retval(0) = string ("cannot read '") + file + "' as a ZIP archive."; return retval; } size_t sz = 0; void *p = mz_zip_reader_extract_file_to_heap (&zip, "content.xml", &sz, 0); if (! p) { mz_zip_reader_end (&zip); retval(0) = string ("'") + file + "' has no 'content.xml' entry."; return retval; } res = doc.load_buffer (p, sz); mz_free (p); mz_zip_reader_end (&zip); } else { res = doc.load_file (file.c_str ()); } if (! res) { retval(0) = string ("cannot read '") + file + "': " + res.description () + "."; return retval; } // The spreadsheet lives under (flat) or // (packaged). pugi::xml_node root = doc.child ("office:document"); if (! root) root = doc.child ("office:document-content"); pugi::xml_node spreadsheet = root.child ("office:body").child ("office:spreadsheet"); if (! spreadsheet) { retval(0) = string ("'") + file + "' is not an OpenDocument spreadsheet."; return retval; } pugi::xml_node data_tbl, meta_tbl; long data_seen = 0; vector sheet_names; // all data sheet names, document order for (pugi::xml_node t = spreadsheet.child ("table:table"); t; t = t.next_sibling ("table:table")) { string name = t.attribute ("table:name").as_string (); if (name == "__datatypes_meta__") { meta_tbl = t; continue; } sheet_names.push_back (name); data_seen++; if (by_name) { if (name == want_name && ! data_tbl) data_tbl = t; } else if (by_index) { if (data_seen == want_index && ! data_tbl) data_tbl = t; } else if (! data_tbl) data_tbl = t; } // A requested sheet that does not exist is an error the caller reports. if ((by_name || by_index) && ! data_tbl) { if (by_name) retval(0) = string ("sheet '") + want_name + "' not found in '" + file + "'."; else retval(0) = string ("sheet index ") + std::to_string (want_index) + " out of range in '" + file + "'."; return retval; } Cell data, vtype, meta, meta_vt; // Trim the data grid to the used block only for foreign spreadsheets (no // metadata sheet); our own house files are authoritative and kept verbatim. if (data_tbl) read_sheet (data_tbl, data, vtype, true, ! meta_tbl); if (meta_tbl) read_sheet (meta_tbl, meta, meta_vt, false, false); // In a multi-sheet house workbook the metadata sheet is sectioned; keep only // the section belonging to the selected data sheet. if (meta.numel () > 0 && data_tbl) { string sel_name = data_tbl.attribute ("table:name").as_string (); meta = meta_section (meta, sel_name); } Cell names_out (1, sheet_names.size ()); for (size_t i = 0; i < sheet_names.size (); i++) names_out(i) = sheet_names[i]; retval(0) = data; retval(1) = vtype; retval(2) = meta; retval(3) = names_out; return retval; } pr0m1th3as-datatypes-9c9a8d3/src/__table2csv__.cc000066400000000000000000000123541522766574100216410ustar00rootroot00000000000000/* Copyright (C) 2025-2026 Andreas Bertsatos This file is part of the datatypes package for GNU Octave. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, see . */ #include #include #include #include #include #include #include using namespace std; DEFUN_DLD (__table2csv__, args, nargout, "-*- texinfo -*-\n \ @deftypefn {datatypes} {} __table2csv__ (@var{file}, @var{C})\n\ \n\ \n\ Barebone function for saving a cell array to a CSV file.\n\ \n\ This is a helper IO function for the @qcode{table2csv} method of the \ @qcode{table} class. Do NOT call it directly. \n\ \n\ @end deftypefn") { octave_value_list retval(nargout); // Check input arguments if (args.length() < 2 || args.length() > 5) { error ("__table2csv__: two to five input arguments are required."); } // Get input arguments string file = args(0).string_value (); Cell C = args(1).cell_value (); // Optional field delimiter (a single character); defaults to a comma. string sep = ","; if (args.length() >= 3) { string d = args(2).string_value (); if (d.length () != 1) { error ("__table2csv__: DELIMITER must be a single character."); } sep = d; } // Optional string-quoting mode: "all" (default), "minimal", or "none". string quote_mode = "all"; if (args.length() >= 4) { quote_mode = args(3).string_value (); } // Optional append flag: when true, rows are appended to an existing file // instead of truncating it (used by writetable's 'WriteMode', 'append'). bool append = false; if (args.length() >= 5) { append = args(4).bool_value (); } // Open CSV file ofstream fd(file.c_str (), append ? (ios::out | ios::app) : ios::out); if (! fd.is_open ()) { retval(0) = "cannot open file '" + file + "' for writing."; return retval; } // Initialize necessary variable; string word; string prot = "\""; int rows = C.rows (); int cols = C.columns (); // Process each row for (int row = 0; row < rows; row++) { // Process each element in row for (int col = 0; col < cols; col++) { word = ""; // Add separator if (col != 0) { word += sep; } // Integer scalar values are written exactly: a double cannot hold the // full 64-bit integer range, so bypass the double conversion below. if (C(row, col).isinteger () && C(row, col).numel () == 1) { ostringstream oss; if (C(row, col).is_uint8_type () || C(row, col).is_uint16_type () || C(row, col).is_uint32_type () || C(row, col).is_uint64_type ()) { oss << C(row, col).uint64_scalar_value ().value (); } else { oss << C(row, col).int64_scalar_value ().value (); } word += oss.str (); } // Real numeric values else if (C(row, col).is_real_scalar ()) { double value = C(row, col).double_value (); // Handle NaN first if (octave::math::isna (value)) { word += "NA"; } else if (isnan (value)) { word += "NaN"; } else { char tmp[32]; int cx = snprintf(tmp, 32, "%.15g", value); word += tmp; } } // String values else if (C(row, col).is_string ()) { string str = C(row, col).string_value (); // Decide whether to quote: always ("all"), never ("none"), or only // when the field contains the delimiter, a quote, or a line break // ("minimal"). bool quote; if (quote_mode == "none") { quote = false; } else if (quote_mode == "minimal") { quote = (str.find (sep) != string::npos || str.find (prot) != string::npos || str.find ('\n') != string::npos || str.find ('\r') != string::npos); } else { quote = true; } if (quote) { // Escape embedded quotes by doubling them (RFC 4180) so that // __csv2table__ can restore them when reading the field back. size_t pos = 0; while ((pos = str.find (prot, pos)) != string::npos) { str.insert (pos, prot); pos += 2; } str = prot + str + prot; } word += str; } // Everything else is forced to NaN else if (!C(row, col).isempty ()) { word += "NaN"; } fd << word; } // Add end of line fd << endl; } // Close file fd.close(); retval(0) = 0; return retval; } pr0m1th3as-datatypes-9c9a8d3/src/__table2ods__.cc000066400000000000000000000772741522766574100216470ustar00rootroot00000000000000/* Copyright (C) 2026 Andreas Bertsatos This file is part of the datatypes package for GNU Octave. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, see . */ #include #include #include #include #include #include #include #include #include "pugixml.hpp" #include "miniz.h" using namespace std; static const char *ODS_MIMETYPE = "application/vnd.oasis.opendocument.spreadsheet"; // Write a numeric : integer scalars keep their exact digits // (a double cannot hold the full 64-bit range; the reader restores the exact // class from the metadata sheet), everything else prints with %.15g. NaN and // empty produce a bare, value-less cell (i.e. a missing value). static void write_float (pugi::xml_node &cell, const octave_value &ov) { if (ov.isinteger () && ov.numel () == 1) { ostringstream oss; if (ov.is_uint8_type () || ov.is_uint16_type () || ov.is_uint32_type () || ov.is_uint64_type ()) oss << ov.uint64_scalar_value ().value (); else oss << ov.int64_scalar_value ().value (); string s = oss.str (); cell.append_attribute ("office:value-type") = "float"; cell.append_attribute ("office:value") = s.c_str (); cell.append_child ("text:p").text ().set (s.c_str ()); } else if (ov.is_real_scalar ()) { double value = ov.double_value (); if (isnan (value)) return; // NaN -> missing (bare cell) char tmp[32]; snprintf (tmp, 32, "%.15g", value); cell.append_attribute ("office:value-type") = "float"; cell.append_attribute ("office:value") = tmp; cell.append_child ("text:p").text ().set (tmp); } } // Emit one under 'row' for the value 'ov', encoded per the // ODS value-type 'vt' ("float" | "boolean" | "date" | "time" | "string"). // datetime/duration arrive from the .m layer already formatted as ISO 8601 // strings (office:date-value / office:time-value); an empty string is a // missing value. The "string" branch inspects the value so that generic // 'cell' columns still map numbers to float and text to string. static void write_cell (pugi::xml_node &row, const octave_value &ov, const string &vt) { pugi::xml_node cell = row.append_child ("table:table-cell"); // An empty value is a missing cell, EXCEPT an empty char, which is a real // empty string and must be preserved (as an empty string cell) below. if (ov.isempty () && ! ov.is_string ()) return; if (vt == "float") { write_float (cell, ov); } else if (vt == "boolean") { if (ov.is_real_scalar ()) { double value = ov.double_value (); if (isnan (value)) return; bool b = (value != 0); cell.append_attribute ("office:value-type") = "boolean"; cell.append_attribute ("office:boolean-value") = b ? "true" : "false"; cell.append_child ("text:p").text ().set (b ? "TRUE" : "FALSE"); } } else if (vt == "date") { if (ov.is_string ()) { string str = ov.string_value (); if (str.empty ()) return; // NaT -> missing (bare cell) // Cells at exactly midnight display as a plain date, the rest as a // date-and-time value. size_t tpos = str.find ('T'); bool date_only = (tpos != string::npos && str.substr (tpos + 1) == "00:00:00"); cell.append_attribute ("table:style-name") = date_only ? "ce_date" : "ce_datetime"; cell.append_attribute ("office:value-type") = "date"; cell.append_attribute ("office:date-value") = str.c_str (); cell.append_child ("text:p").text ().set (str.c_str ()); } } else if (vt == "time") { if (ov.is_string ()) { string str = ov.string_value (); if (str.empty ()) return; // missing -> bare cell cell.append_attribute ("table:style-name") = "ce_time"; cell.append_attribute ("office:value-type") = "time"; cell.append_attribute ("office:time-value") = str.c_str (); cell.append_child ("text:p").text ().set (str.c_str ()); } } else // "string" / "cell" (generic) { if (ov.is_string ()) { string str = ov.string_value (); cell.append_attribute ("office:value-type") = "string"; cell.append_child ("text:p").text ().set (str.c_str ()); } else // numbers in a generic cell column { write_float (cell, ov); } } } // Estimate the number of characters a cell displays as, used to size columns. // A missing cell is 0; date/time cells use their displayed (formatted) width // rather than the longer ISO string. static size_t display_len (const octave_value &ov, const string &vt) { if (ov.isempty () && ! ov.is_string ()) return 0; if (vt == "boolean") return 5; // "FALSE" if (vt == "float") { if (ov.isinteger () && ov.numel () == 1) { ostringstream oss; if (ov.is_uint8_type () || ov.is_uint16_type () || ov.is_uint32_type () || ov.is_uint64_type ()) oss << ov.uint64_scalar_value ().value (); else oss << ov.int64_scalar_value ().value (); return oss.str ().size (); } if (ov.is_real_scalar ()) { double v = ov.double_value (); if (isnan (v)) return 0; char tmp[32]; return snprintf (tmp, 32, "%.15g", v); } return 0; } if (vt == "date" && ov.is_string ()) { string s = ov.string_value (); size_t tp = s.find ('T'); if (tp != string::npos && s.substr (tp + 1) == "00:00:00") return 10; // date only: YYYY-MM-DD return 19; // date and time: YYYY-MM-DD HH:MM:SS } if (ov.is_string ()) // time (ISO) / string return ov.string_value ().size (); return 0; } // Estimate a display width (in cm) for each data column from its widest cell. // The metrics are approximate (we lack the viewer's font), erring wide so that // dates and numbers are not clipped to '###'. static vector compute_col_widths (const Cell &C, const Cell &vtype, const Cell &header) { octave_idx_type rows = C.rows (); octave_idx_type cols = C.columns (); bool have_vt = (vtype.numel () == cols); bool have_hd = (header.numel () == cols); vector widths (cols, 0.0); for (octave_idx_type c = 0; c < cols; c++) { size_t maxlen = 0; if (have_hd && header(c).is_string ()) maxlen = header(c).string_value ().size (); for (octave_idx_type r = 0; r < rows; r++) { string vt = have_vt ? vtype(c).string_value () : string ("string"); size_t l = display_len (C(r, c), vt); if (l > maxlen) maxlen = l; } double cm = maxlen * 0.21 + 0.35; // ~0.21 cm/char plus padding if (cm < 0.75) cm = 0.75; if (cm > 12.0) cm = 12.0; widths[c] = cm; } return widths; } // Emit a leading empty cell spanning 'coff' columns (a 'Range' column offset). static void pad_cols (pugi::xml_node &row, octave_idx_type coff) { if (coff <= 0) return; pugi::xml_node pad = row.append_child ("table:table-cell"); if (coff > 1) pad.append_attribute ("table:number-columns-repeated") = (int) coff; } // Append the data rows of grid 'C' (per-column value-types 'vtype') to an // existing table node. Used both for a fresh sheet and when appending rows to // an existing sheet ('WriteMode', 'append'). An empty 'vtype' treats every // cell as a "string" (the text-only metadata sheet). static void append_data_rows (pugi::xml_node &table, const Cell &C, const Cell &vtype, octave_idx_type coff = 0) { octave_idx_type rows = C.rows (); octave_idx_type cols = C.columns (); bool have_vt = (vtype.numel () == cols); for (octave_idx_type r = 0; r < rows; r++) { pugi::xml_node row = table.append_child ("table:table-row"); pad_cols (row, coff); for (octave_idx_type c = 0; c < cols; c++) { string vt = have_vt ? vtype(c).string_value () : string ("string"); write_cell (row, C(r, c), vt); } } } // Populate an (already named) table node: optional "coN" column-width styles, // a 'Range' row/column offset, an optional visible header row of variable // names, then the data rows. static void write_sheet_body (pugi::xml_node &table, const Cell &C, const Cell &vtype, bool emit_cols, const Cell &header, octave_idx_type roff, octave_idx_type coff) { octave_idx_type cols = C.columns (); if (emit_cols) { // Leading empty column definition(s) for a 'Range' column offset. if (coff > 0) { pugi::xml_node col = table.append_child ("table:table-column"); if (coff > 1) col.append_attribute ("table:number-columns-repeated") = (int) coff; } for (octave_idx_type c = 0; c < cols; c++) { char nm[16]; snprintf (nm, 16, "co%d", (int) (c + 1)); table.append_child ("table:table-column") .append_attribute ("table:style-name") = nm; } } // Leading empty rows for a 'Range' row offset. if (roff > 0) { pugi::xml_node row = table.append_child ("table:table-row"); if (roff > 1) row.append_attribute ("table:number-rows-repeated") = (int) roff; } // Optional visible header row of variable names (text cells) if (header.numel () == cols && cols > 0) { pugi::xml_node row = table.append_child ("table:table-row"); pad_cols (row, coff); for (octave_idx_type c = 0; c < cols; c++) { pugi::xml_node cell = row.append_child ("table:table-cell"); string h = header(c).string_value (); cell.append_attribute ("office:value-type") = "string"; cell.append_child ("text:p").text ().set (h.c_str ()); } } append_data_rows (table, C, vtype, coff); } // Write a full element named 'name' into 'spreadsheet' from the // cell grid 'C'. See 'write_sheet_body' for the emit_cols/header/roff/coff // arguments. static void write_sheet (pugi::xml_node &spreadsheet, const string &name, const Cell &C, const Cell &vtype, const char *style = 0, bool emit_cols = false, const Cell &header = Cell (), octave_idx_type roff = 0, octave_idx_type coff = 0) { pugi::xml_node table = spreadsheet.append_child ("table:table"); table.append_attribute ("table:name") = name.c_str (); if (style) table.append_attribute ("table:style-name") = style; write_sheet_body (table, C, vtype, emit_cols, header, roff, coff); } // Declare the OpenDocument namespaces and version common to the flat document // and the packaged content.xml. static void add_office_namespaces (pugi::xml_node &root) { root.append_attribute ("xmlns:office") = "urn:oasis:names:tc:opendocument:xmlns:office:1.0"; root.append_attribute ("xmlns:table") = "urn:oasis:names:tc:opendocument:xmlns:table:1.0"; root.append_attribute ("xmlns:text") = "urn:oasis:names:tc:opendocument:xmlns:text:1.0"; root.append_attribute ("xmlns:style") = "urn:oasis:names:tc:opendocument:xmlns:style:1.0"; root.append_attribute ("xmlns:number") = "urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0"; root.append_attribute ("office:version") = "1.3"; } // Helpers for building the number-format elements of a data style. static void number_part (pugi::xml_node &st, const char *elem, const char *style_val) { pugi::xml_node n = st.append_child (elem); if (style_val) n.append_attribute ("number:style") = style_val; } static void number_text (pugi::xml_node &st, const char *txt) { st.append_child ("number:text").text ().set (txt); } // Declare the automatic styles: a table style that hides the // '__datatypes_meta__' sheet, plus number formats bound to cell styles so that // date and time cells display as formatted dates/times rather than as the // underlying serial numbers. static void fill_automatic_styles (pugi::xml_node &styles) { pugi::xml_node hid = styles.append_child ("style:style"); hid.append_attribute ("style:name") = "hidden_tbl"; hid.append_attribute ("style:family") = "table"; hid.append_child ("style:table-properties") .append_attribute ("table:display") = "false"; // Date format: YYYY-MM-DD pugi::xml_node nd = styles.append_child ("number:date-style"); nd.append_attribute ("style:name") = "N_date"; number_part (nd, "number:year", "long"); number_text (nd, "-"); number_part (nd, "number:month", "long"); number_text (nd, "-"); number_part (nd, "number:day", "long"); // Date-time format: YYYY-MM-DD HH:MM:SS pugi::xml_node ndt = styles.append_child ("number:date-style"); ndt.append_attribute ("style:name") = "N_datetime"; number_part (ndt, "number:year", "long"); number_text (ndt, "-"); number_part (ndt, "number:month", "long"); number_text (ndt, "-"); number_part (ndt, "number:day", "long"); number_text (ndt, " "); number_part (ndt, "number:hours", "long"); number_text (ndt, ":"); number_part (ndt, "number:minutes", "long"); number_text (ndt, ":"); number_part (ndt, "number:seconds", "long"); // Duration format: [HH]:MM:SS, hours not wrapped at 24 pugi::xml_node nt = styles.append_child ("number:time-style"); nt.append_attribute ("style:name") = "N_time"; nt.append_attribute ("number:truncate-on-overflow") = "false"; number_part (nt, "number:hours", "long"); number_text (nt, ":"); number_part (nt, "number:minutes", "long"); number_text (nt, ":"); number_part (nt, "number:seconds", "long"); // Cell styles binding each value type to its number format const char *ce[][2] = { {"ce_date", "N_date"}, {"ce_datetime", "N_datetime"}, {"ce_time", "N_time"} }; for (int i = 0; i < 3; i++) { pugi::xml_node st = styles.append_child ("style:style"); st.append_attribute ("style:name") = ce[i][0]; st.append_attribute ("style:family") = "table-cell"; st.append_attribute ("style:data-style-name") = ce[i][1]; } } // Create and populate the node under 'root'. static void add_automatic_styles (pugi::xml_node &root) { pugi::xml_node styles = root.append_child ("office:automatic-styles"); fill_automatic_styles (styles); } // Append a "coN" table-column style carrying an explicit width for each data // column, so viewers show columns wide enough for their content (the optimal- // width flag alone is not honoured on load). static void add_column_styles (pugi::xml_node &root, const vector &widths) { pugi::xml_node styles = root.child ("office:automatic-styles"); for (size_t i = 0; i < widths.size (); i++) { pugi::xml_node st = styles.append_child ("style:style"); char nm[16]; snprintf (nm, 16, "co%d", (int) (i + 1)); st.append_attribute ("style:name") = nm; st.append_attribute ("style:family") = "table-column"; pugi::xml_node cp = st.append_child ("style:table-column-properties"); char w[32]; snprintf (w, 32, "%.3fcm", widths[i]); cp.append_attribute ("style:column-width") = w; cp.append_attribute ("style:use-optimal-column-width") = "true"; } } // Append office:body -> office:spreadsheet with the data and metadata sheets. // The data sheet emits per-column width styles; the metadata sheet is tagged // with the hidden table style. static void build_spreadsheet (pugi::xml_node &root, const Cell &data, const Cell &vtype, const Cell &meta, const Cell &header, const string &sheetname, octave_idx_type roff, octave_idx_type coff) { pugi::xml_node body = root.append_child ("office:body"); pugi::xml_node spreadsheet = body.append_child ("office:spreadsheet"); write_sheet (spreadsheet, sheetname, data, vtype, 0, true, header, roff, coff); // The hidden metadata sheet is written only for the house format (non-empty // 'meta'); the MATLAB-compatible 'writetable' path passes an empty 'meta'. if (meta.numel () > 0) write_sheet (spreadsheet, "__datatypes_meta__", meta, Cell (), "hidden_tbl"); } // Append office:body -> office:spreadsheet with several data sheets (from the // struct array 'sheets', fields 'name'/'data'/'vtype') followed by one hidden, // sectioned metadata sheet. Used by 'struct2ods' for multi-sheet workbooks. // Data sheets carry no visible header row (house format: variable names live in // the metadata sheet) and no per-column width styles. static void build_multi_spreadsheet (pugi::xml_node &root, const octave_map &sheets, const Cell &meta) { pugi::xml_node body = root.append_child ("office:body"); pugi::xml_node spreadsheet = body.append_child ("office:spreadsheet"); Cell names = sheets.contents ("name"); Cell datas = sheets.contents ("data"); Cell vtypes = sheets.contents ("vtype"); octave_idx_type K = names.numel (); for (octave_idx_type k = 0; k < K; k++) write_sheet (spreadsheet, names(k).string_value (), datas(k).cell_value (), vtypes(k).cell_value (), 0, false, Cell (), 0, 0); if (meta.numel () > 0) write_sheet (spreadsheet, "__datatypes_meta__", meta, Cell (), "hidden_tbl"); } // Populate a document root with automatic styles and the spreadsheet body, // dispatching to the single-sheet or multi-sheet builder. Shared by the flat // and packaged output paths. static void populate_root (pugi::xml_node &root, bool multi, const octave_map &sheets, const Cell &data, const Cell &vtype, const Cell &meta, const Cell &header, const string &sheetname, octave_idx_type roff, octave_idx_type coff) { add_automatic_styles (root); if (multi) { build_multi_spreadsheet (root, sheets, meta); } else { add_column_styles (root, compute_col_widths (data, vtype, header)); build_spreadsheet (root, data, vtype, meta, header, sheetname, roff, coff); } } // Append a little-endian integer to a byte buffer. static void put16 (string &b, mz_uint16 v) { b.push_back ((char) (v & 0xff)); b.push_back ((char) ((v >> 8) & 0xff)); } static void put32 (string &b, mz_uint32 v) { for (int i = 0; i < 4; i++) b.push_back ((char) ((v >> (8 * i)) & 0xff)); } // One entry of the archive: name, raw payload, and whether to deflate it. struct ods_entry { string name; string data; bool compress; }; // Write a list of entries as a compliant '.ods' ZIP. miniz's own zip writer // sets the data-descriptor flag on every entry, which makes the required stored // 'mimetype' entry illegal (a stored entry has no discoverable end without its // sizes in the local header) and strict readers reject the package. We instead // emit the archive by hand -- mimetype first, stored, no data descriptor and no // extra field -- using miniz only for CRC-32 and raw deflate. The caller is // responsible for putting a stored 'mimetype' entry first. static string write_zip_entries (const string &file, const vector &entries) { int flags = tdefl_create_comp_flags_from_zip_params (MZ_DEFAULT_LEVEL, -15, MZ_DEFAULT_STRATEGY); string local, central; mz_uint16 count = 0; for (size_t i = 0; i < entries.size (); i++) { const ods_entry &e = entries[i]; mz_uint32 usize = (mz_uint32) e.data.size (); mz_uint32 crc = (mz_uint32) mz_crc32 (MZ_CRC32_INIT, (const unsigned char *) e.data.data (), e.data.size ()); string payload; mz_uint16 method = 0; if (e.compress && usize > 0) { size_t outlen = 0; void *cd = tdefl_compress_mem_to_heap (e.data.data (), e.data.size (), &outlen, flags); if (cd && outlen < e.data.size ()) { payload.assign ((const char *) cd, outlen); method = 8; } else payload = e.data; // incompressible: store it if (cd) mz_free (cd); } else payload = e.data; mz_uint32 csize = (mz_uint32) payload.size (); mz_uint32 offset = (mz_uint32) local.size (); mz_uint16 namelen = (mz_uint16) e.name.size (); // Local file header (no general-purpose flags, no extra field) put32 (local, 0x04034b50); put16 (local, 20); put16 (local, 0); put16 (local, method); put16 (local, 0); put16 (local, 0); put32 (local, crc); put32 (local, csize); put32 (local, usize); put16 (local, namelen); put16 (local, 0); local += e.name; local += payload; // Central directory header put32 (central, 0x02014b50); put16 (central, 20); put16 (central, 20); put16 (central, 0); put16 (central, method); put16 (central, 0); put16 (central, 0); put32 (central, crc); put32 (central, csize); put32 (central, usize); put16 (central, namelen); put16 (central, 0); put16 (central, 0); put16 (central, 0); put16 (central, 0); put32 (central, 0); put32 (central, offset); central += e.name; count++; } mz_uint32 cd_offset = (mz_uint32) local.size (); mz_uint32 cd_size = (mz_uint32) central.size (); string eocd; put32 (eocd, 0x06054b50); put16 (eocd, 0); put16 (eocd, 0); put16 (eocd, count); put16 (eocd, count); put32 (eocd, cd_size); put32 (eocd, cd_offset); put16 (eocd, 0); ofstream f (file.c_str (), ios::binary); if (! f.is_open ()) return "cannot open file '" + file + "' for writing."; f.write (local.data (), local.size ()); f.write (central.data (), central.size ()); f.write (eocd.data (), eocd.size ()); f.close (); if (! f) return "failed to write ODS archive '" + file + "'."; return ""; } // Package a freshly built content.xml as a minimal, compliant '.ods'. static string write_ods_zip (const string &file, const string &content) { string manifest = "\n" "\n" " \n" " \n" "\n"; vector entries; entries.push_back (ods_entry {"mimetype", string (ODS_MIMETYPE), false}); entries.push_back (ods_entry {"META-INF/manifest.xml", manifest, true}); entries.push_back (ods_entry {"content.xml", content, true}); return write_zip_entries (file, entries); } // Read-modify-write an existing spreadsheet: add or replace the sheet named // 'sheetname' (or, for 'append', append the data rows to it) while preserving // every other sheet and every other package part. 'writemode' is "append", // "overwritesheet", "inplace", or "" (default); all but "append" replace the // sheet's contents. Returns "" on success or an error message. static string merge_ods (const string &file, bool flat, const Cell &data, const Cell &vtype, const Cell &header, const string &sheetname, const string &writemode) { // --- Read the existing package --- vector entries; // packaged '.ods': every archive member size_t content_idx = 0; bool have_content = false; string content_xml; if (flat) { ifstream f (file.c_str (), ios::binary); if (! f.is_open ()) return "cannot read '" + file + "'."; ostringstream ss; ss << f.rdbuf (); content_xml = ss.str (); } else { mz_zip_archive zip; memset (&zip, 0, sizeof (zip)); if (! mz_zip_reader_init_file (&zip, file.c_str (), 0)) return "cannot read '" + file + "' as a ZIP archive."; mz_uint n = mz_zip_reader_get_num_files (&zip); for (mz_uint i = 0; i < n; i++) { mz_zip_archive_file_stat st; if (! mz_zip_reader_file_stat (&zip, i, &st)) continue; size_t sz = 0; void *p = mz_zip_reader_extract_to_heap (&zip, i, &sz, 0); string bytes = (p ? string ((const char *) p, sz) : string ()); if (p) mz_free (p); string nm = st.m_filename; if (nm == "content.xml") { content_idx = entries.size (); have_content = true; content_xml = bytes; } entries.push_back (ods_entry {nm, bytes, nm != "mimetype"}); } mz_zip_reader_end (&zip); if (! have_content) return "'" + file + "' has no 'content.xml' entry."; } // --- Parse content.xml and locate the spreadsheet --- pugi::xml_document doc; if (! doc.load_buffer (content_xml.data (), content_xml.size ())) return "cannot parse the spreadsheet in '" + file + "'."; pugi::xml_node root = doc.child ("office:document"); if (! root) root = doc.child ("office:document-content"); if (! root) return "'" + file + "' is not an OpenDocument spreadsheet."; pugi::xml_node spreadsheet = root.child ("office:body").child ("office:spreadsheet"); if (! spreadsheet) return "'" + file + "' is not an OpenDocument spreadsheet."; // --- Ensure our date/time cell styles are present (foreign files lack them; // without them date/time cells keep their value but lose formatting) --- pugi::xml_node styles = root.child ("office:automatic-styles"); bool have_styles = false; if (styles) for (pugi::xml_node s = styles.child ("style:style"); s; s = s.next_sibling ("style:style")) if (string (s.attribute ("style:name").as_string ()) == "ce_date") { have_styles = true; break; } if (! have_styles) { if (! styles) styles = root.insert_child_before ("office:automatic-styles", root.child ("office:body")); fill_automatic_styles (styles); } // --- Add, replace, or append to the target sheet --- pugi::xml_node sheet; for (pugi::xml_node t = spreadsheet.child ("table:table"); t; t = t.next_sibling ("table:table")) if (string (t.attribute ("table:name").as_string ()) == sheetname) { sheet = t; break; } if (writemode == "append" && sheet) { // Append the data rows below the sheet's existing content, no header. append_data_rows (sheet, data, vtype, 0); } else if (sheet) { // Replace the sheet's contents in place (default / overwritesheet / // inplace), keeping its position among the other sheets. while (sheet.first_child ()) sheet.remove_child (sheet.first_child ()); write_sheet_body (sheet, data, vtype, false, header, 0, 0); } else { // The sheet does not exist yet: add it after the existing sheets. write_sheet (spreadsheet, sheetname, data, vtype, 0, false, header, 0, 0); } // --- Serialize and write back --- ostringstream oss; doc.save (oss, " "); string newxml = oss.str (); if (flat) { ofstream f (file.c_str (), ios::binary); if (! f.is_open ()) return "cannot open file '" + file + "' for writing."; f.write (newxml.data (), newxml.size ()); f.close (); if (! f) return "failed to write '" + file + "'."; return ""; } entries[content_idx].data = newxml; // The stored 'mimetype' entry must come first. for (size_t i = 0; i < entries.size (); i++) if (entries[i].name == "mimetype") { entries[i].compress = false; if (i != 0) { ods_entry tmp = entries[0]; entries[0] = entries[i]; entries[i] = tmp; } break; } return write_zip_entries (file, entries); } DEFUN_DLD (__table2ods__, args, nargout, "-*- texinfo -*-\n \ @deftypefn {datatypes} {} __table2ods__ (@var{file}, @var{data}, \ @var{vtype}, @var{meta}, @var{flat})\n\ @deftypefnx {datatypes} {} __table2ods__ (@var{file}, @var{data}, \ @var{vtype}, @var{meta}, @var{flat}, @var{opts})\n\ \n\ \n\ Barebone function for saving a table to a flat (@qcode{.fods}) or compressed \ (@qcode{.ods}) OpenDocument spreadsheet file.\n\ \n\ This is a helper IO function for the @qcode{table2ods} method of the \ @qcode{table} class. Do NOT call it directly. \n\ \n\ @end deftypefn") { octave_value_list retval (nargout); if (args.length () != 5 && args.length () != 6) error ("__table2ods__: five or six input arguments are required."); string file = args(0).string_value (); Cell data = args(1).cell_value (); Cell vtype = args(2).cell_value (); Cell meta = args(3).cell_value (); bool flat = args(4).bool_value (); // Optional options struct (6th arg) with any of the fields: // 'header' -- visible header row of variable names (cellstr); when // present, an empty 'meta' suppresses the metadata sheet. // 'sheetname' -- name of the data sheet (default "Sheet1"). // 'roff'/'coff' -- leading empty row/column offsets for a 'Range' anchor. Cell header; string sheetname = "Sheet1"; octave_idx_type roff = 0, coff = 0; bool multi = false; octave_map sheets; bool merge = false; string writemode; if (args.length () == 6) { octave_scalar_map opts = args(5).scalar_map_value (); if (opts.isfield ("header")) header = opts.contents ("header").cell_value (); if (opts.isfield ("sheetname")) sheetname = opts.contents ("sheetname").string_value (); if (opts.isfield ("roff")) roff = (octave_idx_type) opts.contents ("roff").double_value (); if (opts.isfield ("coff")) coff = (octave_idx_type) opts.contents ("coff").double_value (); // Multi-sheet mode: 'sheets' is a struct array (fields name/data/vtype) and // 'meta' is the single sectioned metadata grid. Overrides the scalar args. if (opts.isfield ("sheets")) { multi = true; sheets = opts.contents ("sheets").map_value (); meta = opts.isfield ("meta") ? opts.contents ("meta").cell_value () : Cell (); } // Merge mode: add/replace/append to a sheet of an existing file in place. if (opts.isfield ("merge")) merge = opts.contents ("merge").bool_value (); if (opts.isfield ("writemode")) writemode = opts.contents ("writemode").string_value (); } // Read-modify-write an existing package rather than writing a fresh one. if (merge) { string msg = merge_ods (file, flat, data, vtype, header, sheetname, writemode); retval(0) = msg.empty () ? octave_value (0.0) : octave_value (msg); return retval; } pugi::xml_document doc; pugi::xml_node decl = doc.append_child (pugi::node_declaration); decl.append_attribute ("version") = "1.0"; decl.append_attribute ("encoding") = "UTF-8"; if (flat) { // A flat '.fods' is a single carrying the mimetype pugi::xml_node root = doc.append_child ("office:document"); add_office_namespaces (root); root.append_attribute ("office:mimetype") = ODS_MIMETYPE; populate_root (root, multi, sheets, data, vtype, meta, header, sheetname, roff, coff); if (! doc.save_file (file.c_str (), " ")) { retval(0) = "cannot open file '" + file + "' for writing."; return retval; } } else { // A compressed '.ods' packages a content.xml () pugi::xml_node root = doc.append_child ("office:document-content"); add_office_namespaces (root); populate_root (root, multi, sheets, data, vtype, meta, header, sheetname, roff, coff); ostringstream oss; doc.save (oss, " "); string msg = write_ods_zip (file, oss.str ()); if (! msg.empty ()) { retval(0) = msg; return retval; } } retval(0) = 0; return retval; } pr0m1th3as-datatypes-9c9a8d3/src/__table2xlsx__.cc000066400000000000000000000362501522766574100220450ustar00rootroot00000000000000/* Copyright (C) 2026 Andreas Bertsatos This file is part of the datatypes package for GNU Octave. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, see . */ #include #include #include #include #include #include #include #include #include "miniz.h" using namespace std; // Days from 1970-01-01 to civil date y-m-d (Howard Hinnant's algorithm). static long days_from_civil (long y, unsigned m, unsigned d) { y -= m <= 2; long era = (y >= 0 ? y : y - 399) / 400; unsigned yoe = (unsigned) (y - era * 400); unsigned doy = (153 * (m + (m > 2 ? -3 : 9)) + 2) / 5 + d - 1; unsigned doe = yoe * 365 + yoe / 4 - yoe / 100 + doy; return era * 146097 + (long) doe - 719468; } // Excel serial number (1900 date system) for an ISO datetime "Y-M-DThh:mm:ss". // 25569 is the serial of 1970-01-01. 'date_only' reports whether the time part // is exactly midnight, so the caller can pick a date vs date-and-time format. static bool iso_datetime_to_serial (const string &s, double &serial, bool &date_only) { int Y, Mo, D, h, mi; double sec; if (sscanf (s.c_str (), "%d-%d-%dT%d:%d:%lf", &Y, &Mo, &D, &h, &mi, &sec) != 6) return false; double day = (double) days_from_civil (Y, (unsigned) Mo, (unsigned) D) + 25569.0; serial = day + (h * 3600.0 + mi * 60.0 + sec) / 86400.0; date_only = (h == 0 && mi == 0 && sec == 0.0); return true; } // Excel time serial (fraction of a day) for an ISO 8601 duration "PTnHnMnS", // hours not wrapped at 24; a leading '-' negates it. static bool pt_duration_to_serial (const string &in, double &serial) { string s = in; bool neg = (! s.empty () && s[0] == '-'); if (neg) s.erase (0, 1); double H, M, S; if (sscanf (s.c_str (), "PT%lfH%lfM%lfS", &H, &M, &S) != 3) return false; serial = (H * 3600.0 + M * 60.0 + S) / 86400.0; if (neg) serial = -serial; return true; } // Spreadsheet column letters for a 1-based column index (1->A, 27->AA). static string col_letter (long n) { string s; while (n > 0) { long r = (n - 1) % 26; s = string (1, (char) ('A' + r)) + s; n = (n - 1) / 26; } return s; } // XML-escape text content / attribute values. static string xml_escape (const string &in) { string out; out.reserve (in.size ()); for (size_t i = 0; i < in.size (); i++) { char c = in[i]; switch (c) { case '&': out += "&"; break; case '<': out += "<"; break; case '>': out += ">"; break; case '"': out += """; break; default: out += c; } } return out; } // Emit one worksheet cell at (row, col) for value 'ov' of value-type 'vt'. // A missing/empty value produces no cell (a blank). Style indices match the // cellXfs written in styles.xml: 1 = date, 2 = date-and-time, 3 = duration. static void emit_cell (ostringstream &oss, long row, long col, const octave_value &ov, const string &vt) { if (ov.isempty () && ! ov.is_string ()) return; string ref = col_letter (col) + std::to_string (row); if (vt == "float") { if (ov.isinteger () && ov.numel () == 1) { ostringstream v; if (ov.is_uint8_type () || ov.is_uint16_type () || ov.is_uint32_type () || ov.is_uint64_type ()) v << ov.uint64_scalar_value ().value (); else v << ov.int64_scalar_value ().value (); oss << "" << v.str () << ""; } else if (ov.is_real_scalar ()) { double val = ov.double_value (); if (isnan (val)) return; char tmp[32]; snprintf (tmp, 32, "%.15g", val); oss << "" << tmp << ""; } } else if (vt == "boolean") { if (ov.is_real_scalar ()) { double val = ov.double_value (); if (isnan (val)) return; oss << "" << (val != 0 ? 1 : 0) << ""; } } else if (vt == "date") { if (ov.is_string ()) { double serial; bool date_only; string str = ov.string_value (); if (str.empty () || ! iso_datetime_to_serial (str, serial, date_only)) return; char tmp[32]; snprintf (tmp, 32, "%.11g", serial); oss << "" << tmp << ""; } } else if (vt == "time") { if (ov.is_string ()) { double serial; string str = ov.string_value (); if (str.empty () || ! pt_duration_to_serial (str, serial)) return; char tmp[32]; snprintf (tmp, 32, "%.11g", serial); oss << "" << tmp << ""; } } else // string / generic { if (ov.is_string ()) { oss << "" << xml_escape (ov.string_value ()) << ""; } else if (ov.is_real_scalar ()) { double val = ov.double_value (); if (isnan (val)) return; char tmp[32]; snprintf (tmp, 32, "%.15g", val); oss << "" << tmp << ""; } } } // Build the worksheet part from the data grid, per-column value types, an // optional header row of variable names, and a 'Range' row/column offset. static string build_worksheet (const Cell &data, const Cell &vtype, const Cell &header, long roff, long coff) { octave_idx_type rows = data.rows (); octave_idx_type cols = data.columns (); bool have_vt = (vtype.numel () == cols); bool have_hd = (header.numel () == cols && cols > 0); ostringstream oss; oss << "" << ""; long row = roff; // 1-based row number of the next row - 1 if (have_hd) { row++; oss << ""; for (octave_idx_type c = 0; c < cols; c++) emit_cell (oss, row, coff + c + 1, header(c), "string"); oss << ""; } for (octave_idx_type r = 0; r < rows; r++) { row++; oss << ""; for (octave_idx_type c = 0; c < cols; c++) { string vt = have_vt ? vtype(c).string_value () : string ("string"); emit_cell (oss, row, coff + c + 1, data(r, c), vt); } oss << ""; } oss << ""; return oss.str (); } // [Content_Types].xml with one worksheet override per sheet. 'macro' selects // the macro-enabled ('.xlsm') workbook content type. static string content_types_xml (bool macro, size_t nsheets) { string wb = macro ? "application/vnd.ms-excel.sheet.macroEnabled.main+xml" : "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"; ostringstream o; o << "" "" "" "" ""; for (size_t k = 1; k <= nsheets; k++) o << ""; o << ""; return o.str (); } static const char *ROOT_RELS = "" ""; // workbook.xml.rels: one worksheet relationship per sheet plus the styles part. static string workbook_rels_xml (size_t nsheets) { ostringstream o; o << "" ""; for (size_t k = 1; k <= nsheets; k++) o << ""; o << ""; return o.str (); } // Styles: three custom number formats (date, date-and-time, duration) bound to // cell formats s=1..3; s=0 is the default General format. static const char *STYLES_XML = "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" ""; // workbook.xml listing each sheet (sheetId 1..N, relationship rId1..rIdN). static string workbook_xml (const vector &names) { ostringstream o; o << "" ""; for (size_t k = 0; k < names.size (); k++) o << ""; o << ""; return o.str (); } DEFUN_DLD (__table2xlsx__, args, nargout, "-*- texinfo -*-\n \ @deftypefn {datatypes} {} __table2xlsx__ (@var{file}, @var{data}, \ @var{vtype}, @var{opts})\n\ \n\ \n\ Barebone function for writing an Office Open XML (@qcode{.xlsx} / \ @qcode{.xlsm}) spreadsheet. A single sheet is written from @var{data} / \ @var{vtype}; when @var{opts} carries a @qcode{sheets} struct array (fields \ @qcode{name}, @qcode{data}, @qcode{vtype}, @qcode{header}) a multi-sheet \ workbook is written instead.\n\ \n\ This is a helper IO function for @qcode{writetable} and @qcode{struct2xlsx}. \ Do NOT call it directly. \n\ \n\ @end deftypefn") { octave_value_list retval (nargout); if (args.length () != 3 && args.length () != 4) error ("__table2xlsx__: three or four input arguments are required."); string file = args(0).string_value (); Cell data = args(1).cell_value (); Cell vtype = args(2).cell_value (); Cell header; string sheetname = "Sheet1"; long roff = 0, coff = 0; bool macro = false; bool multi = false; octave_map sheets; if (args.length () == 4) { octave_scalar_map opts = args(3).scalar_map_value (); if (opts.isfield ("header")) header = opts.contents ("header").cell_value (); if (opts.isfield ("sheetname")) sheetname = opts.contents ("sheetname").string_value (); if (opts.isfield ("roff")) roff = (long) opts.contents ("roff").double_value (); if (opts.isfield ("coff")) coff = (long) opts.contents ("coff").double_value (); if (opts.isfield ("macro")) macro = opts.contents ("macro").bool_value (); if (opts.isfield ("sheets")) { multi = true; sheets = opts.contents ("sheets").map_value (); } } // Build the worksheet XML for each sheet. vector names, sheetxml; if (multi) { Cell nm = sheets.contents ("name"); Cell da = sheets.contents ("data"); Cell vt = sheets.contents ("vtype"); bool have_hd = sheets.isfield ("header"); Cell hd; if (have_hd) hd = sheets.contents ("header"); for (octave_idx_type k = 0; k < nm.numel (); k++) { names.push_back (nm(k).string_value ()); sheetxml.push_back (build_worksheet (da(k).cell_value (), vt(k).cell_value (), have_hd ? hd(k).cell_value () : Cell (), 0, 0)); } } else { names.push_back (sheetname); sheetxml.push_back (build_worksheet (data, vtype, header, roff, coff)); } string ctypes = content_types_xml (macro, names.size ()); string wb = workbook_xml (names); string wbrels = workbook_rels_xml (names.size ()); string root_rels = ROOT_RELS, styles = STYLES_XML; // Package the parts. XLSX has no stored-first-entry rule, so miniz's own ZIP // writer is fine here. mz_zip_archive zip; memset (&zip, 0, sizeof (zip)); remove (file.c_str ()); if (! mz_zip_writer_init_file (&zip, file.c_str (), 0)) { retval(0) = "cannot open file '" + file + "' for writing."; return retval; } bool ok = true; ok = ok && mz_zip_writer_add_mem (&zip, "[Content_Types].xml", ctypes.data (), ctypes.size (), MZ_DEFAULT_COMPRESSION); ok = ok && mz_zip_writer_add_mem (&zip, "_rels/.rels", root_rels.data (), root_rels.size (), MZ_DEFAULT_COMPRESSION); ok = ok && mz_zip_writer_add_mem (&zip, "xl/workbook.xml", wb.data (), wb.size (), MZ_DEFAULT_COMPRESSION); ok = ok && mz_zip_writer_add_mem (&zip, "xl/_rels/workbook.xml.rels", wbrels.data (), wbrels.size (), MZ_DEFAULT_COMPRESSION); ok = ok && mz_zip_writer_add_mem (&zip, "xl/styles.xml", styles.data (), styles.size (), MZ_DEFAULT_COMPRESSION); for (size_t k = 0; k < sheetxml.size () && ok; k++) { string pn = "xl/worksheets/sheet" + std::to_string (k + 1) + ".xml"; ok = ok && mz_zip_writer_add_mem (&zip, pn.c_str (), sheetxml[k].data (), sheetxml[k].size (), MZ_DEFAULT_COMPRESSION); } if (! ok || ! mz_zip_writer_finalize_archive (&zip)) { mz_zip_writer_end (&zip); remove (file.c_str ()); retval(0) = "failed to write Excel archive '" + file + "'."; return retval; } mz_zip_writer_end (&zip); retval(0) = 0; return retval; } pr0m1th3as-datatypes-9c9a8d3/src/__xlsx2table__.cc000066400000000000000000000310401522766574100220350ustar00rootroot00000000000000/* Copyright (C) 2026 Andreas Bertsatos This file is part of the datatypes package for GNU Octave. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, see . */ #include #include #include #include #include #include #include #include #include #include #include #include "pugixml.hpp" #include "miniz.h" using namespace std; // Civil date from a day count since 1970-01-01 (inverse of days_from_civil). static void civil_from_days (long z, long &y, unsigned &m, unsigned &d) { z += 719468; long era = (z >= 0 ? z : z - 146096) / 146097; unsigned doe = (unsigned) (z - era * 146097); unsigned yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365; long yy = (long) yoe + era * 400; unsigned doy = doe - (365 * yoe + yoe / 4 - yoe / 100); unsigned mp = (5 * doy + 2) / 153; d = doy - (153 * mp + 2) / 5 + 1; m = mp < 10 ? mp + 3 : mp - 9; y = yy + (m <= 2); } // Excel serial -> ISO datetime "Y-M-DThh:mm:ss" (whole-second resolution), the // form the .m layer's datetime parser expects. static string serial_to_iso_datetime (double serial) { double days = serial - 25569.0; long intdays = (long) floor (days); long secs = (long) llround ((days - intdays) * 86400.0); if (secs >= 86400) { secs -= 86400; intdays++; } long y; unsigned mo, d; civil_from_days (intdays, y, mo, d); long h = secs / 3600, mi = (secs % 3600) / 60, s = secs % 60; char buf[48]; snprintf (buf, 48, "%04ld-%02u-%02uT%02ld:%02ld:%02ld", y, mo, d, h, mi, s); return buf; } // Excel time serial -> ISO 8601 duration "PTnHnMnS" (hours not wrapped). static string serial_to_pt_duration (double serial) { bool neg = serial < 0; long isec = (long) llround (fabs (serial) * 86400.0); long H = isec / 3600, M = (isec % 3600) / 60, S = isec % 60; char buf[64]; snprintf (buf, 64, "%sPT%ldH%ldM%ldS", neg ? "-" : "", H, M, S); return buf; } // Parse an A1 cell reference into 1-based (row, col). static bool ref_to_rowcol (const string &ref, long &row, long &col) { size_t i = 0; col = 0; while (i < ref.size () && isalpha ((unsigned char) ref[i])) { col = col * 26 + (toupper ((unsigned char) ref[i]) - 'A' + 1); i++; } if (i == 0 || i >= ref.size ()) return false; row = atol (ref.c_str () + i); return (col > 0 && row > 0); } // Extract a named archive member as a string; empty if absent. static string read_member (mz_zip_archive &zip, const char *name) { size_t sz = 0; void *p = mz_zip_reader_extract_file_to_heap (&zip, name, &sz, 0); if (! p) return string (); string out ((const char *) p, sz); mz_free (p); return out; } // Concatenate the text of all descendants of a node (shared-string item). static string all_text (const pugi::xml_node &node) { string out; for (pugi::xml_node t = node.child ("t"); t; t = t.next_sibling ("t")) out += t.text ().as_string (); for (pugi::xml_node r = node.child ("r"); r; r = r.next_sibling ("r")) out += r.child ("t").text ().as_string (); return out; } // Classify a cell's style index into 'date', 'time', or "" (plain number), // using the number-format bound to that cell format. static string style_kind (long s, const vector &xf_fmt, const map &custom_fmt) { if (s < 0 || (size_t) s >= xf_fmt.size ()) return string (); int id = xf_fmt[s]; // Built-in date/time format ids. if ((id >= 14 && id <= 22)) return "date"; if (id == 45 || id == 46 || id == 47) return "time"; if (id >= 164) { map::const_iterator it = custom_fmt.find (id); if (it != custom_fmt.end ()) { const string &f = it->second; if (f.find ('[') != string::npos) // elapsed [h]/[hh]/[mm] -> time return "time"; if (f.find ('y') != string::npos || f.find ('Y') != string::npos || f.find ('d') != string::npos || f.find ('D') != string::npos) return "date"; if (f.find ('h') != string::npos || f.find ('H') != string::npos || f.find ('s') != string::npos || f.find ('S') != string::npos) return "date"; // time-of-day -> datetime } } return string (); } DEFUN_DLD (__xlsx2table__, args, nargout, "-*- texinfo -*-\n \ @deftypefn {datatypes} {[@var{data}, @var{vtype}, @var{meta}, @var{names}] =} \ __xlsx2table__ (@var{file})\n\ @deftypefnx {datatypes} {[@var{data}, @var{vtype}, @var{meta}, @var{names}] =} \ __xlsx2table__ (@var{file}, @var{sheet})\n\ \n\ \n\ Barebone function for reading a sheet of an Office Open XML (@qcode{.xlsx} / \ @qcode{.xlsm}) spreadsheet.\n\ \n\ This is a helper IO function for the @qcode{readtable} function. Do NOT call \ it directly. \n\ \n\ @end deftypefn") { octave_value_list retval (4); retval(1) = Cell (); retval(2) = Cell (); // no metadata sheet in the interop format retval(3) = Cell (); if (args.length () < 1 || args.length () > 2) error ("__xlsx2table__: one or two input arguments are required."); string file = args(0).string_value (); string want_name; long want_index = 0; bool by_name = false, by_index = false; if (args.length () == 2 && ! args(1).isempty ()) { if (args(1).is_string ()) { want_name = args(1).string_value (); by_name = true; } else { want_index = (long) args(1).scalar_value (); by_index = true; } } mz_zip_archive zip; memset (&zip, 0, sizeof (zip)); if (! mz_zip_reader_init_file (&zip, file.c_str (), 0)) { retval(0) = string ("cannot read '") + file + "' as an Excel archive."; return retval; } // --- workbook.xml: sheet names in order, plus each sheet's relationship id --- pugi::xml_document wbdoc; string wbxml = read_member (zip, "xl/workbook.xml"); if (wbxml.empty () || ! wbdoc.load_buffer (wbxml.data (), wbxml.size ())) { mz_zip_reader_end (&zip); retval(0) = string ("'") + file + "' is not a readable Excel workbook."; return retval; } vector sheet_names, sheet_rid; for (pugi::xml_node sh = wbdoc.child ("workbook").child ("sheets").child ("sheet"); sh; sh = sh.next_sibling ("sheet")) { sheet_names.push_back (sh.attribute ("name").as_string ()); sheet_rid.push_back (sh.attribute ("r:id").as_string ()); } // Select the sheet. long sel = -1; if (by_name) { for (size_t i = 0; i < sheet_names.size (); i++) if (sheet_names[i] == want_name) { sel = (long) i; break; } if (sel < 0) { mz_zip_reader_end (&zip); retval(0) = string ("sheet '") + want_name + "' not found in '" + file + "'."; return retval; } } else if (by_index) { if (want_index < 1 || (size_t) want_index > sheet_names.size ()) { mz_zip_reader_end (&zip); retval(0) = string ("sheet index ") + std::to_string (want_index) + " out of range in '" + file + "'."; return retval; } sel = want_index - 1; } else sel = (sheet_names.empty () ? -1 : 0); Cell names_out (1, sheet_names.size ()); for (size_t i = 0; i < sheet_names.size (); i++) names_out(i) = sheet_names[i]; retval(3) = names_out; if (sel < 0) { mz_zip_reader_end (&zip); retval(0) = Cell (); retval(1) = Cell (); return retval; } // --- resolve the worksheet part via workbook relationships --- string target = "worksheets/sheet1.xml"; pugi::xml_document rdoc; string rxml = read_member (zip, "xl/_rels/workbook.xml.rels"); if (! rxml.empty () && rdoc.load_buffer (rxml.data (), rxml.size ())) { for (pugi::xml_node rel = rdoc.child ("Relationships").child ("Relationship"); rel; rel = rel.next_sibling ("Relationship")) if (sheet_rid[sel] == rel.attribute ("Id").as_string ()) { target = rel.attribute ("Target").as_string (); break; } } if (! target.empty () && target[0] == '/') target = target.substr (1); else target = "xl/" + target; // --- shared strings (optional) --- vector shared; string sxml = read_member (zip, "xl/sharedStrings.xml"); if (! sxml.empty ()) { pugi::xml_document sdoc; if (sdoc.load_buffer (sxml.data (), sxml.size ())) for (pugi::xml_node si = sdoc.child ("sst").child ("si"); si; si = si.next_sibling ("si")) shared.push_back (all_text (si)); } // --- styles: map each cell format (s index) to its number-format id --- vector xf_fmt; map custom_fmt; string stxml = read_member (zip, "xl/styles.xml"); if (! stxml.empty ()) { pugi::xml_document stdoc; if (stdoc.load_buffer (stxml.data (), stxml.size ())) { pugi::xml_node ss = stdoc.child ("styleSheet"); for (pugi::xml_node nf = ss.child ("numFmts").child ("numFmt"); nf; nf = nf.next_sibling ("numFmt")) custom_fmt[nf.attribute ("numFmtId").as_int ()] = nf.attribute ("formatCode").as_string (); for (pugi::xml_node xf = ss.child ("cellXfs").child ("xf"); xf; xf = xf.next_sibling ("xf")) xf_fmt.push_back (xf.attribute ("numFmtId").as_int (0)); } } // --- worksheet cells --- pugi::xml_document wsdoc; string wsxml = read_member (zip, target.c_str ()); mz_zip_reader_end (&zip); if (wsxml.empty () || ! wsdoc.load_buffer (wsxml.data (), wsxml.size ())) { retval(0) = string ("cannot read sheet in '") + file + "'."; return retval; } struct xcell { long row, col; octave_value val; string vt; }; vector cells; long maxrow = 0, maxcol = 0; pugi::xml_node sheetData = wsdoc.child ("worksheet").child ("sheetData"); for (pugi::xml_node row = sheetData.child ("row"); row; row = row.next_sibling ("row")) { for (pugi::xml_node c = row.child ("c"); c; c = c.next_sibling ("c")) { long r, col; if (! ref_to_rowcol (c.attribute ("r").as_string (), r, col)) continue; string t = c.attribute ("t").as_string (); long s = c.attribute ("s").as_int (-1); octave_value val; string vt; if (t == "inlineStr") { val = octave_value (all_text (c.child ("is"))); vt = "string"; } else if (t == "s") // shared string index { long idx = c.child ("v").text ().as_llong (-1); val = octave_value ((idx >= 0 && (size_t) idx < shared.size ()) ? shared[idx] : string ()); vt = "string"; } else if (t == "str") // formula string result { val = octave_value (c.child ("v").text ().as_string ()); vt = "string"; } else if (t == "b") // boolean { val = octave_value (c.child ("v").text ().as_int (0) != 0); vt = "boolean"; } else // number (possibly a date/time) { pugi::xml_node v = c.child ("v"); if (! v) continue; // empty cell double num = atof (v.text ().as_string ()); string kind = style_kind (s, xf_fmt, custom_fmt); if (kind == "date") { val = octave_value (serial_to_iso_datetime (num)); vt = "date"; } else if (kind == "time") { val = octave_value (serial_to_pt_duration (num)); vt = "time"; } else { val = octave_value (num); vt = "float"; } } xcell xc; xc.row = r; xc.col = col; xc.val = val; xc.vt = vt; cells.push_back (xc); if (r > maxrow) maxrow = r; if (col > maxcol) maxcol = col; } } Cell data (maxrow, maxcol), vtype (maxrow, maxcol); for (long r = 0; r < maxrow; r++) for (long col = 0; col < maxcol; col++) { data(r, col) = Matrix (0, 0); // blank vtype(r, col) = string (); } for (size_t i = 0; i < cells.size (); i++) { data(cells[i].row - 1, cells[i].col - 1) = cells[i].val; vtype(cells[i].row - 1, cells[i].col - 1) = cells[i].vt; } retval(0) = data; retval(1) = vtype; return retval; } pr0m1th3as-datatypes-9c9a8d3/src/date/000077500000000000000000000000001522766574100175615ustar00rootroot00000000000000pr0m1th3as-datatypes-9c9a8d3/src/date/chrono_io.h000066400000000000000000000026731522766574100217210ustar00rootroot00000000000000#ifndef CHRONO_IO_H #define CHRONO_IO_H // The MIT License (MIT) // // Copyright (c) 2016, 2017 Howard Hinnant // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. // // Our apologies. When the previous paragraph was written, lowercase had not yet // been invented (that would involve another several millennia of evolution). // We did not mean to shout. // This functionality has moved to "date.h" #include "date.h" #endif // CHRONO_IO_H pr0m1th3as-datatypes-9c9a8d3/src/date/date.h000066400000000000000000007303131522766574100206560ustar00rootroot00000000000000#ifndef DATE_H #define DATE_H // The MIT License (MIT) // // Copyright (c) 2015, 2016, 2017 Howard Hinnant // Copyright (c) 2016 Adrian Colomitchi // Copyright (c) 2017 Florian Dang // Copyright (c) 2017 Paul Thompson // Copyright (c) 2018, 2019 Tomasz Kamiński // Copyright (c) 2019 Jiangang Zhuang // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. // // Our apologies. When the previous paragraph was written, lowercase had not yet // been invented (that would involve another several millennia of evolution). // We did not mean to shout. #ifndef HAS_STRING_VIEW # if __cplusplus >= 201703 || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) # define HAS_STRING_VIEW 1 # else # define HAS_STRING_VIEW 0 # endif #endif // HAS_STRING_VIEW #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if HAS_STRING_VIEW # include #endif #include #include #ifdef __GNUC__ # pragma GCC diagnostic push # if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ > 7) # pragma GCC diagnostic ignored "-Wpedantic" # endif # if __GNUC__ < 5 // GCC 4.9 Bug 61489 Wrong warning with -Wmissing-field-initializers # pragma GCC diagnostic ignored "-Wmissing-field-initializers" # endif #endif #ifdef _MSC_VER # pragma warning(push) // warning C4127: conditional expression is constant # pragma warning(disable : 4127) #endif namespace date { //---------------+ // Configuration | //---------------+ #ifndef ONLY_C_LOCALE # define ONLY_C_LOCALE 0 #endif #if defined(_MSC_VER) && (!defined(__clang__) || (_MSC_VER < 1910)) // MSVC # ifndef _SILENCE_CXX17_UNCAUGHT_EXCEPTION_DEPRECATION_WARNING # define _SILENCE_CXX17_UNCAUGHT_EXCEPTION_DEPRECATION_WARNING # endif # if _MSC_VER < 1910 // before VS2017 # define CONSTDATA const # define CONSTCD11 # define CONSTCD14 # define NOEXCEPT _NOEXCEPT # else // VS2017 and later # define CONSTDATA constexpr const # define CONSTCD11 constexpr # define CONSTCD14 constexpr # define NOEXCEPT noexcept # endif #elif defined(__SUNPRO_CC) && __SUNPRO_CC <= 0x5150 // Oracle Developer Studio 12.6 and earlier # define CONSTDATA constexpr const # define CONSTCD11 constexpr # define CONSTCD14 # define NOEXCEPT noexcept #elif __cplusplus >= 201402 // C++14 # define CONSTDATA constexpr const # define CONSTCD11 constexpr # define CONSTCD14 constexpr # define NOEXCEPT noexcept #else // C++11 # define CONSTDATA constexpr const # define CONSTCD11 constexpr # define CONSTCD14 # define NOEXCEPT noexcept #endif #ifndef HAS_UNCAUGHT_EXCEPTIONS # if __cplusplus >= 201703 || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) # define HAS_UNCAUGHT_EXCEPTIONS 1 # else # define HAS_UNCAUGHT_EXCEPTIONS 0 # endif #endif // HAS_UNCAUGHT_EXCEPTIONS #ifndef HAS_VOID_T # if __cplusplus >= 201703 || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) # define HAS_VOID_T 1 # else # define HAS_VOID_T 0 # endif #endif // HAS_VOID_T // Protect from Oracle sun macro #ifdef sun # undef sun #endif // Work around for a NVCC compiler bug which causes it to fail // to compile std::ratio_{multiply,divide} when used directly // in the std::chrono::duration template instantiations below namespace detail { template using ratio_multiply = decltype(std::ratio_multiply{}); template using ratio_divide = decltype(std::ratio_divide{}); } // namespace detail //-----------+ // Interface | //-----------+ // durations using days = std::chrono::duration , std::chrono::hours::period>>; using weeks = std::chrono::duration , days::period>>; using years = std::chrono::duration , days::period>>; using months = std::chrono::duration >>; // time_point template using sys_time = std::chrono::time_point; using sys_days = sys_time; using sys_seconds = sys_time; struct local_t {}; template using local_time = std::chrono::time_point; using local_seconds = local_time; using local_days = local_time; // types struct last_spec { explicit last_spec() = default; }; class day; class month; class year; class weekday; class weekday_indexed; class weekday_last; class month_day; class month_day_last; class month_weekday; class month_weekday_last; class year_month; class year_month_day; class year_month_day_last; class year_month_weekday; class year_month_weekday_last; // date composition operators CONSTCD11 year_month operator/(const year& y, const month& m) NOEXCEPT; CONSTCD11 year_month operator/(const year& y, int m) NOEXCEPT; CONSTCD11 month_day operator/(const day& d, const month& m) NOEXCEPT; CONSTCD11 month_day operator/(const day& d, int m) NOEXCEPT; CONSTCD11 month_day operator/(const month& m, const day& d) NOEXCEPT; CONSTCD11 month_day operator/(const month& m, int d) NOEXCEPT; CONSTCD11 month_day operator/(int m, const day& d) NOEXCEPT; CONSTCD11 month_day_last operator/(const month& m, last_spec) NOEXCEPT; CONSTCD11 month_day_last operator/(int m, last_spec) NOEXCEPT; CONSTCD11 month_day_last operator/(last_spec, const month& m) NOEXCEPT; CONSTCD11 month_day_last operator/(last_spec, int m) NOEXCEPT; CONSTCD11 month_weekday operator/(const month& m, const weekday_indexed& wdi) NOEXCEPT; CONSTCD11 month_weekday operator/(int m, const weekday_indexed& wdi) NOEXCEPT; CONSTCD11 month_weekday operator/(const weekday_indexed& wdi, const month& m) NOEXCEPT; CONSTCD11 month_weekday operator/(const weekday_indexed& wdi, int m) NOEXCEPT; CONSTCD11 month_weekday_last operator/(const month& m, const weekday_last& wdl) NOEXCEPT; CONSTCD11 month_weekday_last operator/(int m, const weekday_last& wdl) NOEXCEPT; CONSTCD11 month_weekday_last operator/(const weekday_last& wdl, const month& m) NOEXCEPT; CONSTCD11 month_weekday_last operator/(const weekday_last& wdl, int m) NOEXCEPT; CONSTCD11 year_month_day operator/(const year_month& ym, const day& d) NOEXCEPT; CONSTCD11 year_month_day operator/(const year_month& ym, int d) NOEXCEPT; CONSTCD11 year_month_day operator/(const year& y, const month_day& md) NOEXCEPT; CONSTCD11 year_month_day operator/(int y, const month_day& md) NOEXCEPT; CONSTCD11 year_month_day operator/(const month_day& md, const year& y) NOEXCEPT; CONSTCD11 year_month_day operator/(const month_day& md, int y) NOEXCEPT; CONSTCD11 year_month_day_last operator/(const year_month& ym, last_spec) NOEXCEPT; CONSTCD11 year_month_day_last operator/(const year& y, const month_day_last& mdl) NOEXCEPT; CONSTCD11 year_month_day_last operator/(int y, const month_day_last& mdl) NOEXCEPT; CONSTCD11 year_month_day_last operator/(const month_day_last& mdl, const year& y) NOEXCEPT; CONSTCD11 year_month_day_last operator/(const month_day_last& mdl, int y) NOEXCEPT; CONSTCD11 year_month_weekday operator/(const year_month& ym, const weekday_indexed& wdi) NOEXCEPT; CONSTCD11 year_month_weekday operator/(const year& y, const month_weekday& mwd) NOEXCEPT; CONSTCD11 year_month_weekday operator/(int y, const month_weekday& mwd) NOEXCEPT; CONSTCD11 year_month_weekday operator/(const month_weekday& mwd, const year& y) NOEXCEPT; CONSTCD11 year_month_weekday operator/(const month_weekday& mwd, int y) NOEXCEPT; CONSTCD11 year_month_weekday_last operator/(const year_month& ym, const weekday_last& wdl) NOEXCEPT; CONSTCD11 year_month_weekday_last operator/(const year& y, const month_weekday_last& mwdl) NOEXCEPT; CONSTCD11 year_month_weekday_last operator/(int y, const month_weekday_last& mwdl) NOEXCEPT; CONSTCD11 year_month_weekday_last operator/(const month_weekday_last& mwdl, const year& y) NOEXCEPT; CONSTCD11 year_month_weekday_last operator/(const month_weekday_last& mwdl, int y) NOEXCEPT; // Detailed interface // day class day { unsigned char d_; public: day() = default; explicit CONSTCD11 day(unsigned d) NOEXCEPT; CONSTCD14 day& operator++() NOEXCEPT; CONSTCD14 day operator++(int) NOEXCEPT; CONSTCD14 day& operator--() NOEXCEPT; CONSTCD14 day operator--(int) NOEXCEPT; CONSTCD14 day& operator+=(const days& d) NOEXCEPT; CONSTCD14 day& operator-=(const days& d) NOEXCEPT; CONSTCD11 explicit operator unsigned() const NOEXCEPT; CONSTCD11 bool ok() const NOEXCEPT; }; CONSTCD11 bool operator==(const day& x, const day& y) NOEXCEPT; CONSTCD11 bool operator!=(const day& x, const day& y) NOEXCEPT; CONSTCD11 bool operator< (const day& x, const day& y) NOEXCEPT; CONSTCD11 bool operator> (const day& x, const day& y) NOEXCEPT; CONSTCD11 bool operator<=(const day& x, const day& y) NOEXCEPT; CONSTCD11 bool operator>=(const day& x, const day& y) NOEXCEPT; CONSTCD11 day operator+(const day& x, const days& y) NOEXCEPT; CONSTCD11 day operator+(const days& x, const day& y) NOEXCEPT; CONSTCD11 day operator-(const day& x, const days& y) NOEXCEPT; CONSTCD11 days operator-(const day& x, const day& y) NOEXCEPT; template std::basic_ostream& operator<<(std::basic_ostream& os, const day& d); // month class month { unsigned char m_; public: month() = default; explicit CONSTCD11 month(unsigned m) NOEXCEPT; CONSTCD14 month& operator++() NOEXCEPT; CONSTCD14 month operator++(int) NOEXCEPT; CONSTCD14 month& operator--() NOEXCEPT; CONSTCD14 month operator--(int) NOEXCEPT; CONSTCD14 month& operator+=(const months& m) NOEXCEPT; CONSTCD14 month& operator-=(const months& m) NOEXCEPT; CONSTCD11 explicit operator unsigned() const NOEXCEPT; CONSTCD11 bool ok() const NOEXCEPT; }; CONSTCD11 bool operator==(const month& x, const month& y) NOEXCEPT; CONSTCD11 bool operator!=(const month& x, const month& y) NOEXCEPT; CONSTCD11 bool operator< (const month& x, const month& y) NOEXCEPT; CONSTCD11 bool operator> (const month& x, const month& y) NOEXCEPT; CONSTCD11 bool operator<=(const month& x, const month& y) NOEXCEPT; CONSTCD11 bool operator>=(const month& x, const month& y) NOEXCEPT; CONSTCD14 month operator+(const month& x, const months& y) NOEXCEPT; CONSTCD14 month operator+(const months& x, const month& y) NOEXCEPT; CONSTCD14 month operator-(const month& x, const months& y) NOEXCEPT; CONSTCD14 months operator-(const month& x, const month& y) NOEXCEPT; template std::basic_ostream& operator<<(std::basic_ostream& os, const month& m); // year class year { short y_; public: year() = default; explicit CONSTCD11 year(int y) NOEXCEPT; CONSTCD14 year& operator++() NOEXCEPT; CONSTCD14 year operator++(int) NOEXCEPT; CONSTCD14 year& operator--() NOEXCEPT; CONSTCD14 year operator--(int) NOEXCEPT; CONSTCD14 year& operator+=(const years& y) NOEXCEPT; CONSTCD14 year& operator-=(const years& y) NOEXCEPT; CONSTCD11 year operator-() const NOEXCEPT; CONSTCD11 year operator+() const NOEXCEPT; CONSTCD11 bool is_leap() const NOEXCEPT; CONSTCD11 explicit operator int() const NOEXCEPT; CONSTCD11 bool ok() const NOEXCEPT; static CONSTCD11 year min() NOEXCEPT { return year{-32767}; } static CONSTCD11 year max() NOEXCEPT { return year{32767}; } }; CONSTCD11 bool operator==(const year& x, const year& y) NOEXCEPT; CONSTCD11 bool operator!=(const year& x, const year& y) NOEXCEPT; CONSTCD11 bool operator< (const year& x, const year& y) NOEXCEPT; CONSTCD11 bool operator> (const year& x, const year& y) NOEXCEPT; CONSTCD11 bool operator<=(const year& x, const year& y) NOEXCEPT; CONSTCD11 bool operator>=(const year& x, const year& y) NOEXCEPT; CONSTCD11 year operator+(const year& x, const years& y) NOEXCEPT; CONSTCD11 year operator+(const years& x, const year& y) NOEXCEPT; CONSTCD11 year operator-(const year& x, const years& y) NOEXCEPT; CONSTCD11 years operator-(const year& x, const year& y) NOEXCEPT; template std::basic_ostream& operator<<(std::basic_ostream& os, const year& y); // weekday class weekday { unsigned char wd_; public: weekday() = default; explicit CONSTCD11 weekday(unsigned wd) NOEXCEPT; CONSTCD14 weekday(const sys_days& dp) NOEXCEPT; CONSTCD14 explicit weekday(const local_days& dp) NOEXCEPT; CONSTCD14 weekday& operator++() NOEXCEPT; CONSTCD14 weekday operator++(int) NOEXCEPT; CONSTCD14 weekday& operator--() NOEXCEPT; CONSTCD14 weekday operator--(int) NOEXCEPT; CONSTCD14 weekday& operator+=(const days& d) NOEXCEPT; CONSTCD14 weekday& operator-=(const days& d) NOEXCEPT; CONSTCD11 bool ok() const NOEXCEPT; CONSTCD11 unsigned c_encoding() const NOEXCEPT; CONSTCD11 unsigned iso_encoding() const NOEXCEPT; CONSTCD11 weekday_indexed operator[](unsigned index) const NOEXCEPT; CONSTCD11 weekday_last operator[](last_spec) const NOEXCEPT; private: static CONSTCD14 unsigned char weekday_from_days(int z) NOEXCEPT; friend CONSTCD11 bool operator==(const weekday& x, const weekday& y) NOEXCEPT; friend CONSTCD14 days operator-(const weekday& x, const weekday& y) NOEXCEPT; friend CONSTCD14 weekday operator+(const weekday& x, const days& y) NOEXCEPT; template friend std::basic_ostream& operator<<(std::basic_ostream& os, const weekday& wd); friend class weekday_indexed; }; CONSTCD11 bool operator==(const weekday& x, const weekday& y) NOEXCEPT; CONSTCD11 bool operator!=(const weekday& x, const weekday& y) NOEXCEPT; CONSTCD14 weekday operator+(const weekday& x, const days& y) NOEXCEPT; CONSTCD14 weekday operator+(const days& x, const weekday& y) NOEXCEPT; CONSTCD14 weekday operator-(const weekday& x, const days& y) NOEXCEPT; CONSTCD14 days operator-(const weekday& x, const weekday& y) NOEXCEPT; template std::basic_ostream& operator<<(std::basic_ostream& os, const weekday& wd); // weekday_indexed class weekday_indexed { unsigned char wd_ : 4; unsigned char index_ : 4; public: weekday_indexed() = default; CONSTCD11 weekday_indexed(const date::weekday& wd, unsigned index) NOEXCEPT; CONSTCD11 date::weekday weekday() const NOEXCEPT; CONSTCD11 unsigned index() const NOEXCEPT; CONSTCD11 bool ok() const NOEXCEPT; }; CONSTCD11 bool operator==(const weekday_indexed& x, const weekday_indexed& y) NOEXCEPT; CONSTCD11 bool operator!=(const weekday_indexed& x, const weekday_indexed& y) NOEXCEPT; template std::basic_ostream& operator<<(std::basic_ostream& os, const weekday_indexed& wdi); // weekday_last class weekday_last { date::weekday wd_; public: explicit CONSTCD11 weekday_last(const date::weekday& wd) NOEXCEPT; CONSTCD11 date::weekday weekday() const NOEXCEPT; CONSTCD11 bool ok() const NOEXCEPT; }; CONSTCD11 bool operator==(const weekday_last& x, const weekday_last& y) NOEXCEPT; CONSTCD11 bool operator!=(const weekday_last& x, const weekday_last& y) NOEXCEPT; template std::basic_ostream& operator<<(std::basic_ostream& os, const weekday_last& wdl); namespace detail { struct unspecified_month_disambiguator {}; } // namespace detail // year_month class year_month { date::year y_; date::month m_; public: year_month() = default; CONSTCD11 year_month(const date::year& y, const date::month& m) NOEXCEPT; CONSTCD11 date::year year() const NOEXCEPT; CONSTCD11 date::month month() const NOEXCEPT; template CONSTCD14 year_month& operator+=(const months& dm) NOEXCEPT; template CONSTCD14 year_month& operator-=(const months& dm) NOEXCEPT; CONSTCD14 year_month& operator+=(const years& dy) NOEXCEPT; CONSTCD14 year_month& operator-=(const years& dy) NOEXCEPT; CONSTCD11 bool ok() const NOEXCEPT; }; CONSTCD11 bool operator==(const year_month& x, const year_month& y) NOEXCEPT; CONSTCD11 bool operator!=(const year_month& x, const year_month& y) NOEXCEPT; CONSTCD11 bool operator< (const year_month& x, const year_month& y) NOEXCEPT; CONSTCD11 bool operator> (const year_month& x, const year_month& y) NOEXCEPT; CONSTCD11 bool operator<=(const year_month& x, const year_month& y) NOEXCEPT; CONSTCD11 bool operator>=(const year_month& x, const year_month& y) NOEXCEPT; template CONSTCD14 year_month operator+(const year_month& ym, const months& dm) NOEXCEPT; template CONSTCD14 year_month operator+(const months& dm, const year_month& ym) NOEXCEPT; template CONSTCD14 year_month operator-(const year_month& ym, const months& dm) NOEXCEPT; CONSTCD11 months operator-(const year_month& x, const year_month& y) NOEXCEPT; CONSTCD11 year_month operator+(const year_month& ym, const years& dy) NOEXCEPT; CONSTCD11 year_month operator+(const years& dy, const year_month& ym) NOEXCEPT; CONSTCD11 year_month operator-(const year_month& ym, const years& dy) NOEXCEPT; template std::basic_ostream& operator<<(std::basic_ostream& os, const year_month& ym); // month_day class month_day { date::month m_; date::day d_; public: month_day() = default; CONSTCD11 month_day(const date::month& m, const date::day& d) NOEXCEPT; CONSTCD11 date::month month() const NOEXCEPT; CONSTCD11 date::day day() const NOEXCEPT; CONSTCD14 bool ok() const NOEXCEPT; }; CONSTCD11 bool operator==(const month_day& x, const month_day& y) NOEXCEPT; CONSTCD11 bool operator!=(const month_day& x, const month_day& y) NOEXCEPT; CONSTCD11 bool operator< (const month_day& x, const month_day& y) NOEXCEPT; CONSTCD11 bool operator> (const month_day& x, const month_day& y) NOEXCEPT; CONSTCD11 bool operator<=(const month_day& x, const month_day& y) NOEXCEPT; CONSTCD11 bool operator>=(const month_day& x, const month_day& y) NOEXCEPT; template std::basic_ostream& operator<<(std::basic_ostream& os, const month_day& md); // month_day_last class month_day_last { date::month m_; public: CONSTCD11 explicit month_day_last(const date::month& m) NOEXCEPT; CONSTCD11 date::month month() const NOEXCEPT; CONSTCD11 bool ok() const NOEXCEPT; }; CONSTCD11 bool operator==(const month_day_last& x, const month_day_last& y) NOEXCEPT; CONSTCD11 bool operator!=(const month_day_last& x, const month_day_last& y) NOEXCEPT; CONSTCD11 bool operator< (const month_day_last& x, const month_day_last& y) NOEXCEPT; CONSTCD11 bool operator> (const month_day_last& x, const month_day_last& y) NOEXCEPT; CONSTCD11 bool operator<=(const month_day_last& x, const month_day_last& y) NOEXCEPT; CONSTCD11 bool operator>=(const month_day_last& x, const month_day_last& y) NOEXCEPT; template std::basic_ostream& operator<<(std::basic_ostream& os, const month_day_last& mdl); // month_weekday class month_weekday { date::month m_; date::weekday_indexed wdi_; public: CONSTCD11 month_weekday(const date::month& m, const date::weekday_indexed& wdi) NOEXCEPT; CONSTCD11 date::month month() const NOEXCEPT; CONSTCD11 date::weekday_indexed weekday_indexed() const NOEXCEPT; CONSTCD11 bool ok() const NOEXCEPT; }; CONSTCD11 bool operator==(const month_weekday& x, const month_weekday& y) NOEXCEPT; CONSTCD11 bool operator!=(const month_weekday& x, const month_weekday& y) NOEXCEPT; template std::basic_ostream& operator<<(std::basic_ostream& os, const month_weekday& mwd); // month_weekday_last class month_weekday_last { date::month m_; date::weekday_last wdl_; public: CONSTCD11 month_weekday_last(const date::month& m, const date::weekday_last& wd) NOEXCEPT; CONSTCD11 date::month month() const NOEXCEPT; CONSTCD11 date::weekday_last weekday_last() const NOEXCEPT; CONSTCD11 bool ok() const NOEXCEPT; }; CONSTCD11 bool operator==(const month_weekday_last& x, const month_weekday_last& y) NOEXCEPT; CONSTCD11 bool operator!=(const month_weekday_last& x, const month_weekday_last& y) NOEXCEPT; template std::basic_ostream& operator<<(std::basic_ostream& os, const month_weekday_last& mwdl); // class year_month_day class year_month_day { date::year y_; date::month m_; date::day d_; public: year_month_day() = default; CONSTCD11 year_month_day(const date::year& y, const date::month& m, const date::day& d) NOEXCEPT; CONSTCD14 year_month_day(const year_month_day_last& ymdl) NOEXCEPT; CONSTCD14 year_month_day(sys_days dp) NOEXCEPT; CONSTCD14 explicit year_month_day(local_days dp) NOEXCEPT; template CONSTCD14 year_month_day& operator+=(const months& m) NOEXCEPT; template CONSTCD14 year_month_day& operator-=(const months& m) NOEXCEPT; CONSTCD14 year_month_day& operator+=(const years& y) NOEXCEPT; CONSTCD14 year_month_day& operator-=(const years& y) NOEXCEPT; CONSTCD11 date::year year() const NOEXCEPT; CONSTCD11 date::month month() const NOEXCEPT; CONSTCD11 date::day day() const NOEXCEPT; CONSTCD14 operator sys_days() const NOEXCEPT; CONSTCD14 explicit operator local_days() const NOEXCEPT; CONSTCD14 bool ok() const NOEXCEPT; private: static CONSTCD14 year_month_day from_days(days dp) NOEXCEPT; CONSTCD14 days to_days() const NOEXCEPT; }; CONSTCD11 bool operator==(const year_month_day& x, const year_month_day& y) NOEXCEPT; CONSTCD11 bool operator!=(const year_month_day& x, const year_month_day& y) NOEXCEPT; CONSTCD11 bool operator< (const year_month_day& x, const year_month_day& y) NOEXCEPT; CONSTCD11 bool operator> (const year_month_day& x, const year_month_day& y) NOEXCEPT; CONSTCD11 bool operator<=(const year_month_day& x, const year_month_day& y) NOEXCEPT; CONSTCD11 bool operator>=(const year_month_day& x, const year_month_day& y) NOEXCEPT; template CONSTCD14 year_month_day operator+(const year_month_day& ymd, const months& dm) NOEXCEPT; template CONSTCD14 year_month_day operator+(const months& dm, const year_month_day& ymd) NOEXCEPT; template CONSTCD14 year_month_day operator-(const year_month_day& ymd, const months& dm) NOEXCEPT; CONSTCD11 year_month_day operator+(const year_month_day& ymd, const years& dy) NOEXCEPT; CONSTCD11 year_month_day operator+(const years& dy, const year_month_day& ymd) NOEXCEPT; CONSTCD11 year_month_day operator-(const year_month_day& ymd, const years& dy) NOEXCEPT; template std::basic_ostream& operator<<(std::basic_ostream& os, const year_month_day& ymd); // year_month_day_last class year_month_day_last { date::year y_; date::month_day_last mdl_; public: CONSTCD11 year_month_day_last(const date::year& y, const date::month_day_last& mdl) NOEXCEPT; template CONSTCD14 year_month_day_last& operator+=(const months& m) NOEXCEPT; template CONSTCD14 year_month_day_last& operator-=(const months& m) NOEXCEPT; CONSTCD14 year_month_day_last& operator+=(const years& y) NOEXCEPT; CONSTCD14 year_month_day_last& operator-=(const years& y) NOEXCEPT; CONSTCD11 date::year year() const NOEXCEPT; CONSTCD11 date::month month() const NOEXCEPT; CONSTCD11 date::month_day_last month_day_last() const NOEXCEPT; CONSTCD14 date::day day() const NOEXCEPT; CONSTCD14 operator sys_days() const NOEXCEPT; CONSTCD14 explicit operator local_days() const NOEXCEPT; CONSTCD11 bool ok() const NOEXCEPT; }; CONSTCD11 bool operator==(const year_month_day_last& x, const year_month_day_last& y) NOEXCEPT; CONSTCD11 bool operator!=(const year_month_day_last& x, const year_month_day_last& y) NOEXCEPT; CONSTCD11 bool operator< (const year_month_day_last& x, const year_month_day_last& y) NOEXCEPT; CONSTCD11 bool operator> (const year_month_day_last& x, const year_month_day_last& y) NOEXCEPT; CONSTCD11 bool operator<=(const year_month_day_last& x, const year_month_day_last& y) NOEXCEPT; CONSTCD11 bool operator>=(const year_month_day_last& x, const year_month_day_last& y) NOEXCEPT; template CONSTCD14 year_month_day_last operator+(const year_month_day_last& ymdl, const months& dm) NOEXCEPT; template CONSTCD14 year_month_day_last operator+(const months& dm, const year_month_day_last& ymdl) NOEXCEPT; CONSTCD11 year_month_day_last operator+(const year_month_day_last& ymdl, const years& dy) NOEXCEPT; CONSTCD11 year_month_day_last operator+(const years& dy, const year_month_day_last& ymdl) NOEXCEPT; template CONSTCD14 year_month_day_last operator-(const year_month_day_last& ymdl, const months& dm) NOEXCEPT; CONSTCD11 year_month_day_last operator-(const year_month_day_last& ymdl, const years& dy) NOEXCEPT; template std::basic_ostream& operator<<(std::basic_ostream& os, const year_month_day_last& ymdl); // year_month_weekday class year_month_weekday { date::year y_; date::month m_; date::weekday_indexed wdi_; public: year_month_weekday() = default; CONSTCD11 year_month_weekday(const date::year& y, const date::month& m, const date::weekday_indexed& wdi) NOEXCEPT; CONSTCD14 year_month_weekday(const sys_days& dp) NOEXCEPT; CONSTCD14 explicit year_month_weekday(const local_days& dp) NOEXCEPT; template CONSTCD14 year_month_weekday& operator+=(const months& m) NOEXCEPT; template CONSTCD14 year_month_weekday& operator-=(const months& m) NOEXCEPT; CONSTCD14 year_month_weekday& operator+=(const years& y) NOEXCEPT; CONSTCD14 year_month_weekday& operator-=(const years& y) NOEXCEPT; CONSTCD11 date::year year() const NOEXCEPT; CONSTCD11 date::month month() const NOEXCEPT; CONSTCD11 date::weekday weekday() const NOEXCEPT; CONSTCD11 unsigned index() const NOEXCEPT; CONSTCD11 date::weekday_indexed weekday_indexed() const NOEXCEPT; CONSTCD14 operator sys_days() const NOEXCEPT; CONSTCD14 explicit operator local_days() const NOEXCEPT; CONSTCD14 bool ok() const NOEXCEPT; private: static CONSTCD14 year_month_weekday from_days(days dp) NOEXCEPT; CONSTCD14 days to_days() const NOEXCEPT; }; CONSTCD11 bool operator==(const year_month_weekday& x, const year_month_weekday& y) NOEXCEPT; CONSTCD11 bool operator!=(const year_month_weekday& x, const year_month_weekday& y) NOEXCEPT; template CONSTCD14 year_month_weekday operator+(const year_month_weekday& ymwd, const months& dm) NOEXCEPT; template CONSTCD14 year_month_weekday operator+(const months& dm, const year_month_weekday& ymwd) NOEXCEPT; CONSTCD11 year_month_weekday operator+(const year_month_weekday& ymwd, const years& dy) NOEXCEPT; CONSTCD11 year_month_weekday operator+(const years& dy, const year_month_weekday& ymwd) NOEXCEPT; template CONSTCD14 year_month_weekday operator-(const year_month_weekday& ymwd, const months& dm) NOEXCEPT; CONSTCD11 year_month_weekday operator-(const year_month_weekday& ymwd, const years& dy) NOEXCEPT; template std::basic_ostream& operator<<(std::basic_ostream& os, const year_month_weekday& ymwdi); // year_month_weekday_last class year_month_weekday_last { date::year y_; date::month m_; date::weekday_last wdl_; public: CONSTCD11 year_month_weekday_last(const date::year& y, const date::month& m, const date::weekday_last& wdl) NOEXCEPT; template CONSTCD14 year_month_weekday_last& operator+=(const months& m) NOEXCEPT; template CONSTCD14 year_month_weekday_last& operator-=(const months& m) NOEXCEPT; CONSTCD14 year_month_weekday_last& operator+=(const years& y) NOEXCEPT; CONSTCD14 year_month_weekday_last& operator-=(const years& y) NOEXCEPT; CONSTCD11 date::year year() const NOEXCEPT; CONSTCD11 date::month month() const NOEXCEPT; CONSTCD11 date::weekday weekday() const NOEXCEPT; CONSTCD11 date::weekday_last weekday_last() const NOEXCEPT; CONSTCD14 operator sys_days() const NOEXCEPT; CONSTCD14 explicit operator local_days() const NOEXCEPT; CONSTCD11 bool ok() const NOEXCEPT; private: CONSTCD14 days to_days() const NOEXCEPT; }; CONSTCD11 bool operator==(const year_month_weekday_last& x, const year_month_weekday_last& y) NOEXCEPT; CONSTCD11 bool operator!=(const year_month_weekday_last& x, const year_month_weekday_last& y) NOEXCEPT; template CONSTCD14 year_month_weekday_last operator+(const year_month_weekday_last& ymwdl, const months& dm) NOEXCEPT; template CONSTCD14 year_month_weekday_last operator+(const months& dm, const year_month_weekday_last& ymwdl) NOEXCEPT; CONSTCD11 year_month_weekday_last operator+(const year_month_weekday_last& ymwdl, const years& dy) NOEXCEPT; CONSTCD11 year_month_weekday_last operator+(const years& dy, const year_month_weekday_last& ymwdl) NOEXCEPT; template CONSTCD14 year_month_weekday_last operator-(const year_month_weekday_last& ymwdl, const months& dm) NOEXCEPT; CONSTCD11 year_month_weekday_last operator-(const year_month_weekday_last& ymwdl, const years& dy) NOEXCEPT; template std::basic_ostream& operator<<(std::basic_ostream& os, const year_month_weekday_last& ymwdl); #if !defined(_MSC_VER) || (_MSC_VER >= 1900) inline namespace literals { CONSTCD11 date::day operator ""_d(unsigned long long d) NOEXCEPT; CONSTCD11 date::year operator ""_y(unsigned long long y) NOEXCEPT; } // inline namespace literals #endif // !defined(_MSC_VER) || (_MSC_VER >= 1900) // CONSTDATA date::month January{1}; // CONSTDATA date::month February{2}; // CONSTDATA date::month March{3}; // CONSTDATA date::month April{4}; // CONSTDATA date::month May{5}; // CONSTDATA date::month June{6}; // CONSTDATA date::month July{7}; // CONSTDATA date::month August{8}; // CONSTDATA date::month September{9}; // CONSTDATA date::month October{10}; // CONSTDATA date::month November{11}; // CONSTDATA date::month December{12}; // // CONSTDATA date::weekday Sunday{0u}; // CONSTDATA date::weekday Monday{1u}; // CONSTDATA date::weekday Tuesday{2u}; // CONSTDATA date::weekday Wednesday{3u}; // CONSTDATA date::weekday Thursday{4u}; // CONSTDATA date::weekday Friday{5u}; // CONSTDATA date::weekday Saturday{6u}; #if HAS_VOID_T template > struct is_clock : std::false_type {}; template struct is_clock> : std::true_type {}; template inline constexpr bool is_clock_v = is_clock::value; #endif // HAS_VOID_T //----------------+ // Implementation | //----------------+ // utilities namespace detail { template> class save_istream { protected: std::basic_ios& is_; CharT fill_; std::ios::fmtflags flags_; std::streamsize precision_; std::streamsize width_; std::basic_ostream* tie_; std::locale loc_; public: ~save_istream() { is_.fill(fill_); is_.flags(flags_); is_.precision(precision_); is_.width(width_); is_.imbue(loc_); is_.tie(tie_); } save_istream(const save_istream&) = delete; save_istream& operator=(const save_istream&) = delete; explicit save_istream(std::basic_ios& is) : is_(is) , fill_(is.fill()) , flags_(is.flags()) , precision_(is.precision()) , width_(is.width(0)) , tie_(is.tie(nullptr)) , loc_(is.getloc()) { if (tie_ != nullptr) tie_->flush(); } }; template> class save_ostream : private save_istream { public: ~save_ostream() { if ((this->flags_ & std::ios::unitbuf) && #if HAS_UNCAUGHT_EXCEPTIONS std::uncaught_exceptions() == 0 && #else !std::uncaught_exception() && #endif this->is_.good()) this->is_.rdbuf()->pubsync(); } save_ostream(const save_ostream&) = delete; save_ostream& operator=(const save_ostream&) = delete; explicit save_ostream(std::basic_ios& os) : save_istream(os) { } }; template struct choose_trunc_type { static const int digits = std::numeric_limits::digits; using type = typename std::conditional < digits < 32, std::int32_t, typename std::conditional < digits < 64, std::int64_t, #ifdef __SIZEOF_INT128__ __int128 #else std::int64_t #endif >::type >::type; }; template CONSTCD11 inline typename std::enable_if < !std::chrono::treat_as_floating_point::value, T >::type trunc(T t) NOEXCEPT { return t; } template CONSTCD14 inline typename std::enable_if < std::chrono::treat_as_floating_point::value, T >::type trunc(T t) NOEXCEPT { using std::numeric_limits; using I = typename choose_trunc_type::type; CONSTDATA auto digits = numeric_limits::digits; static_assert(digits < numeric_limits::digits, ""); CONSTDATA auto max = I{1} << (digits-1); CONSTDATA auto min = -max; const auto negative = t < T{0}; if (min <= t && t <= max && t != 0 && t == t) { t = static_cast(static_cast(t)); if (t == 0 && negative) t = -t; } return t; } template struct static_gcd { static const std::intmax_t value = static_gcd::value; }; template struct static_gcd { static const std::intmax_t value = Xp; }; template <> struct static_gcd<0, 0> { static const std::intmax_t value = 1; }; template struct no_overflow { private: static const std::intmax_t gcd_n1_n2 = static_gcd::value; static const std::intmax_t gcd_d1_d2 = static_gcd::value; static const std::intmax_t n1 = R1::num / gcd_n1_n2; static const std::intmax_t d1 = R1::den / gcd_d1_d2; static const std::intmax_t n2 = R2::num / gcd_n1_n2; static const std::intmax_t d2 = R2::den / gcd_d1_d2; #ifdef __cpp_constexpr static const std::intmax_t max = std::numeric_limits::max(); #else static const std::intmax_t max = LLONG_MAX; #endif template struct mul // overflow == false { static const std::intmax_t value = Xp * Yp; }; template struct mul { static const std::intmax_t value = 1; }; public: static const bool value = (n1 <= max / d2) && (n2 <= max / d1); typedef std::ratio::value, mul::value> type; }; } // detail // trunc towards zero template CONSTCD11 inline typename std::enable_if < detail::no_overflow::value, To >::type trunc(const std::chrono::duration& d) { return To{detail::trunc(std::chrono::duration_cast(d).count())}; } template CONSTCD11 inline typename std::enable_if < !detail::no_overflow::value, To >::type trunc(const std::chrono::duration& d) { using std::chrono::duration_cast; using std::chrono::duration; using rep = typename std::common_type::type; return To{detail::trunc(duration_cast(duration_cast>(d)).count())}; } #ifndef HAS_CHRONO_ROUNDING # if defined(_MSC_FULL_VER) && (_MSC_FULL_VER >= 190023918 || (_MSC_FULL_VER >= 190000000 && defined (__clang__))) # define HAS_CHRONO_ROUNDING 1 # elif defined(__cpp_lib_chrono) && __cplusplus > 201402 && __cpp_lib_chrono >= 201510 # define HAS_CHRONO_ROUNDING 1 # elif defined(_LIBCPP_VERSION) && __cplusplus > 201402 && _LIBCPP_VERSION >= 3800 # define HAS_CHRONO_ROUNDING 1 # else # define HAS_CHRONO_ROUNDING 0 # endif #endif // HAS_CHRONO_ROUNDING #if HAS_CHRONO_ROUNDING == 0 // round down template CONSTCD14 inline typename std::enable_if < detail::no_overflow::value, To >::type floor(const std::chrono::duration& d) { auto t = trunc(d); if (t > d) return t - To{1}; return t; } template CONSTCD14 inline typename std::enable_if < !detail::no_overflow::value, To >::type floor(const std::chrono::duration& d) { using rep = typename std::common_type::type; return floor(floor>(d)); } // round to nearest, to even on tie template CONSTCD14 inline To round(const std::chrono::duration& d) { auto t0 = floor(d); auto t1 = t0 + To{1}; if (t1 == To{0} && t0 < To{0}) t1 = -t1; auto diff0 = d - t0; auto diff1 = t1 - d; if (diff0 == diff1) { if (t0 - trunc(t0/2)*2 == To{0}) return t0; return t1; } if (diff0 < diff1) return t0; return t1; } // round up template CONSTCD14 inline To ceil(const std::chrono::duration& d) { auto t = trunc(d); if (t < d) return t + To{1}; return t; } template ::is_signed >::type> CONSTCD11 std::chrono::duration abs(std::chrono::duration d) { return d >= d.zero() ? d : static_cast(-d); } // round down template CONSTCD11 inline std::chrono::time_point floor(const std::chrono::time_point& tp) { using std::chrono::time_point; return time_point{date::floor(tp.time_since_epoch())}; } // round to nearest, to even on tie template CONSTCD11 inline std::chrono::time_point round(const std::chrono::time_point& tp) { using std::chrono::time_point; return time_point{round(tp.time_since_epoch())}; } // round up template CONSTCD11 inline std::chrono::time_point ceil(const std::chrono::time_point& tp) { using std::chrono::time_point; return time_point{ceil(tp.time_since_epoch())}; } #else // HAS_CHRONO_ROUNDING == 1 using std::chrono::floor; using std::chrono::ceil; using std::chrono::round; using std::chrono::abs; #endif // HAS_CHRONO_ROUNDING namespace detail { template CONSTCD14 inline typename std::enable_if < !std::chrono::treat_as_floating_point::value, To >::type round_i(const std::chrono::duration& d) { return round(d); } template CONSTCD14 inline typename std::enable_if < std::chrono::treat_as_floating_point::value, To >::type round_i(const std::chrono::duration& d) { return d; } template CONSTCD11 inline std::chrono::time_point round_i(const std::chrono::time_point& tp) { using std::chrono::time_point; return time_point{round_i(tp.time_since_epoch())}; } } // detail // trunc towards zero template CONSTCD11 inline std::chrono::time_point trunc(const std::chrono::time_point& tp) { using std::chrono::time_point; return time_point{trunc(tp.time_since_epoch())}; } // day CONSTCD11 inline day::day(unsigned d) NOEXCEPT : d_(static_cast(d)) {} CONSTCD14 inline day& day::operator++() NOEXCEPT {++d_; return *this;} CONSTCD14 inline day day::operator++(int) NOEXCEPT {auto tmp(*this); ++(*this); return tmp;} CONSTCD14 inline day& day::operator--() NOEXCEPT {--d_; return *this;} CONSTCD14 inline day day::operator--(int) NOEXCEPT {auto tmp(*this); --(*this); return tmp;} CONSTCD14 inline day& day::operator+=(const days& d) NOEXCEPT {*this = *this + d; return *this;} CONSTCD14 inline day& day::operator-=(const days& d) NOEXCEPT {*this = *this - d; return *this;} CONSTCD11 inline day::operator unsigned() const NOEXCEPT {return d_;} CONSTCD11 inline bool day::ok() const NOEXCEPT {return 1 <= d_ && d_ <= 31;} CONSTCD11 inline bool operator==(const day& x, const day& y) NOEXCEPT { return static_cast(x) == static_cast(y); } CONSTCD11 inline bool operator!=(const day& x, const day& y) NOEXCEPT { return !(x == y); } CONSTCD11 inline bool operator<(const day& x, const day& y) NOEXCEPT { return static_cast(x) < static_cast(y); } CONSTCD11 inline bool operator>(const day& x, const day& y) NOEXCEPT { return y < x; } CONSTCD11 inline bool operator<=(const day& x, const day& y) NOEXCEPT { return !(y < x); } CONSTCD11 inline bool operator>=(const day& x, const day& y) NOEXCEPT { return !(x < y); } CONSTCD11 inline days operator-(const day& x, const day& y) NOEXCEPT { return days{static_cast(static_cast(x) - static_cast(y))}; } CONSTCD11 inline day operator+(const day& x, const days& y) NOEXCEPT { return day{static_cast(x) + static_cast(y.count())}; } CONSTCD11 inline day operator+(const days& x, const day& y) NOEXCEPT { return y + x; } CONSTCD11 inline day operator-(const day& x, const days& y) NOEXCEPT { return x + -y; } namespace detail { template std::basic_ostream& low_level_fmt(std::basic_ostream& os, const day& d) { detail::save_ostream _(os); os.fill('0'); os.flags(std::ios::dec | std::ios::right); os.width(2); os << static_cast(d); return os; } } // namespace detail template inline std::basic_ostream& operator<<(std::basic_ostream& os, const day& d) { detail::low_level_fmt(os, d); if (!d.ok()) os << " is not a valid day"; return os; } // month CONSTCD11 inline month::month(unsigned m) NOEXCEPT : m_(static_cast(m)) {} CONSTCD14 inline month& month::operator++() NOEXCEPT {*this += months{1}; return *this;} CONSTCD14 inline month month::operator++(int) NOEXCEPT {auto tmp(*this); ++(*this); return tmp;} CONSTCD14 inline month& month::operator--() NOEXCEPT {*this -= months{1}; return *this;} CONSTCD14 inline month month::operator--(int) NOEXCEPT {auto tmp(*this); --(*this); return tmp;} CONSTCD14 inline month& month::operator+=(const months& m) NOEXCEPT { *this = *this + m; return *this; } CONSTCD14 inline month& month::operator-=(const months& m) NOEXCEPT { *this = *this - m; return *this; } CONSTCD11 inline month::operator unsigned() const NOEXCEPT {return m_;} CONSTCD11 inline bool month::ok() const NOEXCEPT {return 1 <= m_ && m_ <= 12;} CONSTCD11 inline bool operator==(const month& x, const month& y) NOEXCEPT { return static_cast(x) == static_cast(y); } CONSTCD11 inline bool operator!=(const month& x, const month& y) NOEXCEPT { return !(x == y); } CONSTCD11 inline bool operator<(const month& x, const month& y) NOEXCEPT { return static_cast(x) < static_cast(y); } CONSTCD11 inline bool operator>(const month& x, const month& y) NOEXCEPT { return y < x; } CONSTCD11 inline bool operator<=(const month& x, const month& y) NOEXCEPT { return !(y < x); } CONSTCD11 inline bool operator>=(const month& x, const month& y) NOEXCEPT { return !(x < y); } CONSTCD14 inline months operator-(const month& x, const month& y) NOEXCEPT { auto const d = static_cast(x) - static_cast(y); return months(d <= 11 ? d : d + 12); } CONSTCD14 inline month operator+(const month& x, const months& y) NOEXCEPT { auto const mu = static_cast(static_cast(x)) + y.count() - 1; auto const yr = (mu >= 0 ? mu : mu-11) / 12; return month{static_cast(mu - yr * 12 + 1)}; } CONSTCD14 inline month operator+(const months& x, const month& y) NOEXCEPT { return y + x; } CONSTCD14 inline month operator-(const month& x, const months& y) NOEXCEPT { return x + -y; } namespace detail { template std::basic_ostream& low_level_fmt(std::basic_ostream& os, const month& m) { if (m.ok()) { CharT fmt[] = {'%', 'b', 0}; os << format(os.getloc(), fmt, m); } else os << static_cast(m); return os; } } // namespace detail template inline std::basic_ostream& operator<<(std::basic_ostream& os, const month& m) { detail::low_level_fmt(os, m); if (!m.ok()) os << " is not a valid month"; return os; } // year CONSTCD11 inline year::year(int y) NOEXCEPT : y_(static_cast(y)) {} CONSTCD14 inline year& year::operator++() NOEXCEPT {++y_; return *this;} CONSTCD14 inline year year::operator++(int) NOEXCEPT {auto tmp(*this); ++(*this); return tmp;} CONSTCD14 inline year& year::operator--() NOEXCEPT {--y_; return *this;} CONSTCD14 inline year year::operator--(int) NOEXCEPT {auto tmp(*this); --(*this); return tmp;} CONSTCD14 inline year& year::operator+=(const years& y) NOEXCEPT {*this = *this + y; return *this;} CONSTCD14 inline year& year::operator-=(const years& y) NOEXCEPT {*this = *this - y; return *this;} CONSTCD11 inline year year::operator-() const NOEXCEPT {return year{-y_};} CONSTCD11 inline year year::operator+() const NOEXCEPT {return *this;} CONSTCD11 inline bool year::is_leap() const NOEXCEPT { return y_ % 4 == 0 && (y_ % 100 != 0 || y_ % 400 == 0); } CONSTCD11 inline year::operator int() const NOEXCEPT {return y_;} CONSTCD11 inline bool year::ok() const NOEXCEPT { return y_ != std::numeric_limits::min(); } CONSTCD11 inline bool operator==(const year& x, const year& y) NOEXCEPT { return static_cast(x) == static_cast(y); } CONSTCD11 inline bool operator!=(const year& x, const year& y) NOEXCEPT { return !(x == y); } CONSTCD11 inline bool operator<(const year& x, const year& y) NOEXCEPT { return static_cast(x) < static_cast(y); } CONSTCD11 inline bool operator>(const year& x, const year& y) NOEXCEPT { return y < x; } CONSTCD11 inline bool operator<=(const year& x, const year& y) NOEXCEPT { return !(y < x); } CONSTCD11 inline bool operator>=(const year& x, const year& y) NOEXCEPT { return !(x < y); } CONSTCD11 inline years operator-(const year& x, const year& y) NOEXCEPT { return years{static_cast(x) - static_cast(y)}; } CONSTCD11 inline year operator+(const year& x, const years& y) NOEXCEPT { return year{static_cast(x) + y.count()}; } CONSTCD11 inline year operator+(const years& x, const year& y) NOEXCEPT { return y + x; } CONSTCD11 inline year operator-(const year& x, const years& y) NOEXCEPT { return year{static_cast(x) - y.count()}; } namespace detail { template std::basic_ostream& low_level_fmt(std::basic_ostream& os, const year& y) { detail::save_ostream _(os); os.fill('0'); os.flags(std::ios::dec | std::ios::internal); os.width(4 + (y < year{0})); os.imbue(std::locale::classic()); os << static_cast(y); return os; } } // namespace detail template inline std::basic_ostream& operator<<(std::basic_ostream& os, const year& y) { detail::low_level_fmt(os, y); if (!y.ok()) os << " is not a valid year"; return os; } // weekday CONSTCD14 inline unsigned char weekday::weekday_from_days(int z) NOEXCEPT { auto u = static_cast(z); return static_cast(z >= -4 ? (u+4) % 7 : u % 7); } CONSTCD11 inline weekday::weekday(unsigned wd) NOEXCEPT : wd_(static_cast(wd != 7 ? wd : 0)) {} CONSTCD14 inline weekday::weekday(const sys_days& dp) NOEXCEPT : wd_(weekday_from_days(dp.time_since_epoch().count())) {} CONSTCD14 inline weekday::weekday(const local_days& dp) NOEXCEPT : wd_(weekday_from_days(dp.time_since_epoch().count())) {} CONSTCD14 inline weekday& weekday::operator++() NOEXCEPT {*this += days{1}; return *this;} CONSTCD14 inline weekday weekday::operator++(int) NOEXCEPT {auto tmp(*this); ++(*this); return tmp;} CONSTCD14 inline weekday& weekday::operator--() NOEXCEPT {*this -= days{1}; return *this;} CONSTCD14 inline weekday weekday::operator--(int) NOEXCEPT {auto tmp(*this); --(*this); return tmp;} CONSTCD14 inline weekday& weekday::operator+=(const days& d) NOEXCEPT { *this = *this + d; return *this; } CONSTCD14 inline weekday& weekday::operator-=(const days& d) NOEXCEPT { *this = *this - d; return *this; } CONSTCD11 inline bool weekday::ok() const NOEXCEPT {return wd_ <= 6;} CONSTCD11 inline unsigned weekday::c_encoding() const NOEXCEPT { return unsigned{wd_}; } CONSTCD11 inline unsigned weekday::iso_encoding() const NOEXCEPT { return unsigned{((wd_ == 0u) ? 7u : wd_)}; } CONSTCD11 inline bool operator==(const weekday& x, const weekday& y) NOEXCEPT { return x.wd_ == y.wd_; } CONSTCD11 inline bool operator!=(const weekday& x, const weekday& y) NOEXCEPT { return !(x == y); } CONSTCD14 inline days operator-(const weekday& x, const weekday& y) NOEXCEPT { auto const wdu = x.wd_ - y.wd_; auto const wk = (wdu >= 0 ? wdu : wdu-6) / 7; return days{wdu - wk * 7}; } CONSTCD14 inline weekday operator+(const weekday& x, const days& y) NOEXCEPT { auto const wdu = static_cast(static_cast(x.wd_)) + y.count(); auto const wk = (wdu >= 0 ? wdu : wdu-6) / 7; return weekday{static_cast(wdu - wk * 7)}; } CONSTCD14 inline weekday operator+(const days& x, const weekday& y) NOEXCEPT { return y + x; } CONSTCD14 inline weekday operator-(const weekday& x, const days& y) NOEXCEPT { return x + -y; } namespace detail { template std::basic_ostream& low_level_fmt(std::basic_ostream& os, const weekday& wd) { if (wd.ok()) { CharT fmt[] = {'%', 'a', 0}; os << format(fmt, wd); } else os << wd.c_encoding(); return os; } } // namespace detail template inline std::basic_ostream& operator<<(std::basic_ostream& os, const weekday& wd) { detail::low_level_fmt(os, wd); if (!wd.ok()) os << " is not a valid weekday"; return os; } #if !defined(_MSC_VER) || (_MSC_VER >= 1900) inline namespace literals { CONSTCD11 inline date::day operator ""_d(unsigned long long d) NOEXCEPT { return date::day{static_cast(d)}; } CONSTCD11 inline date::year operator ""_y(unsigned long long y) NOEXCEPT { return date::year(static_cast(y)); } #endif // !defined(_MSC_VER) || (_MSC_VER >= 1900) CONSTDATA date::last_spec last{}; CONSTDATA date::month jan{1}; CONSTDATA date::month feb{2}; CONSTDATA date::month mar{3}; CONSTDATA date::month apr{4}; CONSTDATA date::month may{5}; CONSTDATA date::month jun{6}; CONSTDATA date::month jul{7}; CONSTDATA date::month aug{8}; CONSTDATA date::month sep{9}; CONSTDATA date::month oct{10}; CONSTDATA date::month nov{11}; CONSTDATA date::month dec{12}; CONSTDATA date::weekday sun{0u}; CONSTDATA date::weekday mon{1u}; CONSTDATA date::weekday tue{2u}; CONSTDATA date::weekday wed{3u}; CONSTDATA date::weekday thu{4u}; CONSTDATA date::weekday fri{5u}; CONSTDATA date::weekday sat{6u}; #if !defined(_MSC_VER) || (_MSC_VER >= 1900) } // inline namespace literals #endif CONSTDATA date::month January{1}; CONSTDATA date::month February{2}; CONSTDATA date::month March{3}; CONSTDATA date::month April{4}; CONSTDATA date::month May{5}; CONSTDATA date::month June{6}; CONSTDATA date::month July{7}; CONSTDATA date::month August{8}; CONSTDATA date::month September{9}; CONSTDATA date::month October{10}; CONSTDATA date::month November{11}; CONSTDATA date::month December{12}; CONSTDATA date::weekday Monday{1}; CONSTDATA date::weekday Tuesday{2}; CONSTDATA date::weekday Wednesday{3}; CONSTDATA date::weekday Thursday{4}; CONSTDATA date::weekday Friday{5}; CONSTDATA date::weekday Saturday{6}; CONSTDATA date::weekday Sunday{7}; // weekday_indexed CONSTCD11 inline weekday weekday_indexed::weekday() const NOEXCEPT { return date::weekday{static_cast(wd_)}; } CONSTCD11 inline unsigned weekday_indexed::index() const NOEXCEPT {return index_;} CONSTCD11 inline bool weekday_indexed::ok() const NOEXCEPT { return weekday().ok() && 1 <= index_ && index_ <= 5; } #ifdef __GNUC__ # pragma GCC diagnostic push # pragma GCC diagnostic ignored "-Wconversion" #endif // __GNUC__ CONSTCD11 inline weekday_indexed::weekday_indexed(const date::weekday& wd, unsigned index) NOEXCEPT : wd_(static_cast(static_cast(wd.wd_))) , index_(static_cast(index)) {} #ifdef __GNUC__ # pragma GCC diagnostic pop #endif // __GNUC__ namespace detail { template std::basic_ostream& low_level_fmt(std::basic_ostream& os, const weekday_indexed& wdi) { return low_level_fmt(os, wdi.weekday()) << '[' << wdi.index() << ']'; } } // namespace detail template inline std::basic_ostream& operator<<(std::basic_ostream& os, const weekday_indexed& wdi) { detail::low_level_fmt(os, wdi); if (!wdi.ok()) os << " is not a valid weekday_indexed"; return os; } CONSTCD11 inline weekday_indexed weekday::operator[](unsigned index) const NOEXCEPT { return {*this, index}; } CONSTCD11 inline bool operator==(const weekday_indexed& x, const weekday_indexed& y) NOEXCEPT { return x.weekday() == y.weekday() && x.index() == y.index(); } CONSTCD11 inline bool operator!=(const weekday_indexed& x, const weekday_indexed& y) NOEXCEPT { return !(x == y); } // weekday_last CONSTCD11 inline date::weekday weekday_last::weekday() const NOEXCEPT {return wd_;} CONSTCD11 inline bool weekday_last::ok() const NOEXCEPT {return wd_.ok();} CONSTCD11 inline weekday_last::weekday_last(const date::weekday& wd) NOEXCEPT : wd_(wd) {} CONSTCD11 inline bool operator==(const weekday_last& x, const weekday_last& y) NOEXCEPT { return x.weekday() == y.weekday(); } CONSTCD11 inline bool operator!=(const weekday_last& x, const weekday_last& y) NOEXCEPT { return !(x == y); } namespace detail { template std::basic_ostream& low_level_fmt(std::basic_ostream& os, const weekday_last& wdl) { return low_level_fmt(os, wdl.weekday()) << "[last]"; } } // namespace detail template inline std::basic_ostream& operator<<(std::basic_ostream& os, const weekday_last& wdl) { detail::low_level_fmt(os, wdl); if (!wdl.ok()) os << " is not a valid weekday_last"; return os; } CONSTCD11 inline weekday_last weekday::operator[](last_spec) const NOEXCEPT { return weekday_last{*this}; } // year_month CONSTCD11 inline year_month::year_month(const date::year& y, const date::month& m) NOEXCEPT : y_(y) , m_(m) {} CONSTCD11 inline year year_month::year() const NOEXCEPT {return y_;} CONSTCD11 inline month year_month::month() const NOEXCEPT {return m_;} CONSTCD11 inline bool year_month::ok() const NOEXCEPT {return y_.ok() && m_.ok();} template CONSTCD14 inline year_month& year_month::operator+=(const months& dm) NOEXCEPT { *this = *this + dm; return *this; } template CONSTCD14 inline year_month& year_month::operator-=(const months& dm) NOEXCEPT { *this = *this - dm; return *this; } CONSTCD14 inline year_month& year_month::operator+=(const years& dy) NOEXCEPT { *this = *this + dy; return *this; } CONSTCD14 inline year_month& year_month::operator-=(const years& dy) NOEXCEPT { *this = *this - dy; return *this; } CONSTCD11 inline bool operator==(const year_month& x, const year_month& y) NOEXCEPT { return x.year() == y.year() && x.month() == y.month(); } CONSTCD11 inline bool operator!=(const year_month& x, const year_month& y) NOEXCEPT { return !(x == y); } CONSTCD11 inline bool operator<(const year_month& x, const year_month& y) NOEXCEPT { return x.year() < y.year() ? true : (x.year() > y.year() ? false : (x.month() < y.month())); } CONSTCD11 inline bool operator>(const year_month& x, const year_month& y) NOEXCEPT { return y < x; } CONSTCD11 inline bool operator<=(const year_month& x, const year_month& y) NOEXCEPT { return !(y < x); } CONSTCD11 inline bool operator>=(const year_month& x, const year_month& y) NOEXCEPT { return !(x < y); } template CONSTCD14 inline year_month operator+(const year_month& ym, const months& dm) NOEXCEPT { auto dmi = static_cast(static_cast(ym.month())) - 1 + dm.count(); auto dy = (dmi >= 0 ? dmi : dmi-11) / 12; dmi = dmi - dy * 12 + 1; return (ym.year() + years(dy)) / month(static_cast(dmi)); } template CONSTCD14 inline year_month operator+(const months& dm, const year_month& ym) NOEXCEPT { return ym + dm; } template CONSTCD14 inline year_month operator-(const year_month& ym, const months& dm) NOEXCEPT { return ym + -dm; } CONSTCD11 inline months operator-(const year_month& x, const year_month& y) NOEXCEPT { return (x.year() - y.year()) + months(static_cast(x.month()) - static_cast(y.month())); } CONSTCD11 inline year_month operator+(const year_month& ym, const years& dy) NOEXCEPT { return (ym.year() + dy) / ym.month(); } CONSTCD11 inline year_month operator+(const years& dy, const year_month& ym) NOEXCEPT { return ym + dy; } CONSTCD11 inline year_month operator-(const year_month& ym, const years& dy) NOEXCEPT { return ym + -dy; } namespace detail { template std::basic_ostream& low_level_fmt(std::basic_ostream& os, const year_month& ym) { low_level_fmt(os, ym.year()) << '/'; return low_level_fmt(os, ym.month()); } } // namespace detail template inline std::basic_ostream& operator<<(std::basic_ostream& os, const year_month& ym) { detail::low_level_fmt(os, ym); if (!ym.ok()) os << " is not a valid year_month"; return os; } // month_day CONSTCD11 inline month_day::month_day(const date::month& m, const date::day& d) NOEXCEPT : m_(m) , d_(d) {} CONSTCD11 inline date::month month_day::month() const NOEXCEPT {return m_;} CONSTCD11 inline date::day month_day::day() const NOEXCEPT {return d_;} CONSTCD14 inline bool month_day::ok() const NOEXCEPT { CONSTDATA date::day d[] = { date::day(31), date::day(29), date::day(31), date::day(30), date::day(31), date::day(30), date::day(31), date::day(31), date::day(30), date::day(31), date::day(30), date::day(31) }; return m_.ok() && date::day{1} <= d_ && d_ <= d[static_cast(m_)-1]; } CONSTCD11 inline bool operator==(const month_day& x, const month_day& y) NOEXCEPT { return x.month() == y.month() && x.day() == y.day(); } CONSTCD11 inline bool operator!=(const month_day& x, const month_day& y) NOEXCEPT { return !(x == y); } CONSTCD11 inline bool operator<(const month_day& x, const month_day& y) NOEXCEPT { return x.month() < y.month() ? true : (x.month() > y.month() ? false : (x.day() < y.day())); } CONSTCD11 inline bool operator>(const month_day& x, const month_day& y) NOEXCEPT { return y < x; } CONSTCD11 inline bool operator<=(const month_day& x, const month_day& y) NOEXCEPT { return !(y < x); } CONSTCD11 inline bool operator>=(const month_day& x, const month_day& y) NOEXCEPT { return !(x < y); } namespace detail { template std::basic_ostream& low_level_fmt(std::basic_ostream& os, const month_day& md) { low_level_fmt(os, md.month()) << '/'; return low_level_fmt(os, md.day()); } } // namespace detail template inline std::basic_ostream& operator<<(std::basic_ostream& os, const month_day& md) { detail::low_level_fmt(os, md); if (!md.ok()) os << " is not a valid month_day"; return os; } // month_day_last CONSTCD11 inline month month_day_last::month() const NOEXCEPT {return m_;} CONSTCD11 inline bool month_day_last::ok() const NOEXCEPT {return m_.ok();} CONSTCD11 inline month_day_last::month_day_last(const date::month& m) NOEXCEPT : m_(m) {} CONSTCD11 inline bool operator==(const month_day_last& x, const month_day_last& y) NOEXCEPT { return x.month() == y.month(); } CONSTCD11 inline bool operator!=(const month_day_last& x, const month_day_last& y) NOEXCEPT { return !(x == y); } CONSTCD11 inline bool operator<(const month_day_last& x, const month_day_last& y) NOEXCEPT { return x.month() < y.month(); } CONSTCD11 inline bool operator>(const month_day_last& x, const month_day_last& y) NOEXCEPT { return y < x; } CONSTCD11 inline bool operator<=(const month_day_last& x, const month_day_last& y) NOEXCEPT { return !(y < x); } CONSTCD11 inline bool operator>=(const month_day_last& x, const month_day_last& y) NOEXCEPT { return !(x < y); } namespace detail { template std::basic_ostream& low_level_fmt(std::basic_ostream& os, const month_day_last& mdl) { return low_level_fmt(os, mdl.month()) << "/last"; } } // namespace detail template inline std::basic_ostream& operator<<(std::basic_ostream& os, const month_day_last& mdl) { detail::low_level_fmt(os, mdl); if (!mdl.ok()) os << " is not a valid month_day_last"; return os; } // month_weekday CONSTCD11 inline month_weekday::month_weekday(const date::month& m, const date::weekday_indexed& wdi) NOEXCEPT : m_(m) , wdi_(wdi) {} CONSTCD11 inline month month_weekday::month() const NOEXCEPT {return m_;} CONSTCD11 inline weekday_indexed month_weekday::weekday_indexed() const NOEXCEPT { return wdi_; } CONSTCD11 inline bool month_weekday::ok() const NOEXCEPT { return m_.ok() && wdi_.ok(); } CONSTCD11 inline bool operator==(const month_weekday& x, const month_weekday& y) NOEXCEPT { return x.month() == y.month() && x.weekday_indexed() == y.weekday_indexed(); } CONSTCD11 inline bool operator!=(const month_weekday& x, const month_weekday& y) NOEXCEPT { return !(x == y); } namespace detail { template std::basic_ostream& low_level_fmt(std::basic_ostream& os, const month_weekday& mwd) { low_level_fmt(os, mwd.month()) << '/'; return low_level_fmt(os, mwd.weekday_indexed()); } } // namespace detail template inline std::basic_ostream& operator<<(std::basic_ostream& os, const month_weekday& mwd) { detail::low_level_fmt(os, mwd); if (!mwd.ok()) os << " is not a valid month_weekday"; return os; } // month_weekday_last CONSTCD11 inline month_weekday_last::month_weekday_last(const date::month& m, const date::weekday_last& wdl) NOEXCEPT : m_(m) , wdl_(wdl) {} CONSTCD11 inline month month_weekday_last::month() const NOEXCEPT {return m_;} CONSTCD11 inline weekday_last month_weekday_last::weekday_last() const NOEXCEPT { return wdl_; } CONSTCD11 inline bool month_weekday_last::ok() const NOEXCEPT { return m_.ok() && wdl_.ok(); } CONSTCD11 inline bool operator==(const month_weekday_last& x, const month_weekday_last& y) NOEXCEPT { return x.month() == y.month() && x.weekday_last() == y.weekday_last(); } CONSTCD11 inline bool operator!=(const month_weekday_last& x, const month_weekday_last& y) NOEXCEPT { return !(x == y); } namespace detail { template std::basic_ostream& low_level_fmt(std::basic_ostream& os, const month_weekday_last& mwdl) { low_level_fmt(os, mwdl.month()) << '/'; return low_level_fmt(os, mwdl.weekday_last()); } } // namespace detail template inline std::basic_ostream& operator<<(std::basic_ostream& os, const month_weekday_last& mwdl) { detail::low_level_fmt(os, mwdl); if (!mwdl.ok()) os << " is not a valid month_weekday_last"; return os; } // year_month_day_last CONSTCD11 inline year_month_day_last::year_month_day_last(const date::year& y, const date::month_day_last& mdl) NOEXCEPT : y_(y) , mdl_(mdl) {} template CONSTCD14 inline year_month_day_last& year_month_day_last::operator+=(const months& m) NOEXCEPT { *this = *this + m; return *this; } template CONSTCD14 inline year_month_day_last& year_month_day_last::operator-=(const months& m) NOEXCEPT { *this = *this - m; return *this; } CONSTCD14 inline year_month_day_last& year_month_day_last::operator+=(const years& y) NOEXCEPT { *this = *this + y; return *this; } CONSTCD14 inline year_month_day_last& year_month_day_last::operator-=(const years& y) NOEXCEPT { *this = *this - y; return *this; } CONSTCD11 inline year year_month_day_last::year() const NOEXCEPT {return y_;} CONSTCD11 inline month year_month_day_last::month() const NOEXCEPT {return mdl_.month();} CONSTCD11 inline month_day_last year_month_day_last::month_day_last() const NOEXCEPT { return mdl_; } CONSTCD14 inline day year_month_day_last::day() const NOEXCEPT { CONSTDATA date::day d[] = { date::day(31), date::day(28), date::day(31), date::day(30), date::day(31), date::day(30), date::day(31), date::day(31), date::day(30), date::day(31), date::day(30), date::day(31) }; return (month() != February || !y_.is_leap()) && mdl_.ok() ? d[static_cast(month()) - 1] : date::day{29}; } CONSTCD14 inline year_month_day_last::operator sys_days() const NOEXCEPT { return sys_days(year()/month()/day()); } CONSTCD14 inline year_month_day_last::operator local_days() const NOEXCEPT { return local_days(year()/month()/day()); } CONSTCD11 inline bool year_month_day_last::ok() const NOEXCEPT { return y_.ok() && mdl_.ok(); } CONSTCD11 inline bool operator==(const year_month_day_last& x, const year_month_day_last& y) NOEXCEPT { return x.year() == y.year() && x.month_day_last() == y.month_day_last(); } CONSTCD11 inline bool operator!=(const year_month_day_last& x, const year_month_day_last& y) NOEXCEPT { return !(x == y); } CONSTCD11 inline bool operator<(const year_month_day_last& x, const year_month_day_last& y) NOEXCEPT { return x.year() < y.year() ? true : (x.year() > y.year() ? false : (x.month_day_last() < y.month_day_last())); } CONSTCD11 inline bool operator>(const year_month_day_last& x, const year_month_day_last& y) NOEXCEPT { return y < x; } CONSTCD11 inline bool operator<=(const year_month_day_last& x, const year_month_day_last& y) NOEXCEPT { return !(y < x); } CONSTCD11 inline bool operator>=(const year_month_day_last& x, const year_month_day_last& y) NOEXCEPT { return !(x < y); } namespace detail { template std::basic_ostream& low_level_fmt(std::basic_ostream& os, const year_month_day_last& ymdl) { low_level_fmt(os, ymdl.year()) << '/'; return low_level_fmt(os, ymdl.month_day_last()); } } // namespace detail template inline std::basic_ostream& operator<<(std::basic_ostream& os, const year_month_day_last& ymdl) { detail::low_level_fmt(os, ymdl); if (!ymdl.ok()) os << " is not a valid year_month_day_last"; return os; } template CONSTCD14 inline year_month_day_last operator+(const year_month_day_last& ymdl, const months& dm) NOEXCEPT { return (ymdl.year() / ymdl.month() + dm) / last; } template CONSTCD14 inline year_month_day_last operator+(const months& dm, const year_month_day_last& ymdl) NOEXCEPT { return ymdl + dm; } template CONSTCD14 inline year_month_day_last operator-(const year_month_day_last& ymdl, const months& dm) NOEXCEPT { return ymdl + (-dm); } CONSTCD11 inline year_month_day_last operator+(const year_month_day_last& ymdl, const years& dy) NOEXCEPT { return {ymdl.year()+dy, ymdl.month_day_last()}; } CONSTCD11 inline year_month_day_last operator+(const years& dy, const year_month_day_last& ymdl) NOEXCEPT { return ymdl + dy; } CONSTCD11 inline year_month_day_last operator-(const year_month_day_last& ymdl, const years& dy) NOEXCEPT { return ymdl + (-dy); } // year_month_day CONSTCD11 inline year_month_day::year_month_day(const date::year& y, const date::month& m, const date::day& d) NOEXCEPT : y_(y) , m_(m) , d_(d) {} CONSTCD14 inline year_month_day::year_month_day(const year_month_day_last& ymdl) NOEXCEPT : y_(ymdl.year()) , m_(ymdl.month()) , d_(ymdl.day()) {} CONSTCD14 inline year_month_day::year_month_day(sys_days dp) NOEXCEPT : year_month_day(from_days(dp.time_since_epoch())) {} CONSTCD14 inline year_month_day::year_month_day(local_days dp) NOEXCEPT : year_month_day(from_days(dp.time_since_epoch())) {} CONSTCD11 inline year year_month_day::year() const NOEXCEPT {return y_;} CONSTCD11 inline month year_month_day::month() const NOEXCEPT {return m_;} CONSTCD11 inline day year_month_day::day() const NOEXCEPT {return d_;} template CONSTCD14 inline year_month_day& year_month_day::operator+=(const months& m) NOEXCEPT { *this = *this + m; return *this; } template CONSTCD14 inline year_month_day& year_month_day::operator-=(const months& m) NOEXCEPT { *this = *this - m; return *this; } CONSTCD14 inline year_month_day& year_month_day::operator+=(const years& y) NOEXCEPT { *this = *this + y; return *this; } CONSTCD14 inline year_month_day& year_month_day::operator-=(const years& y) NOEXCEPT { *this = *this - y; return *this; } CONSTCD14 inline days year_month_day::to_days() const NOEXCEPT { static_assert(std::numeric_limits::digits >= 18, "This algorithm has not been ported to a 16 bit unsigned integer"); static_assert(std::numeric_limits::digits >= 20, "This algorithm has not been ported to a 16 bit signed integer"); auto const y = static_cast(y_) - (m_ <= February); auto const m = static_cast(m_); auto const d = static_cast(d_); auto const era = (y >= 0 ? y : y-399) / 400; auto const yoe = static_cast(y - era * 400); // [0, 399] auto const doy = (153*(m > 2 ? m-3 : m+9) + 2)/5 + d-1; // [0, 365] auto const doe = yoe * 365 + yoe/4 - yoe/100 + doy; // [0, 146096] return days{era * 146097 + static_cast(doe) - 719468}; } CONSTCD14 inline year_month_day::operator sys_days() const NOEXCEPT { return sys_days{to_days()}; } CONSTCD14 inline year_month_day::operator local_days() const NOEXCEPT { return local_days{to_days()}; } CONSTCD14 inline bool year_month_day::ok() const NOEXCEPT { if (!(y_.ok() && m_.ok())) return false; return date::day{1} <= d_ && d_ <= (y_ / m_ / last).day(); } CONSTCD11 inline bool operator==(const year_month_day& x, const year_month_day& y) NOEXCEPT { return x.year() == y.year() && x.month() == y.month() && x.day() == y.day(); } CONSTCD11 inline bool operator!=(const year_month_day& x, const year_month_day& y) NOEXCEPT { return !(x == y); } CONSTCD11 inline bool operator<(const year_month_day& x, const year_month_day& y) NOEXCEPT { return x.year() < y.year() ? true : (x.year() > y.year() ? false : (x.month() < y.month() ? true : (x.month() > y.month() ? false : (x.day() < y.day())))); } CONSTCD11 inline bool operator>(const year_month_day& x, const year_month_day& y) NOEXCEPT { return y < x; } CONSTCD11 inline bool operator<=(const year_month_day& x, const year_month_day& y) NOEXCEPT { return !(y < x); } CONSTCD11 inline bool operator>=(const year_month_day& x, const year_month_day& y) NOEXCEPT { return !(x < y); } template inline std::basic_ostream& operator<<(std::basic_ostream& os, const year_month_day& ymd) { detail::save_ostream _(os); os.fill('0'); os.flags(std::ios::dec | std::ios::right); os.imbue(std::locale::classic()); os << static_cast(ymd.year()) << '-'; os.width(2); os << static_cast(ymd.month()) << '-'; os.width(2); os << static_cast(ymd.day()); if (!ymd.ok()) os << " is not a valid year_month_day"; return os; } CONSTCD14 inline year_month_day year_month_day::from_days(days dp) NOEXCEPT { static_assert(std::numeric_limits::digits >= 18, "This algorithm has not been ported to a 16 bit unsigned integer"); static_assert(std::numeric_limits::digits >= 20, "This algorithm has not been ported to a 16 bit signed integer"); auto const z = dp.count() + 719468; auto const era = (z >= 0 ? z : z - 146096) / 146097; auto const doe = static_cast(z - era * 146097); // [0, 146096] auto const yoe = (doe - doe/1460 + doe/36524 - doe/146096) / 365; // [0, 399] auto const y = static_cast(yoe) + era * 400; auto const doy = doe - (365*yoe + yoe/4 - yoe/100); // [0, 365] auto const mp = (5*doy + 2)/153; // [0, 11] auto const d = doy - (153*mp+2)/5 + 1; // [1, 31] auto const m = mp < 10 ? mp+3 : mp-9; // [1, 12] return year_month_day{date::year{y + (m <= 2)}, date::month(m), date::day(d)}; } template CONSTCD14 inline year_month_day operator+(const year_month_day& ymd, const months& dm) NOEXCEPT { return (ymd.year() / ymd.month() + dm) / ymd.day(); } template CONSTCD14 inline year_month_day operator+(const months& dm, const year_month_day& ymd) NOEXCEPT { return ymd + dm; } template CONSTCD14 inline year_month_day operator-(const year_month_day& ymd, const months& dm) NOEXCEPT { return ymd + (-dm); } CONSTCD11 inline year_month_day operator+(const year_month_day& ymd, const years& dy) NOEXCEPT { return (ymd.year() + dy) / ymd.month() / ymd.day(); } CONSTCD11 inline year_month_day operator+(const years& dy, const year_month_day& ymd) NOEXCEPT { return ymd + dy; } CONSTCD11 inline year_month_day operator-(const year_month_day& ymd, const years& dy) NOEXCEPT { return ymd + (-dy); } // year_month_weekday CONSTCD11 inline year_month_weekday::year_month_weekday(const date::year& y, const date::month& m, const date::weekday_indexed& wdi) NOEXCEPT : y_(y) , m_(m) , wdi_(wdi) {} CONSTCD14 inline year_month_weekday::year_month_weekday(const sys_days& dp) NOEXCEPT : year_month_weekday(from_days(dp.time_since_epoch())) {} CONSTCD14 inline year_month_weekday::year_month_weekday(const local_days& dp) NOEXCEPT : year_month_weekday(from_days(dp.time_since_epoch())) {} template CONSTCD14 inline year_month_weekday& year_month_weekday::operator+=(const months& m) NOEXCEPT { *this = *this + m; return *this; } template CONSTCD14 inline year_month_weekday& year_month_weekday::operator-=(const months& m) NOEXCEPT { *this = *this - m; return *this; } CONSTCD14 inline year_month_weekday& year_month_weekday::operator+=(const years& y) NOEXCEPT { *this = *this + y; return *this; } CONSTCD14 inline year_month_weekday& year_month_weekday::operator-=(const years& y) NOEXCEPT { *this = *this - y; return *this; } CONSTCD11 inline year year_month_weekday::year() const NOEXCEPT {return y_;} CONSTCD11 inline month year_month_weekday::month() const NOEXCEPT {return m_;} CONSTCD11 inline weekday year_month_weekday::weekday() const NOEXCEPT { return wdi_.weekday(); } CONSTCD11 inline unsigned year_month_weekday::index() const NOEXCEPT { return wdi_.index(); } CONSTCD11 inline weekday_indexed year_month_weekday::weekday_indexed() const NOEXCEPT { return wdi_; } CONSTCD14 inline year_month_weekday::operator sys_days() const NOEXCEPT { return sys_days{to_days()}; } CONSTCD14 inline year_month_weekday::operator local_days() const NOEXCEPT { return local_days{to_days()}; } CONSTCD14 inline bool year_month_weekday::ok() const NOEXCEPT { if (!y_.ok() || !m_.ok() || !wdi_.weekday().ok() || wdi_.index() < 1) return false; if (wdi_.index() <= 4) return true; auto d2 = wdi_.weekday() - date::weekday(static_cast(y_/m_/1)) + days((wdi_.index()-1)*7 + 1); return static_cast(d2.count()) <= static_cast((y_/m_/last).day()); } CONSTCD14 inline year_month_weekday year_month_weekday::from_days(days d) NOEXCEPT { sys_days dp{d}; auto const wd = date::weekday(dp); auto const ymd = year_month_day(dp); return {ymd.year(), ymd.month(), wd[(static_cast(ymd.day())-1)/7+1]}; } CONSTCD14 inline days year_month_weekday::to_days() const NOEXCEPT { auto d = sys_days(y_/m_/1); return (d + (wdi_.weekday() - date::weekday(d) + days{(wdi_.index()-1)*7}) ).time_since_epoch(); } CONSTCD11 inline bool operator==(const year_month_weekday& x, const year_month_weekday& y) NOEXCEPT { return x.year() == y.year() && x.month() == y.month() && x.weekday_indexed() == y.weekday_indexed(); } CONSTCD11 inline bool operator!=(const year_month_weekday& x, const year_month_weekday& y) NOEXCEPT { return !(x == y); } template inline std::basic_ostream& operator<<(std::basic_ostream& os, const year_month_weekday& ymwdi) { detail::low_level_fmt(os, ymwdi.year()) << '/'; detail::low_level_fmt(os, ymwdi.month()) << '/'; detail::low_level_fmt(os, ymwdi.weekday_indexed()); if (!ymwdi.ok()) os << " is not a valid year_month_weekday"; return os; } template CONSTCD14 inline year_month_weekday operator+(const year_month_weekday& ymwd, const months& dm) NOEXCEPT { return (ymwd.year() / ymwd.month() + dm) / ymwd.weekday_indexed(); } template CONSTCD14 inline year_month_weekday operator+(const months& dm, const year_month_weekday& ymwd) NOEXCEPT { return ymwd + dm; } template CONSTCD14 inline year_month_weekday operator-(const year_month_weekday& ymwd, const months& dm) NOEXCEPT { return ymwd + (-dm); } CONSTCD11 inline year_month_weekday operator+(const year_month_weekday& ymwd, const years& dy) NOEXCEPT { return {ymwd.year()+dy, ymwd.month(), ymwd.weekday_indexed()}; } CONSTCD11 inline year_month_weekday operator+(const years& dy, const year_month_weekday& ymwd) NOEXCEPT { return ymwd + dy; } CONSTCD11 inline year_month_weekday operator-(const year_month_weekday& ymwd, const years& dy) NOEXCEPT { return ymwd + (-dy); } // year_month_weekday_last CONSTCD11 inline year_month_weekday_last::year_month_weekday_last(const date::year& y, const date::month& m, const date::weekday_last& wdl) NOEXCEPT : y_(y) , m_(m) , wdl_(wdl) {} template CONSTCD14 inline year_month_weekday_last& year_month_weekday_last::operator+=(const months& m) NOEXCEPT { *this = *this + m; return *this; } template CONSTCD14 inline year_month_weekday_last& year_month_weekday_last::operator-=(const months& m) NOEXCEPT { *this = *this - m; return *this; } CONSTCD14 inline year_month_weekday_last& year_month_weekday_last::operator+=(const years& y) NOEXCEPT { *this = *this + y; return *this; } CONSTCD14 inline year_month_weekday_last& year_month_weekday_last::operator-=(const years& y) NOEXCEPT { *this = *this - y; return *this; } CONSTCD11 inline year year_month_weekday_last::year() const NOEXCEPT {return y_;} CONSTCD11 inline month year_month_weekday_last::month() const NOEXCEPT {return m_;} CONSTCD11 inline weekday year_month_weekday_last::weekday() const NOEXCEPT { return wdl_.weekday(); } CONSTCD11 inline weekday_last year_month_weekday_last::weekday_last() const NOEXCEPT { return wdl_; } CONSTCD14 inline year_month_weekday_last::operator sys_days() const NOEXCEPT { return sys_days{to_days()}; } CONSTCD14 inline year_month_weekday_last::operator local_days() const NOEXCEPT { return local_days{to_days()}; } CONSTCD11 inline bool year_month_weekday_last::ok() const NOEXCEPT { return y_.ok() && m_.ok() && wdl_.ok(); } CONSTCD14 inline days year_month_weekday_last::to_days() const NOEXCEPT { auto const d = sys_days(y_/m_/last); return (d - (date::weekday{d} - wdl_.weekday())).time_since_epoch(); } CONSTCD11 inline bool operator==(const year_month_weekday_last& x, const year_month_weekday_last& y) NOEXCEPT { return x.year() == y.year() && x.month() == y.month() && x.weekday_last() == y.weekday_last(); } CONSTCD11 inline bool operator!=(const year_month_weekday_last& x, const year_month_weekday_last& y) NOEXCEPT { return !(x == y); } template inline std::basic_ostream& operator<<(std::basic_ostream& os, const year_month_weekday_last& ymwdl) { detail::low_level_fmt(os, ymwdl.year()) << '/'; detail::low_level_fmt(os, ymwdl.month()) << '/'; detail::low_level_fmt(os, ymwdl.weekday_last()); if (!ymwdl.ok()) os << " is not a valid year_month_weekday_last"; return os; } template CONSTCD14 inline year_month_weekday_last operator+(const year_month_weekday_last& ymwdl, const months& dm) NOEXCEPT { return (ymwdl.year() / ymwdl.month() + dm) / ymwdl.weekday_last(); } template CONSTCD14 inline year_month_weekday_last operator+(const months& dm, const year_month_weekday_last& ymwdl) NOEXCEPT { return ymwdl + dm; } template CONSTCD14 inline year_month_weekday_last operator-(const year_month_weekday_last& ymwdl, const months& dm) NOEXCEPT { return ymwdl + (-dm); } CONSTCD11 inline year_month_weekday_last operator+(const year_month_weekday_last& ymwdl, const years& dy) NOEXCEPT { return {ymwdl.year()+dy, ymwdl.month(), ymwdl.weekday_last()}; } CONSTCD11 inline year_month_weekday_last operator+(const years& dy, const year_month_weekday_last& ymwdl) NOEXCEPT { return ymwdl + dy; } CONSTCD11 inline year_month_weekday_last operator-(const year_month_weekday_last& ymwdl, const years& dy) NOEXCEPT { return ymwdl + (-dy); } // year_month from operator/() CONSTCD11 inline year_month operator/(const year& y, const month& m) NOEXCEPT { return {y, m}; } CONSTCD11 inline year_month operator/(const year& y, int m) NOEXCEPT { return y / month(static_cast(m)); } // month_day from operator/() CONSTCD11 inline month_day operator/(const month& m, const day& d) NOEXCEPT { return {m, d}; } CONSTCD11 inline month_day operator/(const day& d, const month& m) NOEXCEPT { return m / d; } CONSTCD11 inline month_day operator/(const month& m, int d) NOEXCEPT { return m / day(static_cast(d)); } CONSTCD11 inline month_day operator/(int m, const day& d) NOEXCEPT { return month(static_cast(m)) / d; } CONSTCD11 inline month_day operator/(const day& d, int m) NOEXCEPT {return m / d;} // month_day_last from operator/() CONSTCD11 inline month_day_last operator/(const month& m, last_spec) NOEXCEPT { return month_day_last{m}; } CONSTCD11 inline month_day_last operator/(last_spec, const month& m) NOEXCEPT { return m/last; } CONSTCD11 inline month_day_last operator/(int m, last_spec) NOEXCEPT { return month(static_cast(m))/last; } CONSTCD11 inline month_day_last operator/(last_spec, int m) NOEXCEPT { return m/last; } // month_weekday from operator/() CONSTCD11 inline month_weekday operator/(const month& m, const weekday_indexed& wdi) NOEXCEPT { return {m, wdi}; } CONSTCD11 inline month_weekday operator/(const weekday_indexed& wdi, const month& m) NOEXCEPT { return m / wdi; } CONSTCD11 inline month_weekday operator/(int m, const weekday_indexed& wdi) NOEXCEPT { return month(static_cast(m)) / wdi; } CONSTCD11 inline month_weekday operator/(const weekday_indexed& wdi, int m) NOEXCEPT { return m / wdi; } // month_weekday_last from operator/() CONSTCD11 inline month_weekday_last operator/(const month& m, const weekday_last& wdl) NOEXCEPT { return {m, wdl}; } CONSTCD11 inline month_weekday_last operator/(const weekday_last& wdl, const month& m) NOEXCEPT { return m / wdl; } CONSTCD11 inline month_weekday_last operator/(int m, const weekday_last& wdl) NOEXCEPT { return month(static_cast(m)) / wdl; } CONSTCD11 inline month_weekday_last operator/(const weekday_last& wdl, int m) NOEXCEPT { return m / wdl; } // year_month_day from operator/() CONSTCD11 inline year_month_day operator/(const year_month& ym, const day& d) NOEXCEPT { return {ym.year(), ym.month(), d}; } CONSTCD11 inline year_month_day operator/(const year_month& ym, int d) NOEXCEPT { return ym / day(static_cast(d)); } CONSTCD11 inline year_month_day operator/(const year& y, const month_day& md) NOEXCEPT { return y / md.month() / md.day(); } CONSTCD11 inline year_month_day operator/(int y, const month_day& md) NOEXCEPT { return year(y) / md; } CONSTCD11 inline year_month_day operator/(const month_day& md, const year& y) NOEXCEPT { return y / md; } CONSTCD11 inline year_month_day operator/(const month_day& md, int y) NOEXCEPT { return year(y) / md; } // year_month_day_last from operator/() CONSTCD11 inline year_month_day_last operator/(const year_month& ym, last_spec) NOEXCEPT { return {ym.year(), month_day_last{ym.month()}}; } CONSTCD11 inline year_month_day_last operator/(const year& y, const month_day_last& mdl) NOEXCEPT { return {y, mdl}; } CONSTCD11 inline year_month_day_last operator/(int y, const month_day_last& mdl) NOEXCEPT { return year(y) / mdl; } CONSTCD11 inline year_month_day_last operator/(const month_day_last& mdl, const year& y) NOEXCEPT { return y / mdl; } CONSTCD11 inline year_month_day_last operator/(const month_day_last& mdl, int y) NOEXCEPT { return year(y) / mdl; } // year_month_weekday from operator/() CONSTCD11 inline year_month_weekday operator/(const year_month& ym, const weekday_indexed& wdi) NOEXCEPT { return {ym.year(), ym.month(), wdi}; } CONSTCD11 inline year_month_weekday operator/(const year& y, const month_weekday& mwd) NOEXCEPT { return {y, mwd.month(), mwd.weekday_indexed()}; } CONSTCD11 inline year_month_weekday operator/(int y, const month_weekday& mwd) NOEXCEPT { return year(y) / mwd; } CONSTCD11 inline year_month_weekday operator/(const month_weekday& mwd, const year& y) NOEXCEPT { return y / mwd; } CONSTCD11 inline year_month_weekday operator/(const month_weekday& mwd, int y) NOEXCEPT { return year(y) / mwd; } // year_month_weekday_last from operator/() CONSTCD11 inline year_month_weekday_last operator/(const year_month& ym, const weekday_last& wdl) NOEXCEPT { return {ym.year(), ym.month(), wdl}; } CONSTCD11 inline year_month_weekday_last operator/(const year& y, const month_weekday_last& mwdl) NOEXCEPT { return {y, mwdl.month(), mwdl.weekday_last()}; } CONSTCD11 inline year_month_weekday_last operator/(int y, const month_weekday_last& mwdl) NOEXCEPT { return year(y) / mwdl; } CONSTCD11 inline year_month_weekday_last operator/(const month_weekday_last& mwdl, const year& y) NOEXCEPT { return y / mwdl; } CONSTCD11 inline year_month_weekday_last operator/(const month_weekday_last& mwdl, int y) NOEXCEPT { return year(y) / mwdl; } template struct fields; template std::basic_ostream& to_stream(std::basic_ostream& os, const CharT* fmt, const fields& fds, const std::string* abbrev = nullptr, const std::chrono::seconds* offset_sec = nullptr); template std::basic_istream& from_stream(std::basic_istream& is, const CharT* fmt, fields& fds, std::basic_string* abbrev = nullptr, std::chrono::minutes* offset = nullptr); // hh_mm_ss namespace detail { struct undocumented {explicit undocumented() = default;}; // width::value is the number of fractional decimal digits in 1/n // width<0>::value and width<1>::value are defined to be 0 // If 1/n takes more than 18 fractional decimal digits, // the result is truncated to 19. // Example: width<2>::value == 1 // Example: width<3>::value == 19 // Example: width<4>::value == 2 // Example: width<10>::value == 1 // Example: width<1000>::value == 3 template struct width { static_assert(d > 0, "width called with zero denominator"); static CONSTDATA unsigned value = 1 + width::value; }; template struct width { static CONSTDATA unsigned value = 0; }; template struct static_pow10 { private: static CONSTDATA std::uint64_t h = static_pow10::value; public: static CONSTDATA std::uint64_t value = h * h * (exp % 2 ? 10 : 1); }; template <> struct static_pow10<0> { static CONSTDATA std::uint64_t value = 1; }; template class decimal_format_seconds { using CT = typename std::common_type::type; using rep = typename CT::rep; static unsigned CONSTDATA trial_width = detail::width::value; public: static unsigned CONSTDATA width = trial_width < 19 ? trial_width : 6u; using precision = std::chrono::duration::value>>; private: std::chrono::seconds s_; precision sub_s_; public: CONSTCD11 decimal_format_seconds() : s_() , sub_s_() {} CONSTCD11 explicit decimal_format_seconds(const Duration& d) NOEXCEPT : s_(std::chrono::duration_cast(d)) , sub_s_(std::chrono::duration_cast(d - s_)) {} CONSTCD14 std::chrono::seconds& seconds() NOEXCEPT {return s_;} CONSTCD11 std::chrono::seconds seconds() const NOEXCEPT {return s_;} CONSTCD11 precision subseconds() const NOEXCEPT {return sub_s_;} CONSTCD14 precision to_duration() const NOEXCEPT { return s_ + sub_s_; } CONSTCD11 bool in_conventional_range() const NOEXCEPT { return sub_s_ < std::chrono::seconds{1} && s_ < std::chrono::minutes{1}; } template friend std::basic_ostream& operator<<(std::basic_ostream& os, const decimal_format_seconds& x) { return x.print(os, std::chrono::treat_as_floating_point{}); } template std::basic_ostream& print(std::basic_ostream& os, std::true_type) const { date::detail::save_ostream _(os); std::chrono::duration d = s_ + sub_s_; if (d < std::chrono::seconds{10}) os << '0'; os.precision(width+6); os << std::fixed << d.count(); return os; } template std::basic_ostream& print(std::basic_ostream& os, std::false_type) const { date::detail::save_ostream _(os); os.fill('0'); os.flags(std::ios::dec | std::ios::right); os.width(2); os << s_.count(); if (width > 0) { #if !ONLY_C_LOCALE os << std::use_facet>(os.getloc()).decimal_point(); #else os << '.'; #endif date::detail::save_ostream _s(os); os.imbue(std::locale::classic()); os.width(width); os << sub_s_.count(); } return os; } }; template inline CONSTCD11 typename std::enable_if < std::numeric_limits::is_signed, std::chrono::duration >::type abs(std::chrono::duration d) { return d >= d.zero() ? +d : -d; } template inline CONSTCD11 typename std::enable_if < !std::numeric_limits::is_signed, std::chrono::duration >::type abs(std::chrono::duration d) { return d; } } // namespace detail template class hh_mm_ss { using dfs = detail::decimal_format_seconds::type>; std::chrono::hours h_; std::chrono::minutes m_; dfs s_; bool neg_; public: static unsigned CONSTDATA fractional_width = dfs::width; using precision = typename dfs::precision; CONSTCD11 hh_mm_ss() NOEXCEPT : hh_mm_ss(Duration::zero()) {} CONSTCD11 explicit hh_mm_ss(Duration d) NOEXCEPT : h_(std::chrono::duration_cast(detail::abs(d))) , m_(std::chrono::duration_cast(detail::abs(d)) - h_) , s_(detail::abs(d) - h_ - m_) , neg_(d < Duration::zero()) {} CONSTCD11 std::chrono::hours hours() const NOEXCEPT {return h_;} CONSTCD11 std::chrono::minutes minutes() const NOEXCEPT {return m_;} CONSTCD11 std::chrono::seconds seconds() const NOEXCEPT {return s_.seconds();} CONSTCD14 std::chrono::seconds& seconds(detail::undocumented) NOEXCEPT {return s_.seconds();} CONSTCD11 precision subseconds() const NOEXCEPT {return s_.subseconds();} CONSTCD11 bool is_negative() const NOEXCEPT {return neg_;} CONSTCD11 explicit operator precision() const NOEXCEPT {return to_duration();} CONSTCD11 precision to_duration() const NOEXCEPT {return (s_.to_duration() + m_ + h_) * (1-2*neg_);} CONSTCD11 bool in_conventional_range() const NOEXCEPT { return !neg_ && h_ < days{1} && m_ < std::chrono::hours{1} && s_.in_conventional_range(); } private: template friend std::basic_ostream& operator<<(std::basic_ostream& os, hh_mm_ss const& tod) { if (tod.is_negative()) os << '-'; if (tod.h_ < std::chrono::hours{10}) os << '0'; os << tod.h_.count() << ':'; if (tod.m_ < std::chrono::minutes{10}) os << '0'; os << tod.m_.count() << ':' << tod.s_; return os; } template friend std::basic_ostream& date::to_stream(std::basic_ostream& os, const CharT* fmt, const fields& fds, const std::string* abbrev, const std::chrono::seconds* offset_sec); template friend std::basic_istream& date::from_stream(std::basic_istream& is, const CharT* fmt, fields& fds, std::basic_string* abbrev, std::chrono::minutes* offset); }; inline CONSTCD14 bool is_am(std::chrono::hours const& h) NOEXCEPT { using std::chrono::hours; return hours{0} <= h && h < hours{12}; } inline CONSTCD14 bool is_pm(std::chrono::hours const& h) NOEXCEPT { using std::chrono::hours; return hours{12} <= h && h < hours{24}; } inline CONSTCD14 std::chrono::hours make12(std::chrono::hours h) NOEXCEPT { using std::chrono::hours; if (h < hours{12}) { if (h == hours{0}) h = hours{12}; } else { if (h != hours{12}) h = h - hours{12}; } return h; } inline CONSTCD14 std::chrono::hours make24(std::chrono::hours h, bool is_pm) NOEXCEPT { using std::chrono::hours; if (is_pm) { if (h != hours{12}) h = h + hours{12}; } else if (h == hours{12}) h = hours{0}; return h; } template using time_of_day = hh_mm_ss; template CONSTCD11 inline hh_mm_ss> make_time(const std::chrono::duration& d) { return hh_mm_ss>(d); } template inline typename std::enable_if < !std::is_convertible::value, std::basic_ostream& >::type operator<<(std::basic_ostream& os, const sys_time& tp) { auto const dp = date::floor(tp); return os << year_month_day(dp) << ' ' << make_time(tp-dp); } template inline std::basic_ostream& operator<<(std::basic_ostream& os, const sys_days& dp) { return os << year_month_day(dp); } template inline std::basic_ostream& operator<<(std::basic_ostream& os, const local_time& ut) { return (date::operator<<(os, sys_time{ut.time_since_epoch()})); } namespace detail { template class string_literal; template inline CONSTCD14 string_literal::type, N1 + N2 - 1> operator+(const string_literal& x, const string_literal& y) NOEXCEPT; template class string_literal { CharT p_[N]; CONSTCD11 string_literal() NOEXCEPT : p_{} {} public: using const_iterator = const CharT*; string_literal(string_literal const&) = default; string_literal& operator=(string_literal const&) = delete; template ::type> CONSTCD11 string_literal(CharT c) NOEXCEPT : p_{c} { } template ::type> CONSTCD11 string_literal(CharT c1, CharT c2) NOEXCEPT : p_{c1, c2} { } template ::type> CONSTCD11 string_literal(CharT c1, CharT c2, CharT c3) NOEXCEPT : p_{c1, c2, c3} { } CONSTCD14 string_literal(const CharT(&a)[N]) NOEXCEPT : p_{} { for (std::size_t i = 0; i < N; ++i) p_[i] = a[i]; } template ::type> CONSTCD14 string_literal(const char(&a)[N]) NOEXCEPT : p_{} { for (std::size_t i = 0; i < N; ++i) p_[i] = a[i]; } template ::value>::type> CONSTCD14 string_literal(string_literal const& a) NOEXCEPT : p_{} { for (std::size_t i = 0; i < N; ++i) p_[i] = a[i]; } CONSTCD11 const CharT* data() const NOEXCEPT {return p_;} CONSTCD11 std::size_t size() const NOEXCEPT {return N-1;} CONSTCD11 const_iterator begin() const NOEXCEPT {return p_;} CONSTCD11 const_iterator end() const NOEXCEPT {return p_ + N-1;} CONSTCD11 CharT const& operator[](std::size_t n) const NOEXCEPT { return p_[n]; } template friend std::basic_ostream& operator<<(std::basic_ostream& os, const string_literal& s) { return os << s.p_; } template friend CONSTCD14 string_literal::type, N1 + N2 - 1> operator+(const string_literal& x, const string_literal& y) NOEXCEPT; }; template CONSTCD11 inline string_literal operator+(const string_literal& x, const string_literal& y) NOEXCEPT { return string_literal(x[0], y[0]); } template CONSTCD11 inline string_literal operator+(const string_literal& x, const string_literal& y) NOEXCEPT { return string_literal(x[0], x[1], y[0]); } template CONSTCD14 inline string_literal::type, N1 + N2 - 1> operator+(const string_literal& x, const string_literal& y) NOEXCEPT { using CT = typename std::conditional::type; string_literal r; std::size_t i = 0; for (; i < N1-1; ++i) r.p_[i] = CT(x.p_[i]); for (std::size_t j = 0; j < N2; ++j, ++i) r.p_[i] = CT(y.p_[j]); return r; } template inline std::basic_string operator+(std::basic_string x, const string_literal& y) { x.append(y.data(), y.size()); return x; } #if __cplusplus >= 201402 && (!defined(__EDG_VERSION__) || __EDG_VERSION__ > 411) \ && (!defined(__SUNPRO_CC) || __SUNPRO_CC > 0x5150) template ::value || std::is_same::value || std::is_same::value || std::is_same::value>> CONSTCD14 inline string_literal msl(CharT c) NOEXCEPT { return string_literal{c}; } CONSTCD14 inline std::size_t to_string_len(std::intmax_t i) { std::size_t r = 0; do { i /= 10; ++r; } while (i > 0); return r; } template CONSTCD14 inline std::enable_if_t < N < 10, string_literal > msl() NOEXCEPT { return msl(char(N % 10 + '0')); } template CONSTCD14 inline std::enable_if_t < 10 <= N, string_literal > msl() NOEXCEPT { return msl() + msl(char(N % 10 + '0')); } template CONSTCD14 inline std::enable_if_t < std::ratio::type::den != 1, string_literal::type::num) + to_string_len(std::ratio::type::den) + 4> > msl(std::ratio) NOEXCEPT { using R = typename std::ratio::type; return msl(CharT{'['}) + msl() + msl(CharT{'/'}) + msl() + msl(CharT{']'}); } template CONSTCD14 inline std::enable_if_t < std::ratio::type::den == 1, string_literal::type::num) + 3> > msl(std::ratio) NOEXCEPT { using R = typename std::ratio::type; return msl(CharT{'['}) + msl() + msl(CharT{']'}); } #else // __cplusplus < 201402 || (defined(__EDG_VERSION__) && __EDG_VERSION__ <= 411) inline std::string to_string(std::uint64_t x) { return std::to_string(x); } template inline std::basic_string to_string(std::uint64_t x) { auto y = std::to_string(x); return std::basic_string(y.begin(), y.end()); } template inline typename std::enable_if < std::ratio::type::den != 1, std::basic_string >::type msl(std::ratio) { using R = typename std::ratio::type; return std::basic_string(1, '[') + to_string(R::num) + CharT{'/'} + to_string(R::den) + CharT{']'}; } template inline typename std::enable_if < std::ratio::type::den == 1, std::basic_string >::type msl(std::ratio) { using R = typename std::ratio::type; return std::basic_string(1, '[') + to_string(R::num) + CharT{']'}; } #endif // __cplusplus < 201402 || (defined(__EDG_VERSION__) && __EDG_VERSION__ <= 411) template CONSTCD11 inline string_literal msl(std::atto) NOEXCEPT { return string_literal{'a'}; } template CONSTCD11 inline string_literal msl(std::femto) NOEXCEPT { return string_literal{'f'}; } template CONSTCD11 inline string_literal msl(std::pico) NOEXCEPT { return string_literal{'p'}; } template CONSTCD11 inline string_literal msl(std::nano) NOEXCEPT { return string_literal{'n'}; } template CONSTCD11 inline typename std::enable_if < std::is_same::value, string_literal >::type msl(std::micro) NOEXCEPT { return string_literal{'\xC2', '\xB5'}; } template CONSTCD11 inline typename std::enable_if < !std::is_same::value, string_literal >::type msl(std::micro) NOEXCEPT { return string_literal{CharT{static_cast('\xB5')}}; } template CONSTCD11 inline string_literal msl(std::milli) NOEXCEPT { return string_literal{'m'}; } template CONSTCD11 inline string_literal msl(std::centi) NOEXCEPT { return string_literal{'c'}; } template CONSTCD11 inline string_literal msl(std::deca) NOEXCEPT { return string_literal{'d', 'a'}; } template CONSTCD11 inline string_literal msl(std::deci) NOEXCEPT { return string_literal{'d'}; } template CONSTCD11 inline string_literal msl(std::hecto) NOEXCEPT { return string_literal{'h'}; } template CONSTCD11 inline string_literal msl(std::kilo) NOEXCEPT { return string_literal{'k'}; } template CONSTCD11 inline string_literal msl(std::mega) NOEXCEPT { return string_literal{'M'}; } template CONSTCD11 inline string_literal msl(std::giga) NOEXCEPT { return string_literal{'G'}; } template CONSTCD11 inline string_literal msl(std::tera) NOEXCEPT { return string_literal{'T'}; } template CONSTCD11 inline string_literal msl(std::peta) NOEXCEPT { return string_literal{'P'}; } template CONSTCD11 inline string_literal msl(std::exa) NOEXCEPT { return string_literal{'E'}; } template CONSTCD11 inline auto get_units(Period p) -> decltype(msl(p) + string_literal{'s'}) { return msl(p) + string_literal{'s'}; } template CONSTCD11 inline string_literal get_units(std::ratio<1>) { return string_literal{'s'}; } template CONSTCD11 inline string_literal get_units(std::ratio<3600>) { return string_literal{'h'}; } template CONSTCD11 inline string_literal get_units(std::ratio<60>) { return string_literal{'m', 'i', 'n'}; } template CONSTCD11 inline string_literal get_units(std::ratio<86400>) { return string_literal{'d'}; } template > struct make_string; template <> struct make_string { template static std::string from(Rep n) { return std::to_string(n); } }; template struct make_string { template static std::basic_string from(Rep n) { auto s = std::to_string(n); return std::basic_string(s.begin(), s.end()); } }; template <> struct make_string { template static std::wstring from(Rep n) { return std::to_wstring(n); } }; template struct make_string { template static std::basic_string from(Rep n) { auto s = std::to_wstring(n); return std::basic_string(s.begin(), s.end()); } }; } // namespace detail // to_stream CONSTDATA year nanyear{-32768}; template struct fields { year_month_day ymd{nanyear/0/0}; weekday wd{8u}; hh_mm_ss tod{}; bool has_tod = false; #if !defined(__clang__) && defined(__GNUC__) && (__GNUC__ * 100 + __GNUC_MINOR__ <= 409) fields() : ymd{nanyear/0/0}, wd{8u}, tod{}, has_tod{false} {} #else fields() = default; #endif fields(year_month_day ymd_) : ymd(ymd_) {} fields(weekday wd_) : wd(wd_) {} fields(hh_mm_ss tod_) : tod(tod_), has_tod(true) {} fields(year_month_day ymd_, weekday wd_) : ymd(ymd_), wd(wd_) {} fields(year_month_day ymd_, hh_mm_ss tod_) : ymd(ymd_), tod(tod_), has_tod(true) {} fields(weekday wd_, hh_mm_ss tod_) : wd(wd_), tod(tod_), has_tod(true) {} fields(year_month_day ymd_, weekday wd_, hh_mm_ss tod_) : ymd(ymd_) , wd(wd_) , tod(tod_) , has_tod(true) {} }; namespace detail { template unsigned extract_weekday(std::basic_ostream& os, const fields& fds) { if (!fds.ymd.ok() && !fds.wd.ok()) { // fds does not contain a valid weekday os.setstate(std::ios::failbit); return 8; } weekday wd; if (fds.ymd.ok()) { wd = weekday{sys_days(fds.ymd)}; if (fds.wd.ok() && wd != fds.wd) { // fds.ymd and fds.wd are inconsistent os.setstate(std::ios::failbit); return 8; } } else wd = fds.wd; return static_cast((wd - Sunday).count()); } template unsigned extract_month(std::basic_ostream& os, const fields& fds) { if (!fds.ymd.month().ok()) { // fds does not contain a valid month os.setstate(std::ios::failbit); return 0; } return static_cast(fds.ymd.month()); } } // namespace detail #if ONLY_C_LOCALE namespace detail { inline std::pair weekday_names() { static const std::string nm[] = { "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" }; return std::make_pair(nm, nm+sizeof(nm)/sizeof(nm[0])); } inline std::pair month_names() { static const std::string nm[] = { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; return std::make_pair(nm, nm+sizeof(nm)/sizeof(nm[0])); } inline std::pair ampm_names() { static const std::string nm[] = { "AM", "PM" }; return std::make_pair(nm, nm+sizeof(nm)/sizeof(nm[0])); } template FwdIter scan_keyword(std::basic_istream& is, FwdIter kb, FwdIter ke) { size_t nkw = static_cast(std::distance(kb, ke)); const unsigned char doesnt_match = '\0'; const unsigned char might_match = '\1'; const unsigned char does_match = '\2'; unsigned char statbuf[100]; unsigned char* status = statbuf; std::unique_ptr stat_hold(0, free); if (nkw > sizeof(statbuf)) { status = (unsigned char*)std::malloc(nkw); if (status == nullptr) throw std::bad_alloc(); stat_hold.reset(status); } size_t n_might_match = nkw; // At this point, any keyword might match size_t n_does_match = 0; // but none of them definitely do // Initialize all statuses to might_match, except for "" keywords are does_match unsigned char* st = status; for (auto ky = kb; ky != ke; ++ky, ++st) { if (!ky->empty()) *st = might_match; else { *st = does_match; --n_might_match; ++n_does_match; } } // While there might be a match, test keywords against the next CharT for (size_t indx = 0; is && n_might_match > 0; ++indx) { // Peek at the next CharT but don't consume it auto ic = is.peek(); if (ic == EOF) { is.setstate(std::ios::eofbit); break; } auto c = static_cast(toupper(static_cast(ic))); bool consume = false; // For each keyword which might match, see if the indx character is c // If a match if found, consume c // If a match is found, and that is the last character in the keyword, // then that keyword matches. // If the keyword doesn't match this character, then change the keyword // to doesn't match st = status; for (auto ky = kb; ky != ke; ++ky, ++st) { if (*st == might_match) { if (c == static_cast(toupper(static_cast((*ky)[indx])))) { consume = true; if (ky->size() == indx+1) { *st = does_match; --n_might_match; ++n_does_match; } } else { *st = doesnt_match; --n_might_match; } } } // consume if we matched a character if (consume) { (void)is.get(); // If we consumed a character and there might be a matched keyword that // was marked matched on a previous iteration, then such keywords // are now marked as not matching. if (n_might_match + n_does_match > 1) { st = status; for (auto ky = kb; ky != ke; ++ky, ++st) { if (*st == does_match && ky->size() != indx+1) { *st = doesnt_match; --n_does_match; } } } } } // We've exited the loop because we hit eof and/or we have no more "might matches". // Return the first matching result for (st = status; kb != ke; ++kb, ++st) if (*st == does_match) break; if (kb == ke) is.setstate(std::ios::failbit); return kb; } } // namespace detail #endif // ONLY_C_LOCALE template std::basic_ostream& to_stream(std::basic_ostream& os, const CharT* fmt, const fields& fds, const std::string* abbrev, const std::chrono::seconds* offset_sec) { #if ONLY_C_LOCALE using detail::weekday_names; using detail::month_names; using detail::ampm_names; #endif using detail::save_ostream; using detail::get_units; using detail::extract_weekday; using detail::extract_month; using std::ios; using std::chrono::duration_cast; using std::chrono::seconds; using std::chrono::minutes; using std::chrono::hours; date::detail::save_ostream ss(os); os.fill(' '); os.flags(std::ios::skipws | std::ios::dec); os.width(0); tm tm{}; bool insert_negative = fds.has_tod && fds.tod.to_duration() < Duration::zero(); #if !ONLY_C_LOCALE auto& facet = std::use_facet>(os.getloc()); #endif const CharT* command = nullptr; CharT modified = CharT{}; for (; *fmt; ++fmt) { switch (*fmt) { case 'a': case 'A': if (command) { if (modified == CharT{}) { tm.tm_wday = static_cast(extract_weekday(os, fds)); if (os.fail()) return os; #if !ONLY_C_LOCALE const CharT f[] = {'%', *fmt}; facet.put(os, os, os.fill(), &tm, std::begin(f), std::end(f)); #else // ONLY_C_LOCALE os << weekday_names().first[tm.tm_wday+7*(*fmt == 'a')]; #endif // ONLY_C_LOCALE } else { os << CharT{'%'} << modified << *fmt; modified = CharT{}; } command = nullptr; } else os << *fmt; break; case 'b': case 'B': case 'h': if (command) { if (modified == CharT{}) { tm.tm_mon = static_cast(extract_month(os, fds)) - 1; #if !ONLY_C_LOCALE const CharT f[] = {'%', *fmt}; facet.put(os, os, os.fill(), &tm, std::begin(f), std::end(f)); #else // ONLY_C_LOCALE os << month_names().first[tm.tm_mon+12*(*fmt != 'B')]; #endif // ONLY_C_LOCALE } else { os << CharT{'%'} << modified << *fmt; modified = CharT{}; } command = nullptr; } else os << *fmt; break; case 'c': case 'x': if (command) { if (modified == CharT{'O'}) os << CharT{'%'} << modified << *fmt; else { if (!fds.ymd.ok()) os.setstate(std::ios::failbit); if (*fmt == 'c' && !fds.has_tod) os.setstate(std::ios::failbit); #if !ONLY_C_LOCALE tm = std::tm{}; auto const& ymd = fds.ymd; auto ld = local_days(ymd); if (*fmt == 'c') { tm.tm_sec = static_cast(fds.tod.seconds().count()); tm.tm_min = static_cast(fds.tod.minutes().count()); tm.tm_hour = static_cast(fds.tod.hours().count()); } tm.tm_mday = static_cast(static_cast(ymd.day())); tm.tm_mon = static_cast(extract_month(os, fds) - 1); tm.tm_year = static_cast(ymd.year()) - 1900; tm.tm_wday = static_cast(extract_weekday(os, fds)); if (os.fail()) return os; tm.tm_yday = static_cast((ld - local_days(ymd.year()/1/1)).count()); CharT f[3] = {'%'}; auto fe = std::begin(f) + 1; if (modified == CharT{'E'}) *fe++ = modified; *fe++ = *fmt; facet.put(os, os, os.fill(), &tm, std::begin(f), fe); #else // ONLY_C_LOCALE if (*fmt == 'c') { auto wd = static_cast(extract_weekday(os, fds)); os << weekday_names().first[static_cast(wd)+7] << ' '; os << month_names().first[extract_month(os, fds)-1+12] << ' '; auto d = static_cast(static_cast(fds.ymd.day())); if (d < 10) os << ' '; os << d << ' ' << make_time(duration_cast(fds.tod.to_duration())) << ' ' << fds.ymd.year(); } else // *fmt == 'x' { auto const& ymd = fds.ymd; save_ostream _(os); os.fill('0'); os.flags(std::ios::dec | std::ios::right); os.width(2); os << static_cast(ymd.month()) << CharT{'/'}; os.width(2); os << static_cast(ymd.day()) << CharT{'/'}; os.width(2); os << static_cast(ymd.year()) % 100; } #endif // ONLY_C_LOCALE } command = nullptr; modified = CharT{}; } else os << *fmt; break; case 'C': if (command) { if (modified == CharT{'O'}) os << CharT{'%'} << modified << *fmt; else { if (!fds.ymd.year().ok()) os.setstate(std::ios::failbit); auto y = static_cast(fds.ymd.year()); #if !ONLY_C_LOCALE if (modified == CharT{}) #endif { save_ostream _(os); os.fill('0'); os.flags(std::ios::dec | std::ios::right); if (y >= 0) { os.width(2); os << y/100; } else { os << CharT{'-'}; os.width(2); os << -(y-99)/100; } } #if !ONLY_C_LOCALE else if (modified == CharT{'E'}) { tm.tm_year = y - 1900; CharT f[3] = {'%', 'E', 'C'}; facet.put(os, os, os.fill(), &tm, std::begin(f), std::end(f)); } #endif } command = nullptr; modified = CharT{}; } else os << *fmt; break; case 'd': case 'e': if (command) { if (modified == CharT{'E'}) os << CharT{'%'} << modified << *fmt; else { if (!fds.ymd.day().ok()) os.setstate(std::ios::failbit); auto d = static_cast(static_cast(fds.ymd.day())); #if !ONLY_C_LOCALE if (modified == CharT{}) #endif { save_ostream _(os); if (*fmt == CharT{'d'}) os.fill('0'); else os.fill(' '); os.flags(std::ios::dec | std::ios::right); os.width(2); os << d; } #if !ONLY_C_LOCALE else if (modified == CharT{'O'}) { tm.tm_mday = d; CharT f[3] = {'%', 'O', *fmt}; facet.put(os, os, os.fill(), &tm, std::begin(f), std::end(f)); } #endif } command = nullptr; modified = CharT{}; } else os << *fmt; break; case 'D': if (command) { if (modified == CharT{}) { if (!fds.ymd.ok()) os.setstate(std::ios::failbit); auto const& ymd = fds.ymd; save_ostream _(os); os.fill('0'); os.flags(std::ios::dec | std::ios::right); os.width(2); os << static_cast(ymd.month()) << CharT{'/'}; os.width(2); os << static_cast(ymd.day()) << CharT{'/'}; os.width(2); os << static_cast(ymd.year()) % 100; } else { os << CharT{'%'} << modified << *fmt; modified = CharT{}; } command = nullptr; } else os << *fmt; break; case 'F': if (command) { if (modified == CharT{}) { if (!fds.ymd.ok()) os.setstate(std::ios::failbit); auto const& ymd = fds.ymd; save_ostream _(os); os.imbue(std::locale::classic()); os.fill('0'); os.flags(std::ios::dec | std::ios::right); os.width(4); os << static_cast(ymd.year()) << CharT{'-'}; os.width(2); os << static_cast(ymd.month()) << CharT{'-'}; os.width(2); os << static_cast(ymd.day()); } else { os << CharT{'%'} << modified << *fmt; modified = CharT{}; } command = nullptr; } else os << *fmt; break; case 'g': case 'G': if (command) { if (modified == CharT{}) { if (!fds.ymd.ok()) os.setstate(std::ios::failbit); auto ld = local_days(fds.ymd); auto y = year_month_day{ld + days{3}}.year(); auto start = local_days((y-years{1})/December/Thursday[last]) + (Monday-Thursday); if (ld < start) --y; if (*fmt == CharT{'G'}) os << y; else { save_ostream _(os); os.fill('0'); os.flags(std::ios::dec | std::ios::right); os.width(2); os << std::abs(static_cast(y)) % 100; } } else { os << CharT{'%'} << modified << *fmt; modified = CharT{}; } command = nullptr; } else os << *fmt; break; case 'H': case 'I': if (command) { if (modified == CharT{'E'}) os << CharT{'%'} << modified << *fmt; else { if (!fds.has_tod) os.setstate(std::ios::failbit); if (insert_negative) { os << '-'; insert_negative = false; } auto hms = fds.tod; #if !ONLY_C_LOCALE if (modified == CharT{}) #endif { auto h = *fmt == CharT{'I'} ? date::make12(hms.hours()) : hms.hours(); if (h < hours{10}) os << CharT{'0'}; os << h.count(); } #if !ONLY_C_LOCALE else if (modified == CharT{'O'}) { const CharT f[] = {'%', modified, *fmt}; tm.tm_hour = static_cast(hms.hours().count()); facet.put(os, os, os.fill(), &tm, std::begin(f), std::end(f)); } #endif } modified = CharT{}; command = nullptr; } else os << *fmt; break; case 'j': if (command) { if (modified == CharT{}) { if (fds.ymd.ok() || fds.has_tod) { days doy; if (fds.ymd.ok()) { auto ld = local_days(fds.ymd); auto y = fds.ymd.year(); doy = ld - local_days(y/January/1) + days{1}; } else { doy = duration_cast(fds.tod.to_duration()); } save_ostream _(os); os.fill('0'); os.flags(std::ios::dec | std::ios::right); os.width(3); os << doy.count(); } else { os.setstate(std::ios::failbit); } } else { os << CharT{'%'} << modified << *fmt; modified = CharT{}; } command = nullptr; } else os << *fmt; break; case 'm': if (command) { if (modified == CharT{'E'}) os << CharT{'%'} << modified << *fmt; else { if (!fds.ymd.month().ok()) os.setstate(std::ios::failbit); auto m = static_cast(fds.ymd.month()); #if !ONLY_C_LOCALE if (modified == CharT{}) #endif { if (m < 10) os << CharT{'0'}; os << m; } #if !ONLY_C_LOCALE else if (modified == CharT{'O'}) { const CharT f[] = {'%', modified, *fmt}; tm.tm_mon = static_cast(m-1); facet.put(os, os, os.fill(), &tm, std::begin(f), std::end(f)); } #endif } modified = CharT{}; command = nullptr; } else os << *fmt; break; case 'M': if (command) { if (modified == CharT{'E'}) os << CharT{'%'} << modified << *fmt; else { if (!fds.has_tod) os.setstate(std::ios::failbit); if (insert_negative) { os << '-'; insert_negative = false; } #if !ONLY_C_LOCALE if (modified == CharT{}) #endif { if (fds.tod.minutes() < minutes{10}) os << CharT{'0'}; os << fds.tod.minutes().count(); } #if !ONLY_C_LOCALE else if (modified == CharT{'O'}) { const CharT f[] = {'%', modified, *fmt}; tm.tm_min = static_cast(fds.tod.minutes().count()); facet.put(os, os, os.fill(), &tm, std::begin(f), std::end(f)); } #endif } modified = CharT{}; command = nullptr; } else os << *fmt; break; case 'n': if (command) { if (modified == CharT{}) os << CharT{'\n'}; else { os << CharT{'%'} << modified << *fmt; modified = CharT{}; } command = nullptr; } else os << *fmt; break; case 'p': if (command) { if (modified == CharT{}) { if (!fds.has_tod) os.setstate(std::ios::failbit); #if !ONLY_C_LOCALE const CharT f[] = {'%', *fmt}; tm.tm_hour = static_cast(fds.tod.hours().count()); facet.put(os, os, os.fill(), &tm, std::begin(f), std::end(f)); #else if (date::is_am(fds.tod.hours())) os << ampm_names().first[0]; else os << ampm_names().first[1]; #endif } else { os << CharT{'%'} << modified << *fmt; } modified = CharT{}; command = nullptr; } else os << *fmt; break; case 'Q': case 'q': if (command) { if (modified == CharT{}) { if (!fds.has_tod) os.setstate(std::ios::failbit); auto d = fds.tod.to_duration(); if (*fmt == 'q') os << get_units(typename decltype(d)::period::type{}); else os << d.count(); } else { os << CharT{'%'} << modified << *fmt; } modified = CharT{}; command = nullptr; } else os << *fmt; break; case 'r': if (command) { if (modified == CharT{}) { if (!fds.has_tod) os.setstate(std::ios::failbit); #if !ONLY_C_LOCALE const CharT f[] = {'%', *fmt}; tm.tm_hour = static_cast(fds.tod.hours().count()); tm.tm_min = static_cast(fds.tod.minutes().count()); tm.tm_sec = static_cast(fds.tod.seconds().count()); facet.put(os, os, os.fill(), &tm, std::begin(f), std::end(f)); #else hh_mm_ss tod(duration_cast(fds.tod.to_duration())); save_ostream _(os); os.fill('0'); os.width(2); os << date::make12(tod.hours()).count() << CharT{':'}; os.width(2); os << tod.minutes().count() << CharT{':'}; os.width(2); os << tod.seconds().count() << CharT{' '}; if (date::is_am(tod.hours())) os << ampm_names().first[0]; else os << ampm_names().first[1]; #endif } else { os << CharT{'%'} << modified << *fmt; } modified = CharT{}; command = nullptr; } else os << *fmt; break; case 'R': if (command) { if (modified == CharT{}) { if (!fds.has_tod) os.setstate(std::ios::failbit); if (fds.tod.hours() < hours{10}) os << CharT{'0'}; os << fds.tod.hours().count() << CharT{':'}; if (fds.tod.minutes() < minutes{10}) os << CharT{'0'}; os << fds.tod.minutes().count(); } else { os << CharT{'%'} << modified << *fmt; modified = CharT{}; } command = nullptr; } else os << *fmt; break; case 'S': if (command) { if (modified == CharT{'E'}) os << CharT{'%'} << modified << *fmt; else { if (!fds.has_tod) os.setstate(std::ios::failbit); if (insert_negative) { os << '-'; insert_negative = false; } #if !ONLY_C_LOCALE if (modified == CharT{}) #endif { os << fds.tod.s_; } #if !ONLY_C_LOCALE else if (modified == CharT{'O'}) { const CharT f[] = {'%', modified, *fmt}; tm.tm_sec = static_cast(fds.tod.s_.seconds().count()); facet.put(os, os, os.fill(), &tm, std::begin(f), std::end(f)); } #endif } modified = CharT{}; command = nullptr; } else os << *fmt; break; case 't': if (command) { if (modified == CharT{}) os << CharT{'\t'}; else { os << CharT{'%'} << modified << *fmt; modified = CharT{}; } command = nullptr; } else os << *fmt; break; case 'T': if (command) { if (modified == CharT{}) { if (!fds.has_tod) os.setstate(std::ios::failbit); os << fds.tod; } else { os << CharT{'%'} << modified << *fmt; modified = CharT{}; } command = nullptr; } else os << *fmt; break; case 'u': if (command) { if (modified == CharT{'E'}) os << CharT{'%'} << modified << *fmt; else { auto wd = extract_weekday(os, fds); #if !ONLY_C_LOCALE if (modified == CharT{}) #endif { os << (wd != 0 ? wd : 7u); } #if !ONLY_C_LOCALE else if (modified == CharT{'O'}) { const CharT f[] = {'%', modified, *fmt}; tm.tm_wday = static_cast(wd); facet.put(os, os, os.fill(), &tm, std::begin(f), std::end(f)); } #endif } modified = CharT{}; command = nullptr; } else os << *fmt; break; case 'U': if (command) { if (modified == CharT{'E'}) os << CharT{'%'} << modified << *fmt; else { auto const& ymd = fds.ymd; if (!ymd.ok()) os.setstate(std::ios::failbit); auto ld = local_days(ymd); #if !ONLY_C_LOCALE if (modified == CharT{}) #endif { auto st = local_days(Sunday[1]/January/ymd.year()); if (ld < st) os << CharT{'0'} << CharT{'0'}; else { auto wn = duration_cast(ld - st).count() + 1; if (wn < 10) os << CharT{'0'}; os << wn; } } #if !ONLY_C_LOCALE else if (modified == CharT{'O'}) { const CharT f[] = {'%', modified, *fmt}; tm.tm_year = static_cast(ymd.year()) - 1900; tm.tm_wday = static_cast(extract_weekday(os, fds)); if (os.fail()) return os; tm.tm_yday = static_cast((ld - local_days(ymd.year()/1/1)).count()); facet.put(os, os, os.fill(), &tm, std::begin(f), std::end(f)); } #endif } modified = CharT{}; command = nullptr; } else os << *fmt; break; case 'V': if (command) { if (modified == CharT{'E'}) os << CharT{'%'} << modified << *fmt; else { if (!fds.ymd.ok()) os.setstate(std::ios::failbit); auto ld = local_days(fds.ymd); #if !ONLY_C_LOCALE if (modified == CharT{}) #endif { auto y = year_month_day{ld + days{3}}.year(); auto st = local_days((y-years{1})/12/Thursday[last]) + (Monday-Thursday); if (ld < st) { --y; st = local_days((y - years{1})/12/Thursday[last]) + (Monday-Thursday); } auto wn = duration_cast(ld - st).count() + 1; if (wn < 10) os << CharT{'0'}; os << wn; } #if !ONLY_C_LOCALE else if (modified == CharT{'O'}) { const CharT f[] = {'%', modified, *fmt}; auto const& ymd = fds.ymd; tm.tm_year = static_cast(ymd.year()) - 1900; tm.tm_wday = static_cast(extract_weekday(os, fds)); if (os.fail()) return os; tm.tm_yday = static_cast((ld - local_days(ymd.year()/1/1)).count()); facet.put(os, os, os.fill(), &tm, std::begin(f), std::end(f)); } #endif } modified = CharT{}; command = nullptr; } else os << *fmt; break; case 'w': if (command) { auto wd = extract_weekday(os, fds); if (os.fail()) return os; #if !ONLY_C_LOCALE if (modified == CharT{}) #else if (modified != CharT{'E'}) #endif { os << wd; } #if !ONLY_C_LOCALE else if (modified == CharT{'O'}) { const CharT f[] = {'%', modified, *fmt}; tm.tm_wday = static_cast(wd); facet.put(os, os, os.fill(), &tm, std::begin(f), std::end(f)); } #endif else { os << CharT{'%'} << modified << *fmt; } modified = CharT{}; command = nullptr; } else os << *fmt; break; case 'W': if (command) { if (modified == CharT{'E'}) os << CharT{'%'} << modified << *fmt; else { auto const& ymd = fds.ymd; if (!ymd.ok()) os.setstate(std::ios::failbit); auto ld = local_days(ymd); #if !ONLY_C_LOCALE if (modified == CharT{}) #endif { auto st = local_days(Monday[1]/January/ymd.year()); if (ld < st) os << CharT{'0'} << CharT{'0'}; else { auto wn = duration_cast(ld - st).count() + 1; if (wn < 10) os << CharT{'0'}; os << wn; } } #if !ONLY_C_LOCALE else if (modified == CharT{'O'}) { const CharT f[] = {'%', modified, *fmt}; tm.tm_year = static_cast(ymd.year()) - 1900; tm.tm_wday = static_cast(extract_weekday(os, fds)); if (os.fail()) return os; tm.tm_yday = static_cast((ld - local_days(ymd.year()/1/1)).count()); facet.put(os, os, os.fill(), &tm, std::begin(f), std::end(f)); } #endif } modified = CharT{}; command = nullptr; } else os << *fmt; break; case 'X': if (command) { if (modified == CharT{'O'}) os << CharT{'%'} << modified << *fmt; else { if (!fds.has_tod) os.setstate(std::ios::failbit); #if !ONLY_C_LOCALE tm = std::tm{}; tm.tm_sec = static_cast(fds.tod.seconds().count()); tm.tm_min = static_cast(fds.tod.minutes().count()); tm.tm_hour = static_cast(fds.tod.hours().count()); CharT f[3] = {'%'}; auto fe = std::begin(f) + 1; if (modified == CharT{'E'}) *fe++ = modified; *fe++ = *fmt; facet.put(os, os, os.fill(), &tm, std::begin(f), fe); #else os << fds.tod; #endif } command = nullptr; modified = CharT{}; } else os << *fmt; break; case 'y': if (command) { if (!fds.ymd.year().ok()) os.setstate(std::ios::failbit); auto y = static_cast(fds.ymd.year()); #if !ONLY_C_LOCALE if (modified == CharT{}) { #endif y = std::abs(y) % 100; if (y < 10) os << CharT{'0'}; os << y; #if !ONLY_C_LOCALE } else { const CharT f[] = {'%', modified, *fmt}; tm.tm_year = y - 1900; facet.put(os, os, os.fill(), &tm, std::begin(f), std::end(f)); } #endif modified = CharT{}; command = nullptr; } else os << *fmt; break; case 'Y': if (command) { if (modified == CharT{'O'}) os << CharT{'%'} << modified << *fmt; else { if (!fds.ymd.year().ok()) os.setstate(std::ios::failbit); auto y = fds.ymd.year(); #if !ONLY_C_LOCALE if (modified == CharT{}) #endif { save_ostream _(os); os.imbue(std::locale::classic()); os << y; } #if !ONLY_C_LOCALE else if (modified == CharT{'E'}) { const CharT f[] = {'%', modified, *fmt}; tm.tm_year = static_cast(y) - 1900; facet.put(os, os, os.fill(), &tm, std::begin(f), std::end(f)); } #endif } modified = CharT{}; command = nullptr; } else os << *fmt; break; case 'z': if (command) { if (offset_sec == nullptr) { // Can not format %z with unknown offset os.setstate(ios::failbit); return os; } auto m = duration_cast(*offset_sec); auto neg = m < minutes{0}; m = date::abs(m); auto h = duration_cast(m); m -= h; if (neg) os << CharT{'-'}; else os << CharT{'+'}; if (h < hours{10}) os << CharT{'0'}; os << h.count(); if (modified != CharT{}) os << CharT{':'}; if (m < minutes{10}) os << CharT{'0'}; os << m.count(); command = nullptr; modified = CharT{}; } else os << *fmt; break; case 'Z': if (command) { if (modified == CharT{}) { if (abbrev == nullptr) { // Can not format %Z with unknown time_zone os.setstate(ios::failbit); return os; } for (auto c : *abbrev) os << CharT(c); } else { os << CharT{'%'} << modified << *fmt; modified = CharT{}; } command = nullptr; } else os << *fmt; break; case 'E': case 'O': if (command) { if (modified == CharT{}) { modified = *fmt; } else { os << CharT{'%'} << modified << *fmt; command = nullptr; modified = CharT{}; } } else os << *fmt; break; case '%': if (command) { if (modified == CharT{}) { os << CharT{'%'}; command = nullptr; } else { os << CharT{'%'} << modified << CharT{'%'}; command = nullptr; modified = CharT{}; } } else command = fmt; break; default: if (command) { os << CharT{'%'}; command = nullptr; } if (modified != CharT{}) { os << modified; modified = CharT{}; } os << *fmt; break; } } if (command) os << CharT{'%'}; if (modified != CharT{}) os << modified; return os; } template inline std::basic_ostream& to_stream(std::basic_ostream& os, const CharT* fmt, const year& y) { using CT = std::chrono::seconds; fields fds{y/0/0}; return to_stream(os, fmt, fds); } template inline std::basic_ostream& to_stream(std::basic_ostream& os, const CharT* fmt, const month& m) { using CT = std::chrono::seconds; fields fds{m/0/nanyear}; return to_stream(os, fmt, fds); } template inline std::basic_ostream& to_stream(std::basic_ostream& os, const CharT* fmt, const day& d) { using CT = std::chrono::seconds; fields fds{d/0/nanyear}; return to_stream(os, fmt, fds); } template inline std::basic_ostream& to_stream(std::basic_ostream& os, const CharT* fmt, const weekday& wd) { using CT = std::chrono::seconds; fields fds{wd}; return to_stream(os, fmt, fds); } template inline std::basic_ostream& to_stream(std::basic_ostream& os, const CharT* fmt, const year_month& ym) { using CT = std::chrono::seconds; fields fds{ym/0}; return to_stream(os, fmt, fds); } template inline std::basic_ostream& to_stream(std::basic_ostream& os, const CharT* fmt, const month_day& md) { using CT = std::chrono::seconds; fields fds{md/nanyear}; return to_stream(os, fmt, fds); } template inline std::basic_ostream& to_stream(std::basic_ostream& os, const CharT* fmt, const year_month_day& ymd) { using CT = std::chrono::seconds; fields fds{ymd}; return to_stream(os, fmt, fds); } template inline std::basic_ostream& to_stream(std::basic_ostream& os, const CharT* fmt, const std::chrono::duration& d) { using Duration = std::chrono::duration; using CT = typename std::common_type::type; fields fds{hh_mm_ss{d}}; return to_stream(os, fmt, fds); } template std::basic_ostream& to_stream(std::basic_ostream& os, const CharT* fmt, const local_time& tp, const std::string* abbrev = nullptr, const std::chrono::seconds* offset_sec = nullptr) { using CT = typename std::common_type::type; auto ld = std::chrono::time_point_cast(tp); fields fds; if (ld <= tp) fds = fields{year_month_day{ld}, hh_mm_ss{tp-local_seconds{ld}}}; else fds = fields{year_month_day{ld - days{1}}, hh_mm_ss{days{1} - (local_seconds{ld} - tp)}}; return to_stream(os, fmt, fds, abbrev, offset_sec); } template std::basic_ostream& to_stream(std::basic_ostream& os, const CharT* fmt, const sys_time& tp) { using std::chrono::seconds; using CT = typename std::common_type::type; const std::string abbrev("UTC"); CONSTDATA seconds offset{0}; auto sd = std::chrono::time_point_cast(tp); fields fds; if (sd <= tp) fds = fields{year_month_day{sd}, hh_mm_ss{tp-sys_seconds{sd}}}; else fds = fields{year_month_day{sd - days{1}}, hh_mm_ss{days{1} - (sys_seconds{sd} - tp)}}; return to_stream(os, fmt, fds, &abbrev, &offset); } // format template auto format(const std::locale& loc, const CharT* fmt, const Streamable& tp) -> decltype(to_stream(std::declval&>(), fmt, tp), std::basic_string{}) { std::basic_ostringstream os; os.exceptions(std::ios::failbit | std::ios::badbit); os.imbue(loc); to_stream(os, fmt, tp); return os.str(); } template auto format(const CharT* fmt, const Streamable& tp) -> decltype(to_stream(std::declval&>(), fmt, tp), std::basic_string{}) { std::basic_ostringstream os; os.exceptions(std::ios::failbit | std::ios::badbit); to_stream(os, fmt, tp); return os.str(); } template auto format(const std::locale& loc, const std::basic_string& fmt, const Streamable& tp) -> decltype(to_stream(std::declval&>(), fmt.c_str(), tp), std::basic_string{}) { std::basic_ostringstream os; os.exceptions(std::ios::failbit | std::ios::badbit); os.imbue(loc); to_stream(os, fmt.c_str(), tp); return os.str(); } template auto format(const std::basic_string& fmt, const Streamable& tp) -> decltype(to_stream(std::declval&>(), fmt.c_str(), tp), std::basic_string{}) { std::basic_ostringstream os; os.exceptions(std::ios::failbit | std::ios::badbit); to_stream(os, fmt.c_str(), tp); return os.str(); } // parse namespace detail { template bool read_char(std::basic_istream& is, CharT fmt, std::ios::iostate& err) { auto ic = is.get(); if (Traits::eq_int_type(ic, Traits::eof()) || !Traits::eq(Traits::to_char_type(ic), fmt)) { err |= std::ios::failbit; is.setstate(std::ios::failbit); return false; } return true; } template unsigned read_unsigned(std::basic_istream& is, unsigned m = 1, unsigned M = 10) { unsigned x = 0; unsigned count = 0; while (true) { auto ic = is.peek(); if (Traits::eq_int_type(ic, Traits::eof())) break; auto c = static_cast(Traits::to_char_type(ic)); if (!('0' <= c && c <= '9')) break; (void)is.get(); ++count; x = 10*x + static_cast(c - '0'); if (count == M) break; } if (count < m) is.setstate(std::ios::failbit); return x; } template int read_signed(std::basic_istream& is, unsigned m = 1, unsigned M = 10) { auto ic = is.peek(); if (!Traits::eq_int_type(ic, Traits::eof())) { auto c = static_cast(Traits::to_char_type(ic)); if (('0' <= c && c <= '9') || c == '-' || c == '+') { if (c == '-' || c == '+') { (void)is.get(); --M; } auto x = static_cast(read_unsigned(is, std::max(m, 1u), M)); if (!is.fail()) { if (c == '-') x = -x; return x; } } } if (m > 0) is.setstate(std::ios::failbit); return 0; } template long double read_long_double(std::basic_istream& is, unsigned m = 1, unsigned M = 10) { unsigned count = 0; unsigned fcount = 0; unsigned long long i = 0; unsigned long long f = 0; bool parsing_fraction = false; #if ONLY_C_LOCALE typename Traits::int_type decimal_point = '.'; #else auto decimal_point = Traits::to_int_type( std::use_facet>(is.getloc()).decimal_point()); #endif while (true) { auto ic = is.peek(); if (Traits::eq_int_type(ic, Traits::eof())) break; if (Traits::eq_int_type(ic, decimal_point)) { decimal_point = Traits::eof(); parsing_fraction = true; } else { auto c = static_cast(Traits::to_char_type(ic)); if (!('0' <= c && c <= '9')) break; if (!parsing_fraction) { i = 10*i + static_cast(c - '0'); } else { f = 10*f + static_cast(c - '0'); ++fcount; } } (void)is.get(); if (++count == M) break; } if (count < m) { is.setstate(std::ios::failbit); return 0; } return static_cast(i) + static_cast(f)/std::pow(10.L, fcount); } struct rs { int& i; unsigned m; unsigned M; }; struct ru { int& i; unsigned m; unsigned M; }; struct rld { long double& i; unsigned m; unsigned M; }; template void read(std::basic_istream&) { } template void read(std::basic_istream& is, CharT a0, Args&& ...args); template void read(std::basic_istream& is, rs a0, Args&& ...args); template void read(std::basic_istream& is, ru a0, Args&& ...args); template void read(std::basic_istream& is, int a0, Args&& ...args); template void read(std::basic_istream& is, rld a0, Args&& ...args); template void read(std::basic_istream& is, CharT a0, Args&& ...args) { // No-op if a0 == CharT{} if (a0 != CharT{}) { auto ic = is.peek(); if (Traits::eq_int_type(ic, Traits::eof())) { is.setstate(std::ios::failbit | std::ios::eofbit); return; } if (!Traits::eq(Traits::to_char_type(ic), a0)) { is.setstate(std::ios::failbit); return; } (void)is.get(); } read(is, std::forward(args)...); } template void read(std::basic_istream& is, rs a0, Args&& ...args) { auto x = read_signed(is, a0.m, a0.M); if (is.fail()) return; a0.i = x; read(is, std::forward(args)...); } template void read(std::basic_istream& is, ru a0, Args&& ...args) { auto x = read_unsigned(is, a0.m, a0.M); if (is.fail()) return; a0.i = static_cast(x); read(is, std::forward(args)...); } template void read(std::basic_istream& is, int a0, Args&& ...args) { if (a0 != -1) { auto u = static_cast(a0); CharT buf[std::numeric_limits::digits10+2u] = {}; auto e = buf; do { *e++ = static_cast(CharT(u % 10) + CharT{'0'}); u /= 10; } while (u > 0); #if defined(__GNUC__) && __GNUC__ >= 11 #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wstringop-overflow" #endif std::reverse(buf, e); #if defined(__GNUC__) && __GNUC__ >= 11 #pragma GCC diagnostic pop #endif for (auto p = buf; p != e && is.rdstate() == std::ios::goodbit; ++p) read(is, *p); } if (is.rdstate() == std::ios::goodbit) read(is, std::forward(args)...); } template void read(std::basic_istream& is, rld a0, Args&& ...args) { auto x = read_long_double(is, a0.m, a0.M); if (is.fail()) return; a0.i = x; read(is, std::forward(args)...); } template inline void checked_set(T& value, T from, T not_a_value, std::basic_ios& is) { if (!is.fail()) { if (value == not_a_value) value = std::move(from); else if (value != from) is.setstate(std::ios::failbit); } } } // namespace detail; template > std::basic_istream& from_stream(std::basic_istream& is, const CharT* fmt, fields& fds, std::basic_string* abbrev, std::chrono::minutes* offset) { using std::numeric_limits; using std::ios; using std::chrono::duration; using std::chrono::duration_cast; using std::chrono::seconds; using std::chrono::minutes; using std::chrono::hours; using detail::round_i; typename std::basic_istream::sentry ok{is, true}; if (ok) { date::detail::save_istream ss(is); is.fill(' '); is.flags(std::ios::skipws | std::ios::dec); is.width(0); #if !ONLY_C_LOCALE auto& f = std::use_facet>(is.getloc()); std::tm tm{}; #endif const CharT* command = nullptr; auto modified = CharT{}; auto width = -1; CONSTDATA int not_a_year = numeric_limits::min(); CONSTDATA int not_a_2digit_year = 100; CONSTDATA int not_a_century = numeric_limits::min(); CONSTDATA int not_a_month = 0; CONSTDATA int not_a_day = 0; CONSTDATA int not_a_hour = numeric_limits::min(); CONSTDATA int not_a_hour_12_value = 0; CONSTDATA int not_a_minute = not_a_hour; CONSTDATA Duration not_a_second = Duration::min(); CONSTDATA int not_a_doy = -1; CONSTDATA int not_a_weekday = 8; CONSTDATA int not_a_week_num = 100; CONSTDATA int not_a_ampm = -1; CONSTDATA minutes not_a_offset = minutes::min(); int Y = not_a_year; // c, F, Y * int y = not_a_2digit_year; // D, x, y * int g = not_a_2digit_year; // g * int G = not_a_year; // G * int C = not_a_century; // C * int m = not_a_month; // b, B, h, m, c, D, F, x * int d = not_a_day; // c, d, D, e, F, x * int j = not_a_doy; // j * int wd = not_a_weekday; // a, A, u, w * int H = not_a_hour; // c, H, R, T, X * int I = not_a_hour_12_value; // I, r * int p = not_a_ampm; // p, r * int M = not_a_minute; // c, M, r, R, T, X * Duration s = not_a_second; // c, r, S, T, X * int U = not_a_week_num; // U * int V = not_a_week_num; // V * int W = not_a_week_num; // W * std::basic_string temp_abbrev; // Z * minutes temp_offset = not_a_offset; // z * using detail::read; using detail::rs; using detail::ru; using detail::rld; using detail::checked_set; for (; *fmt != CharT{} && !is.fail(); ++fmt) { switch (*fmt) { case 'a': case 'A': case 'u': case 'w': // wd: a, A, u, w if (command) { int trial_wd = not_a_weekday; if (*fmt == 'a' || *fmt == 'A') { if (modified == CharT{}) { #if !ONLY_C_LOCALE ios::iostate err = ios::goodbit; f.get(is, nullptr, is, err, &tm, command, fmt+1); is.setstate(err); if (!is.fail()) trial_wd = tm.tm_wday; #else auto nm = detail::weekday_names(); auto i = detail::scan_keyword(is, nm.first, nm.second) - nm.first; if (!is.fail()) trial_wd = i % 7; #endif } else read(is, CharT{'%'}, width, modified, *fmt); } else // *fmt == 'u' || *fmt == 'w' { #if !ONLY_C_LOCALE if (modified == CharT{}) #else if (modified != CharT{'E'}) #endif { read(is, ru{trial_wd, 1, width == -1 ? 1u : static_cast(width)}); if (!is.fail()) { if (*fmt == 'u') { if (!(1 <= trial_wd && trial_wd <= 7)) { trial_wd = not_a_weekday; is.setstate(ios::failbit); } else if (trial_wd == 7) trial_wd = 0; } else // *fmt == 'w' { if (!(0 <= trial_wd && trial_wd <= 6)) { trial_wd = not_a_weekday; is.setstate(ios::failbit); } } } } #if !ONLY_C_LOCALE else if (modified == CharT{'O'}) { ios::iostate err = ios::goodbit; f.get(is, nullptr, is, err, &tm, command, fmt+1); is.setstate(err); if (!is.fail()) trial_wd = tm.tm_wday; } #endif else read(is, CharT{'%'}, width, modified, *fmt); } if (trial_wd != not_a_weekday) checked_set(wd, trial_wd, not_a_weekday, is); } else // !command read(is, *fmt); command = nullptr; width = -1; modified = CharT{}; break; case 'b': case 'B': case 'h': if (command) { if (modified == CharT{}) { int ttm = not_a_month; #if !ONLY_C_LOCALE ios::iostate err = ios::goodbit; f.get(is, nullptr, is, err, &tm, command, fmt+1); if ((err & ios::failbit) == 0) ttm = tm.tm_mon + 1; is.setstate(err); #else auto nm = detail::month_names(); auto i = detail::scan_keyword(is, nm.first, nm.second) - nm.first; if (!is.fail()) ttm = i % 12 + 1; #endif checked_set(m, ttm, not_a_month, is); } else read(is, CharT{'%'}, width, modified, *fmt); command = nullptr; width = -1; modified = CharT{}; } else read(is, *fmt); break; case 'c': if (command) { if (modified != CharT{'O'}) { #if !ONLY_C_LOCALE ios::iostate err = ios::goodbit; f.get(is, nullptr, is, err, &tm, command, fmt+1); if ((err & ios::failbit) == 0) { checked_set(Y, tm.tm_year + 1900, not_a_year, is); checked_set(m, tm.tm_mon + 1, not_a_month, is); checked_set(d, tm.tm_mday, not_a_day, is); checked_set(H, tm.tm_hour, not_a_hour, is); checked_set(M, tm.tm_min, not_a_minute, is); checked_set(s, duration_cast(seconds{tm.tm_sec}), not_a_second, is); } is.setstate(err); #else // "%a %b %e %T %Y" auto nm = detail::weekday_names(); auto i = detail::scan_keyword(is, nm.first, nm.second) - nm.first; checked_set(wd, static_cast(i % 7), not_a_weekday, is); ws(is); nm = detail::month_names(); i = detail::scan_keyword(is, nm.first, nm.second) - nm.first; checked_set(m, static_cast(i % 12 + 1), not_a_month, is); ws(is); int td = not_a_day; read(is, ru{td, 1, 2}); checked_set(d, td, not_a_day, is); ws(is); using dfs = detail::decimal_format_seconds; CONSTDATA auto w = Duration::period::den == 1 ? 2 : 3 + dfs::width; int tH; int tM; long double S{}; read(is, ru{tH, 1, 2}, CharT{':'}, ru{tM, 1, 2}, CharT{':'}, rld{S, 1, w}); checked_set(H, tH, not_a_hour, is); checked_set(M, tM, not_a_minute, is); checked_set(s, round_i(duration{S}), not_a_second, is); ws(is); int tY = not_a_year; // No need for `rs` here, negative years can't parse // with "%c" since `width` is hardcoded to 4 read(is, ru{tY, 1, 4u}); checked_set(Y, tY, not_a_year, is); #endif } else read(is, CharT{'%'}, width, modified, *fmt); command = nullptr; width = -1; modified = CharT{}; } else read(is, *fmt); break; case 'x': if (command) { if (modified != CharT{'O'}) { #if !ONLY_C_LOCALE ios::iostate err = ios::goodbit; f.get(is, nullptr, is, err, &tm, command, fmt+1); if ((err & ios::failbit) == 0) { checked_set(Y, tm.tm_year + 1900, not_a_year, is); checked_set(m, tm.tm_mon + 1, not_a_month, is); checked_set(d, tm.tm_mday, not_a_day, is); } is.setstate(err); #else // "%m/%d/%y" int ty = not_a_2digit_year; int tm = not_a_month; int td = not_a_day; read(is, ru{tm, 1, 2}, CharT{'/'}, ru{td, 1, 2}, CharT{'/'}, ru{ty, 1, 2}); checked_set(y, ty, not_a_2digit_year, is); checked_set(m, tm, not_a_month, is); checked_set(d, td, not_a_day, is); #endif } else read(is, CharT{'%'}, width, modified, *fmt); command = nullptr; width = -1; modified = CharT{}; } else read(is, *fmt); break; case 'X': if (command) { if (modified != CharT{'O'}) { #if !ONLY_C_LOCALE ios::iostate err = ios::goodbit; f.get(is, nullptr, is, err, &tm, command, fmt+1); if ((err & ios::failbit) == 0) { checked_set(H, tm.tm_hour, not_a_hour, is); checked_set(M, tm.tm_min, not_a_minute, is); checked_set(s, duration_cast(seconds{tm.tm_sec}), not_a_second, is); } is.setstate(err); #else // "%T" using dfs = detail::decimal_format_seconds; CONSTDATA auto w = Duration::period::den == 1 ? 2 : 3 + dfs::width; int tH = not_a_hour; int tM = not_a_minute; long double S{}; read(is, ru{tH, 1, 2}, CharT{':'}, ru{tM, 1, 2}, CharT{':'}, rld{S, 1, w}); checked_set(H, tH, not_a_hour, is); checked_set(M, tM, not_a_minute, is); checked_set(s, round_i(duration{S}), not_a_second, is); #endif } else read(is, CharT{'%'}, width, modified, *fmt); command = nullptr; width = -1; modified = CharT{}; } else read(is, *fmt); break; case 'C': if (command) { int tC = not_a_century; #if !ONLY_C_LOCALE if (modified == CharT{}) { #endif read(is, rs{tC, 1, width == -1 ? 2u : static_cast(width)}); #if !ONLY_C_LOCALE } else { ios::iostate err = ios::goodbit; f.get(is, nullptr, is, err, &tm, command, fmt+1); if ((err & ios::failbit) == 0) { auto tY = tm.tm_year + 1900; tC = (tY >= 0 ? tY : tY-99) / 100; } is.setstate(err); } #endif checked_set(C, tC, not_a_century, is); command = nullptr; width = -1; modified = CharT{}; } else read(is, *fmt); break; case 'D': if (command) { if (modified == CharT{}) { int tn = not_a_month; int td = not_a_day; int ty = not_a_2digit_year; read(is, ru{tn, 1, 2}, CharT{'\0'}, CharT{'/'}, CharT{'\0'}, ru{td, 1, 2}, CharT{'\0'}, CharT{'/'}, CharT{'\0'}, ru{ty, 1, 2}); checked_set(y, ty, not_a_2digit_year, is); checked_set(m, tn, not_a_month, is); checked_set(d, td, not_a_day, is); } else read(is, CharT{'%'}, width, modified, *fmt); command = nullptr; width = -1; modified = CharT{}; } else read(is, *fmt); break; case 'F': if (command) { if (modified == CharT{}) { int tY = not_a_year; int tn = not_a_month; int td = not_a_day; read(is, rs{tY, 1, width == -1 ? 4u : static_cast(width)}, CharT{'-'}, ru{tn, 1, 2}, CharT{'-'}, ru{td, 1, 2}); checked_set(Y, tY, not_a_year, is); checked_set(m, tn, not_a_month, is); checked_set(d, td, not_a_day, is); } else read(is, CharT{'%'}, width, modified, *fmt); command = nullptr; width = -1; modified = CharT{}; } else read(is, *fmt); break; case 'd': case 'e': if (command) { #if !ONLY_C_LOCALE if (modified == CharT{}) #else if (modified != CharT{'E'}) #endif { int td = not_a_day; read(is, ru{td, 1, width == -1 ? 2u : static_cast(width)}); checked_set(d, td, not_a_day, is); } #if !ONLY_C_LOCALE else if (modified == CharT{'O'}) { ios::iostate err = ios::goodbit; f.get(is, nullptr, is, err, &tm, command, fmt+1); command = nullptr; width = -1; modified = CharT{}; if ((err & ios::failbit) == 0) checked_set(d, tm.tm_mday, not_a_day, is); is.setstate(err); } #endif else read(is, CharT{'%'}, width, modified, *fmt); command = nullptr; width = -1; modified = CharT{}; } else read(is, *fmt); break; case 'H': if (command) { #if !ONLY_C_LOCALE if (modified == CharT{}) #else if (modified != CharT{'E'}) #endif { int tH = not_a_hour; read(is, ru{tH, 1, width == -1 ? 2u : static_cast(width)}); checked_set(H, tH, not_a_hour, is); } #if !ONLY_C_LOCALE else if (modified == CharT{'O'}) { ios::iostate err = ios::goodbit; f.get(is, nullptr, is, err, &tm, command, fmt+1); if ((err & ios::failbit) == 0) checked_set(H, tm.tm_hour, not_a_hour, is); is.setstate(err); } #endif else read(is, CharT{'%'}, width, modified, *fmt); command = nullptr; width = -1; modified = CharT{}; } else read(is, *fmt); break; case 'I': if (command) { if (modified == CharT{}) { int tI = not_a_hour_12_value; // reads in an hour into I, but most be in [1, 12] read(is, ru{tI, 1, width == -1 ? 2u : static_cast(width)}); if (!(1 <= tI && tI <= 12)) is.setstate(ios::failbit); checked_set(I, tI, not_a_hour_12_value, is); } else read(is, CharT{'%'}, width, modified, *fmt); command = nullptr; width = -1; modified = CharT{}; } else read(is, *fmt); break; case 'j': if (command) { if (modified == CharT{}) { int tj = not_a_doy; read(is, ru{tj, 1, width == -1 ? 3u : static_cast(width)}); checked_set(j, tj, not_a_doy, is); } else read(is, CharT{'%'}, width, modified, *fmt); command = nullptr; width = -1; modified = CharT{}; } else read(is, *fmt); break; case 'M': if (command) { #if !ONLY_C_LOCALE if (modified == CharT{}) #else if (modified != CharT{'E'}) #endif { int tM = not_a_minute; read(is, ru{tM, 1, width == -1 ? 2u : static_cast(width)}); checked_set(M, tM, not_a_minute, is); } #if !ONLY_C_LOCALE else if (modified == CharT{'O'}) { ios::iostate err = ios::goodbit; f.get(is, nullptr, is, err, &tm, command, fmt+1); if ((err & ios::failbit) == 0) checked_set(M, tm.tm_min, not_a_minute, is); is.setstate(err); } #endif else read(is, CharT{'%'}, width, modified, *fmt); command = nullptr; width = -1; modified = CharT{}; } else read(is, *fmt); break; case 'm': if (command) { #if !ONLY_C_LOCALE if (modified == CharT{}) #else if (modified != CharT{'E'}) #endif { int tn = not_a_month; read(is, ru{tn, 1, width == -1 ? 2u : static_cast(width)}); checked_set(m, tn, not_a_month, is); } #if !ONLY_C_LOCALE else if (modified == CharT{'O'}) { ios::iostate err = ios::goodbit; f.get(is, nullptr, is, err, &tm, command, fmt+1); if ((err & ios::failbit) == 0) checked_set(m, tm.tm_mon + 1, not_a_month, is); is.setstate(err); } #endif else read(is, CharT{'%'}, width, modified, *fmt); command = nullptr; width = -1; modified = CharT{}; } else read(is, *fmt); break; case 'n': case 't': if (command) { if (modified == CharT{}) { // %n matches a single white space character // %t matches 0 or 1 white space characters auto ic = is.peek(); if (Traits::eq_int_type(ic, Traits::eof())) { ios::iostate err = ios::eofbit; if (*fmt == 'n') err |= ios::failbit; is.setstate(err); break; } if (isspace(ic)) { (void)is.get(); } else if (*fmt == 'n') is.setstate(ios::failbit); } else read(is, CharT{'%'}, width, modified, *fmt); command = nullptr; width = -1; modified = CharT{}; } else read(is, *fmt); break; case 'p': if (command) { if (modified == CharT{}) { int tp = not_a_ampm; #if !ONLY_C_LOCALE tm = std::tm{}; tm.tm_hour = 1; ios::iostate err = ios::goodbit; f.get(is, nullptr, is, err, &tm, command, fmt+1); is.setstate(err); if (tm.tm_hour == 1) tp = 0; else if (tm.tm_hour == 13) tp = 1; else is.setstate(err); #else auto nm = detail::ampm_names(); auto i = detail::scan_keyword(is, nm.first, nm.second) - nm.first; tp = static_cast(i); #endif checked_set(p, tp, not_a_ampm, is); } else read(is, CharT{'%'}, width, modified, *fmt); command = nullptr; width = -1; modified = CharT{}; } else read(is, *fmt); break; case 'r': if (command) { if (modified == CharT{}) { #if !ONLY_C_LOCALE ios::iostate err = ios::goodbit; f.get(is, nullptr, is, err, &tm, command, fmt+1); if ((err & ios::failbit) == 0) { checked_set(H, tm.tm_hour, not_a_hour, is); checked_set(M, tm.tm_min, not_a_hour, is); checked_set(s, duration_cast(seconds{tm.tm_sec}), not_a_second, is); } is.setstate(err); #else // "%I:%M:%S %p" using dfs = detail::decimal_format_seconds; CONSTDATA auto w = Duration::period::den == 1 ? 2 : 3 + dfs::width; long double S{}; int tI = not_a_hour_12_value; int tM = not_a_minute; read(is, ru{tI, 1, 2}, CharT{':'}, ru{tM, 1, 2}, CharT{':'}, rld{S, 1, w}); checked_set(I, tI, not_a_hour_12_value, is); checked_set(M, tM, not_a_minute, is); checked_set(s, round_i(duration{S}), not_a_second, is); ws(is); auto nm = detail::ampm_names(); auto i = detail::scan_keyword(is, nm.first, nm.second) - nm.first; checked_set(p, static_cast(i), not_a_ampm, is); #endif } else read(is, CharT{'%'}, width, modified, *fmt); command = nullptr; width = -1; modified = CharT{}; } else read(is, *fmt); break; case 'R': if (command) { if (modified == CharT{}) { int tH = not_a_hour; int tM = not_a_minute; read(is, ru{tH, 1, 2}, CharT{'\0'}, CharT{':'}, CharT{'\0'}, ru{tM, 1, 2}, CharT{'\0'}); checked_set(H, tH, not_a_hour, is); checked_set(M, tM, not_a_minute, is); } else read(is, CharT{'%'}, width, modified, *fmt); command = nullptr; width = -1; modified = CharT{}; } else read(is, *fmt); break; case 'S': if (command) { #if !ONLY_C_LOCALE if (modified == CharT{}) #else if (modified != CharT{'E'}) #endif { using dfs = detail::decimal_format_seconds; CONSTDATA auto w = Duration::period::den == 1 ? 2 : 3 + dfs::width; long double S{}; read(is, rld{S, 1, width == -1 ? w : static_cast(width)}); checked_set(s, round_i(duration{S}), not_a_second, is); } #if !ONLY_C_LOCALE else if (modified == CharT{'O'}) { ios::iostate err = ios::goodbit; f.get(is, nullptr, is, err, &tm, command, fmt+1); if ((err & ios::failbit) == 0) checked_set(s, duration_cast(seconds{tm.tm_sec}), not_a_second, is); is.setstate(err); } #endif else read(is, CharT{'%'}, width, modified, *fmt); command = nullptr; width = -1; modified = CharT{}; } else read(is, *fmt); break; case 'T': if (command) { if (modified == CharT{}) { using dfs = detail::decimal_format_seconds; CONSTDATA auto w = Duration::period::den == 1 ? 2 : 3 + dfs::width; int tH = not_a_hour; int tM = not_a_minute; long double S{}; read(is, ru{tH, 1, 2}, CharT{':'}, ru{tM, 1, 2}, CharT{':'}, rld{S, 1, w}); checked_set(H, tH, not_a_hour, is); checked_set(M, tM, not_a_minute, is); checked_set(s, round_i(duration{S}), not_a_second, is); } else read(is, CharT{'%'}, width, modified, *fmt); command = nullptr; width = -1; modified = CharT{}; } else read(is, *fmt); break; case 'Y': if (command) { #if !ONLY_C_LOCALE if (modified == CharT{}) #else if (modified != CharT{'O'}) #endif { int tY = not_a_year; read(is, rs{tY, 1, width == -1 ? 4u : static_cast(width)}); checked_set(Y, tY, not_a_year, is); } #if !ONLY_C_LOCALE else if (modified == CharT{'E'}) { ios::iostate err = ios::goodbit; f.get(is, nullptr, is, err, &tm, command, fmt+1); if ((err & ios::failbit) == 0) checked_set(Y, tm.tm_year + 1900, not_a_year, is); is.setstate(err); } #endif else read(is, CharT{'%'}, width, modified, *fmt); command = nullptr; width = -1; modified = CharT{}; } else read(is, *fmt); break; case 'y': if (command) { #if !ONLY_C_LOCALE if (modified == CharT{}) #endif { int ty = not_a_2digit_year; read(is, ru{ty, 1, width == -1 ? 2u : static_cast(width)}); checked_set(y, ty, not_a_2digit_year, is); } #if !ONLY_C_LOCALE else { ios::iostate err = ios::goodbit; f.get(is, nullptr, is, err, &tm, command, fmt+1); if ((err & ios::failbit) == 0) checked_set(Y, tm.tm_year + 1900, not_a_year, is); is.setstate(err); } #endif command = nullptr; width = -1; modified = CharT{}; } else read(is, *fmt); break; case 'g': if (command) { if (modified == CharT{}) { int tg = not_a_2digit_year; read(is, ru{tg, 1, width == -1 ? 2u : static_cast(width)}); checked_set(g, tg, not_a_2digit_year, is); } else read(is, CharT{'%'}, width, modified, *fmt); command = nullptr; width = -1; modified = CharT{}; } else read(is, *fmt); break; case 'G': if (command) { if (modified == CharT{}) { int tG = not_a_year; read(is, rs{tG, 1, width == -1 ? 4u : static_cast(width)}); checked_set(G, tG, not_a_year, is); } else read(is, CharT{'%'}, width, modified, *fmt); command = nullptr; width = -1; modified = CharT{}; } else read(is, *fmt); break; case 'U': if (command) { if (modified == CharT{}) { int tU = not_a_week_num; read(is, ru{tU, 1, width == -1 ? 2u : static_cast(width)}); checked_set(U, tU, not_a_week_num, is); } else read(is, CharT{'%'}, width, modified, *fmt); command = nullptr; width = -1; modified = CharT{}; } else read(is, *fmt); break; case 'V': if (command) { if (modified == CharT{}) { int tV = not_a_week_num; read(is, ru{tV, 1, width == -1 ? 2u : static_cast(width)}); checked_set(V, tV, not_a_week_num, is); } else read(is, CharT{'%'}, width, modified, *fmt); command = nullptr; width = -1; modified = CharT{}; } else read(is, *fmt); break; case 'W': if (command) { if (modified == CharT{}) { int tW = not_a_week_num; read(is, ru{tW, 1, width == -1 ? 2u : static_cast(width)}); checked_set(W, tW, not_a_week_num, is); } else read(is, CharT{'%'}, width, modified, *fmt); command = nullptr; width = -1; modified = CharT{}; } else read(is, *fmt); break; case 'E': case 'O': if (command) { if (modified == CharT{}) { modified = *fmt; } else { read(is, CharT{'%'}, width, modified, *fmt); command = nullptr; width = -1; modified = CharT{}; } } else read(is, *fmt); break; case '%': if (command) { if (modified == CharT{}) read(is, *fmt); else read(is, CharT{'%'}, width, modified, *fmt); command = nullptr; width = -1; modified = CharT{}; } else command = fmt; break; case 'z': if (command) { int tH, tM; minutes toff = not_a_offset; bool neg = false; auto ic = is.peek(); if (!Traits::eq_int_type(ic, Traits::eof())) { auto c = static_cast(Traits::to_char_type(ic)); if (c == '-') { neg = true; (void)is.get(); } else if (c == '+') (void)is.get(); } if (modified == CharT{}) { read(is, ru{tH, 2, 2}); if (!is.fail()) toff = hours{std::abs(tH)}; if (is.good()) { ic = is.peek(); if (!Traits::eq_int_type(ic, Traits::eof())) { auto c = static_cast(Traits::to_char_type(ic)); if ('0' <= c && c <= '9') { read(is, ru{tM, 2, 2}); if (!is.fail()) toff += minutes{tM}; } } } } else { read(is, ru{tH, 1, 2}); if (!is.fail()) toff = hours{std::abs(tH)}; if (is.good()) { ic = is.peek(); if (!Traits::eq_int_type(ic, Traits::eof())) { auto c = static_cast(Traits::to_char_type(ic)); if (c == ':') { (void)is.get(); read(is, ru{tM, 2, 2}); if (!is.fail()) toff += minutes{tM}; } } } } if (neg && !is.fail()) toff = -toff; checked_set(temp_offset, toff, not_a_offset, is); command = nullptr; width = -1; modified = CharT{}; } else read(is, *fmt); break; case 'Z': if (command) { if (modified == CharT{}) { std::basic_string buf; while (is.rdstate() == std::ios::goodbit) { auto i = is.rdbuf()->sgetc(); if (Traits::eq_int_type(i, Traits::eof())) { is.setstate(ios::eofbit); break; } auto wc = Traits::to_char_type(i); auto c = static_cast(wc); // is c a valid time zone name or abbreviation character? if (!(CharT{1} < wc && wc < CharT{127}) || !(isalnum(c) || c == '_' || c == '/' || c == '-' || c == '+')) break; buf.push_back(c); is.rdbuf()->sbumpc(); } if (buf.empty()) is.setstate(ios::failbit); checked_set(temp_abbrev, buf, {}, is); } else read(is, CharT{'%'}, width, modified, *fmt); command = nullptr; width = -1; modified = CharT{}; } else read(is, *fmt); break; default: if (command) { if (width == -1 && modified == CharT{} && '0' <= *fmt && *fmt <= '9') { width = static_cast(*fmt) - '0'; while ('0' <= fmt[1] && fmt[1] <= '9') width = 10*width + static_cast(*++fmt) - '0'; } else { if (modified == CharT{}) read(is, CharT{'%'}, width, *fmt); else read(is, CharT{'%'}, width, modified, *fmt); command = nullptr; width = -1; modified = CharT{}; } } else // !command { if (isspace(static_cast(*fmt))) { // space matches 0 or more white space characters if (is.good()) ws(is); } else read(is, *fmt); } break; } } // is.fail() || *fmt == CharT{} if (is.rdstate() == ios::goodbit && command) { if (modified == CharT{}) read(is, CharT{'%'}, width); else read(is, CharT{'%'}, width, modified); } if (!is.fail()) { if (y != not_a_2digit_year) { // Convert y and an optional C to Y if (!(0 <= y && y <= 99)) goto broken; if (C == not_a_century) { if (Y == not_a_year) { if (y >= 69) C = 19; else C = 20; } else { C = (Y >= 0 ? Y : Y-100) / 100; } } int tY; if (C >= 0) tY = 100*C + y; else tY = 100*(C+1) - (y == 0 ? 100 : y); if (Y != not_a_year && Y != tY) goto broken; Y = tY; } if (g != not_a_2digit_year) { // Convert g and an optional C to G if (!(0 <= g && g <= 99)) goto broken; if (C == not_a_century) { if (G == not_a_year) { if (g >= 69) C = 19; else C = 20; } else { C = (G >= 0 ? G : G-100) / 100; } } int tG; if (C >= 0) tG = 100*C + g; else tG = 100*(C+1) - (g == 0 ? 100 : g); if (G != not_a_year && G != tG) goto broken; G = tG; } if (Y < static_cast(year::min()) || Y > static_cast(year::max())) Y = not_a_year; bool computed = false; if (G != not_a_year && V != not_a_week_num && wd != not_a_weekday) { year_month_day ymd_trial = sys_days(year{G-1}/December/Thursday[last]) + (Monday-Thursday) + weeks{V-1} + (weekday{static_cast(wd)}-Monday); if (Y == not_a_year) Y = static_cast(ymd_trial.year()); else if (year{Y} != ymd_trial.year()) goto broken; if (m == not_a_month) m = static_cast(static_cast(ymd_trial.month())); else if (month(static_cast(m)) != ymd_trial.month()) goto broken; if (d == not_a_day) d = static_cast(static_cast(ymd_trial.day())); else if (day(static_cast(d)) != ymd_trial.day()) goto broken; computed = true; } if (Y != not_a_year && U != not_a_week_num && wd != not_a_weekday) { year_month_day ymd_trial = sys_days(year{Y}/January/Sunday[1]) + weeks{U-1} + (weekday{static_cast(wd)} - Sunday); if (year{Y} != ymd_trial.year()) goto broken; if (m == not_a_month) m = static_cast(static_cast(ymd_trial.month())); else if (month(static_cast(m)) != ymd_trial.month()) goto broken; if (d == not_a_day) d = static_cast(static_cast(ymd_trial.day())); else if (day(static_cast(d)) != ymd_trial.day()) goto broken; computed = true; } if (Y != not_a_year && W != not_a_week_num && wd != not_a_weekday) { year_month_day ymd_trial = sys_days(year{Y}/January/Monday[1]) + weeks{W-1} + (weekday{static_cast(wd)} - Monday); if (year{Y} != ymd_trial.year()) goto broken; if (m == not_a_month) m = static_cast(static_cast(ymd_trial.month())); else if (month(static_cast(m)) != ymd_trial.month()) goto broken; if (d == not_a_day) d = static_cast(static_cast(ymd_trial.day())); else if (day(static_cast(d)) != ymd_trial.day()) goto broken; computed = true; } if (j != not_a_doy && Y != not_a_year) { auto ymd_trial = year_month_day{local_days(year{Y}/1/1) + days{j-1}}; if (m == not_a_month) m = static_cast(static_cast(ymd_trial.month())); else if (month(static_cast(m)) != ymd_trial.month()) goto broken; if (d == not_a_day) d = static_cast(static_cast(ymd_trial.day())); else if (day(static_cast(d)) != ymd_trial.day()) goto broken; j = not_a_doy; } auto ymd = year{Y}/m/d; if (ymd.ok()) { if (wd == not_a_weekday) wd = static_cast((weekday(sys_days(ymd)) - Sunday).count()); else if (wd != static_cast((weekday(sys_days(ymd)) - Sunday).count())) goto broken; if (!computed) { if (G != not_a_year || V != not_a_week_num) { sys_days sd = ymd; auto G_trial = year_month_day{sd + days{3}}.year(); auto start = sys_days((G_trial - years{1})/December/Thursday[last]) + (Monday - Thursday); if (sd < start) { --G_trial; if (V != not_a_week_num) start = sys_days((G_trial - years{1})/December/Thursday[last]) + (Monday - Thursday); } if (G != not_a_year && G != static_cast(G_trial)) goto broken; if (V != not_a_week_num) { auto V_trial = duration_cast(sd - start).count() + 1; if (V != V_trial) goto broken; } } if (U != not_a_week_num) { auto start = sys_days(Sunday[1]/January/ymd.year()); auto U_trial = floor(sys_days(ymd) - start).count() + 1; if (U != U_trial) goto broken; } if (W != not_a_week_num) { auto start = sys_days(Monday[1]/January/ymd.year()); auto W_trial = floor(sys_days(ymd) - start).count() + 1; if (W != W_trial) goto broken; } } } fds.ymd = ymd; if (I != not_a_hour_12_value) { if (!(1 <= I && I <= 12)) goto broken; if (p != not_a_ampm) { // p is in [0, 1] == [AM, PM] // Store trial H in I if (I == 12) --p; I += p*12; // Either set H from I or make sure H and I are consistent if (H == not_a_hour) H = I; else if (I != H) goto broken; } else // p == not_a_ampm { // if H, make sure H and I could be consistent if (H != not_a_hour) { if (I == 12) { if (H != 0 && H != 12) goto broken; } else if (!(I == H || I == H+12)) { goto broken; } } else // I is ambiguous, AM or PM? goto broken; } } if (H != not_a_hour) { fds.has_tod = true; fds.tod = hh_mm_ss{hours{H}}; } if (M != not_a_minute) { fds.has_tod = true; fds.tod.m_ = minutes{M}; } if (s != not_a_second) { fds.has_tod = true; fds.tod.s_ = detail::decimal_format_seconds{s}; } if (j != not_a_doy) { fds.has_tod = true; fds.tod.h_ += hours{days{j}}; } if (wd != not_a_weekday) fds.wd = weekday{static_cast(wd)}; if (abbrev != nullptr) *abbrev = std::move(temp_abbrev); if (offset != nullptr && temp_offset != not_a_offset) *offset = temp_offset; } return is; } broken: is.setstate(ios::failbit); return is; } template > std::basic_istream& from_stream(std::basic_istream& is, const CharT* fmt, year& y, std::basic_string* abbrev = nullptr, std::chrono::minutes* offset = nullptr) { using CT = std::chrono::seconds; fields fds{}; date::from_stream(is, fmt, fds, abbrev, offset); if (!fds.ymd.year().ok()) is.setstate(std::ios::failbit); if (!is.fail()) y = fds.ymd.year(); return is; } template > std::basic_istream& from_stream(std::basic_istream& is, const CharT* fmt, month& m, std::basic_string* abbrev = nullptr, std::chrono::minutes* offset = nullptr) { using CT = std::chrono::seconds; fields fds{}; date::from_stream(is, fmt, fds, abbrev, offset); if (!fds.ymd.month().ok()) is.setstate(std::ios::failbit); if (!is.fail()) m = fds.ymd.month(); return is; } template > std::basic_istream& from_stream(std::basic_istream& is, const CharT* fmt, day& d, std::basic_string* abbrev = nullptr, std::chrono::minutes* offset = nullptr) { using CT = std::chrono::seconds; fields fds{}; date::from_stream(is, fmt, fds, abbrev, offset); if (!fds.ymd.day().ok()) is.setstate(std::ios::failbit); if (!is.fail()) d = fds.ymd.day(); return is; } template > std::basic_istream& from_stream(std::basic_istream& is, const CharT* fmt, weekday& wd, std::basic_string* abbrev = nullptr, std::chrono::minutes* offset = nullptr) { using CT = std::chrono::seconds; fields fds{}; date::from_stream(is, fmt, fds, abbrev, offset); if (!fds.wd.ok()) is.setstate(std::ios::failbit); if (!is.fail()) wd = fds.wd; return is; } template > std::basic_istream& from_stream(std::basic_istream& is, const CharT* fmt, year_month& ym, std::basic_string* abbrev = nullptr, std::chrono::minutes* offset = nullptr) { using CT = std::chrono::seconds; fields fds{}; date::from_stream(is, fmt, fds, abbrev, offset); if (!fds.ymd.month().ok()) is.setstate(std::ios::failbit); if (!is.fail()) ym = fds.ymd.year()/fds.ymd.month(); return is; } template > std::basic_istream& from_stream(std::basic_istream& is, const CharT* fmt, month_day& md, std::basic_string* abbrev = nullptr, std::chrono::minutes* offset = nullptr) { using CT = std::chrono::seconds; fields fds{}; date::from_stream(is, fmt, fds, abbrev, offset); if (!fds.ymd.month().ok() || !fds.ymd.day().ok()) is.setstate(std::ios::failbit); if (!is.fail()) md = fds.ymd.month()/fds.ymd.day(); return is; } template > std::basic_istream& from_stream(std::basic_istream& is, const CharT* fmt, year_month_day& ymd, std::basic_string* abbrev = nullptr, std::chrono::minutes* offset = nullptr) { using CT = std::chrono::seconds; fields fds{}; date::from_stream(is, fmt, fds, abbrev, offset); if (!fds.ymd.ok()) is.setstate(std::ios::failbit); if (!is.fail()) ymd = fds.ymd; return is; } template > std::basic_istream& from_stream(std::basic_istream& is, const CharT* fmt, sys_time& tp, std::basic_string* abbrev = nullptr, std::chrono::minutes* offset = nullptr) { using CT = typename std::common_type::type; using detail::round_i; std::chrono::minutes offset_local{}; auto offptr = offset ? offset : &offset_local; fields fds{}; fds.has_tod = true; date::from_stream(is, fmt, fds, abbrev, offptr); if (!fds.ymd.ok() || !fds.tod.in_conventional_range()) is.setstate(std::ios::failbit); if (!is.fail()) tp = round_i(sys_days(fds.ymd) - *offptr + fds.tod.to_duration()); return is; } template > std::basic_istream& from_stream(std::basic_istream& is, const CharT* fmt, local_time& tp, std::basic_string* abbrev = nullptr, std::chrono::minutes* offset = nullptr) { using CT = typename std::common_type::type; using detail::round_i; fields fds{}; fds.has_tod = true; date::from_stream(is, fmt, fds, abbrev, offset); if (!fds.ymd.ok() || !fds.tod.in_conventional_range()) is.setstate(std::ios::failbit); if (!is.fail()) tp = round_i(local_seconds{local_days(fds.ymd)} + fds.tod.to_duration()); return is; } template > std::basic_istream& from_stream(std::basic_istream& is, const CharT* fmt, std::chrono::duration& d, std::basic_string* abbrev = nullptr, std::chrono::minutes* offset = nullptr) { using Duration = std::chrono::duration; using CT = typename std::common_type::type; using detail::round_i; fields fds{}; date::from_stream(is, fmt, fds, abbrev, offset); if (!fds.has_tod) is.setstate(std::ios::failbit); if (!is.fail()) d = round_i(fds.tod.to_duration()); return is; } template , class Alloc = std::allocator> struct parse_manip { const std::basic_string format_; Parsable& tp_; std::basic_string* abbrev_; std::chrono::minutes* offset_; public: parse_manip(std::basic_string format, Parsable& tp, std::basic_string* abbrev = nullptr, std::chrono::minutes* offset = nullptr) : format_(std::move(format)) , tp_(tp) , abbrev_(abbrev) , offset_(offset) {} #if HAS_STRING_VIEW parse_manip(const CharT* format, Parsable& tp, std::basic_string* abbrev = nullptr, std::chrono::minutes* offset = nullptr) : format_(format) , tp_(tp) , abbrev_(abbrev) , offset_(offset) {} parse_manip(std::basic_string_view format, Parsable& tp, std::basic_string* abbrev = nullptr, std::chrono::minutes* offset = nullptr) : format_(format) , tp_(tp) , abbrev_(abbrev) , offset_(offset) {} #endif // HAS_STRING_VIEW }; #ifdef _MSC_VER template std::basic_istream& operator>>(std::basic_istream& is, const parse_manip& x) { return date::from_stream(is, x.format_.c_str(), x.tp_, x.abbrev_, x.offset_); } template inline auto parse(const std::basic_string& format, Parsable& tp) -> decltype(date::from_stream(std::declval&>(), format.c_str(), tp), parse_manip{format, tp}) { return {format, tp}; } template inline auto parse(const std::basic_string& format, Parsable& tp, std::basic_string& abbrev) -> decltype(date::from_stream(std::declval&>(), format.c_str(), tp, &abbrev), parse_manip{format, tp, &abbrev}) { return {format, tp, &abbrev}; } template inline auto parse(const std::basic_string& format, Parsable& tp, std::chrono::minutes& offset) -> decltype(date::from_stream(std::declval&>(), format.c_str(), tp, std::declval*>(), &offset), parse_manip{format, tp, nullptr, &offset}) { return {format, tp, nullptr, &offset}; } template inline auto parse(const std::basic_string& format, Parsable& tp, std::basic_string& abbrev, std::chrono::minutes& offset) -> decltype(date::from_stream(std::declval&>(), format.c_str(), tp, &abbrev, &offset), parse_manip{format, tp, &abbrev, &offset}) { return {format, tp, &abbrev, &offset}; } // const CharT* formats template inline auto parse(const CharT* format, Parsable& tp) -> decltype(date::from_stream(std::declval&>(), format, tp), parse_manip{format, tp}) { return {format, tp}; } template inline auto parse(const CharT* format, Parsable& tp, std::basic_string& abbrev) -> decltype(date::from_stream(std::declval&>(), format, tp, &abbrev), parse_manip{format, tp, &abbrev}) { return {format, tp, &abbrev}; } template inline auto parse(const CharT* format, Parsable& tp, std::chrono::minutes& offset) -> decltype(date::from_stream(std::declval&>(), format, tp, std::declval*>(), &offset), parse_manip{format, tp, nullptr, &offset}) { return {format, tp, nullptr, &offset}; } template inline auto parse(const CharT* format, Parsable& tp, std::basic_string& abbrev, std::chrono::minutes& offset) -> decltype(date::from_stream(std::declval&>(), format, tp, &abbrev, &offset), parse_manip{format, tp, &abbrev, &offset}) { return {format, tp, &abbrev, &offset}; } #else // !defined _MSC_VER template std::basic_istream& operator>>(std::basic_istream& is, const parse_manip& x) { return from_stream(is, x.format_.c_str(), x.tp_, x.abbrev_, x.offset_); } template inline auto parse(const std::basic_string& format, Parsable& tp) -> decltype(from_stream(std::declval&>(), format.c_str(), tp), parse_manip{format, tp}) { return {format, tp}; } template inline auto parse(const std::basic_string& format, Parsable& tp, std::basic_string& abbrev) -> decltype(from_stream(std::declval&>(), format.c_str(), tp, &abbrev), parse_manip{format, tp, &abbrev}) { return {format, tp, &abbrev}; } template inline auto parse(const std::basic_string& format, Parsable& tp, std::chrono::minutes& offset) -> decltype(from_stream(std::declval&>(), format.c_str(), tp, std::declval*>(), &offset), parse_manip{format, tp, nullptr, &offset}) { return {format, tp, nullptr, &offset}; } template inline auto parse(const std::basic_string& format, Parsable& tp, std::basic_string& abbrev, std::chrono::minutes& offset) -> decltype(from_stream(std::declval&>(), format.c_str(), tp, &abbrev, &offset), parse_manip{format, tp, &abbrev, &offset}) { return {format, tp, &abbrev, &offset}; } // const CharT* formats template inline auto parse(const CharT* format, Parsable& tp) -> decltype(from_stream(std::declval&>(), format, tp), parse_manip{format, tp}) { return {format, tp}; } template inline auto parse(const CharT* format, Parsable& tp, std::basic_string& abbrev) -> decltype(from_stream(std::declval&>(), format, tp, &abbrev), parse_manip{format, tp, &abbrev}) { return {format, tp, &abbrev}; } template inline auto parse(const CharT* format, Parsable& tp, std::chrono::minutes& offset) -> decltype(from_stream(std::declval&>(), format, tp, std::declval*>(), &offset), parse_manip{format, tp, nullptr, &offset}) { return {format, tp, nullptr, &offset}; } template inline auto parse(const CharT* format, Parsable& tp, std::basic_string& abbrev, std::chrono::minutes& offset) -> decltype(from_stream(std::declval&>(), format, tp, &abbrev, &offset), parse_manip{format, tp, &abbrev, &offset}) { return {format, tp, &abbrev, &offset}; } #endif // !defined _MSC_VER // duration streaming template inline std::basic_ostream& operator<<(std::basic_ostream& os, const std::chrono::duration& d) { return os << detail::make_string::from(d.count()) + detail::get_units(typename Period::type{}); } } // namespace date #ifdef _MSC_VER # pragma warning(pop) #endif #ifdef __GNUC__ # pragma GCC diagnostic pop #endif #endif // DATE_H pr0m1th3as-datatypes-9c9a8d3/src/date/ios.h000066400000000000000000000031111522766574100205200ustar00rootroot00000000000000// // ios.h // DateTimeLib // // The MIT License (MIT) // // Copyright (c) 2016 Alexander Kormanovsky // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. #ifndef ios_hpp #define ios_hpp #if __APPLE__ # include # if TARGET_OS_IPHONE # include namespace date { namespace iOSUtils { std::string get_tzdata_path(); std::string get_current_timezone(); } // namespace iOSUtils } // namespace date # endif // TARGET_OS_IPHONE #else // !__APPLE__ # define TARGET_OS_IPHONE 0 #endif // !__APPLE__ #endif // ios_hpp pr0m1th3as-datatypes-9c9a8d3/src/date/islamic.h000066400000000000000000002127131522766574100213610ustar00rootroot00000000000000#ifndef ISLAMIC_H #define ISLAMIC_H // The MIT License (MIT) // // Copyright (c) 2016 Howard Hinnant // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. // // Our apologies. When the previous paragraph was written, lowercase had not yet // been invented (that would involve another several millennia of evolution). // We did not mean to shout. #include "date.h" namespace islamic { // durations using days = date::days; using weeks = date::weeks; using years = std::chrono::duration , days::period>>; using months = std::chrono::duration >>; // time_point using sys_days = date::sys_days; using local_days = date::local_days; // types struct last_spec { explicit last_spec() = default; }; class day; class month; class year; class weekday; class weekday_indexed; class weekday_last; class month_day; class month_day_last; class month_weekday; class month_weekday_last; class year_month; class year_month_day; class year_month_day_last; class year_month_weekday; class year_month_weekday_last; // date composition operators CONSTCD11 year_month operator/(const year& y, const month& m) NOEXCEPT; CONSTCD11 year_month operator/(const year& y, int m) NOEXCEPT; CONSTCD11 month_day operator/(const day& d, const month& m) NOEXCEPT; CONSTCD11 month_day operator/(const day& d, int m) NOEXCEPT; CONSTCD11 month_day operator/(const month& m, const day& d) NOEXCEPT; CONSTCD11 month_day operator/(const month& m, int d) NOEXCEPT; CONSTCD11 month_day operator/(int m, const day& d) NOEXCEPT; CONSTCD11 month_day_last operator/(const month& m, last_spec) NOEXCEPT; CONSTCD11 month_day_last operator/(int m, last_spec) NOEXCEPT; CONSTCD11 month_day_last operator/(last_spec, const month& m) NOEXCEPT; CONSTCD11 month_day_last operator/(last_spec, int m) NOEXCEPT; CONSTCD11 month_weekday operator/(const month& m, const weekday_indexed& wdi) NOEXCEPT; CONSTCD11 month_weekday operator/(int m, const weekday_indexed& wdi) NOEXCEPT; CONSTCD11 month_weekday operator/(const weekday_indexed& wdi, const month& m) NOEXCEPT; CONSTCD11 month_weekday operator/(const weekday_indexed& wdi, int m) NOEXCEPT; CONSTCD11 month_weekday_last operator/(const month& m, const weekday_last& wdl) NOEXCEPT; CONSTCD11 month_weekday_last operator/(int m, const weekday_last& wdl) NOEXCEPT; CONSTCD11 month_weekday_last operator/(const weekday_last& wdl, const month& m) NOEXCEPT; CONSTCD11 month_weekday_last operator/(const weekday_last& wdl, int m) NOEXCEPT; CONSTCD11 year_month_day operator/(const year_month& ym, const day& d) NOEXCEPT; CONSTCD11 year_month_day operator/(const year_month& ym, int d) NOEXCEPT; CONSTCD11 year_month_day operator/(const year& y, const month_day& md) NOEXCEPT; CONSTCD11 year_month_day operator/(int y, const month_day& md) NOEXCEPT; CONSTCD11 year_month_day operator/(const month_day& md, const year& y) NOEXCEPT; CONSTCD11 year_month_day operator/(const month_day& md, int y) NOEXCEPT; CONSTCD11 year_month_day_last operator/(const year_month& ym, last_spec) NOEXCEPT; CONSTCD11 year_month_day_last operator/(const year& y, const month_day_last& mdl) NOEXCEPT; CONSTCD11 year_month_day_last operator/(int y, const month_day_last& mdl) NOEXCEPT; CONSTCD11 year_month_day_last operator/(const month_day_last& mdl, const year& y) NOEXCEPT; CONSTCD11 year_month_day_last operator/(const month_day_last& mdl, int y) NOEXCEPT; CONSTCD11 year_month_weekday operator/(const year_month& ym, const weekday_indexed& wdi) NOEXCEPT; CONSTCD11 year_month_weekday operator/(const year& y, const month_weekday& mwd) NOEXCEPT; CONSTCD11 year_month_weekday operator/(int y, const month_weekday& mwd) NOEXCEPT; CONSTCD11 year_month_weekday operator/(const month_weekday& mwd, const year& y) NOEXCEPT; CONSTCD11 year_month_weekday operator/(const month_weekday& mwd, int y) NOEXCEPT; CONSTCD11 year_month_weekday_last operator/(const year_month& ym, const weekday_last& wdl) NOEXCEPT; CONSTCD11 year_month_weekday_last operator/(const year& y, const month_weekday_last& mwdl) NOEXCEPT; CONSTCD11 year_month_weekday_last operator/(int y, const month_weekday_last& mwdl) NOEXCEPT; CONSTCD11 year_month_weekday_last operator/(const month_weekday_last& mwdl, const year& y) NOEXCEPT; CONSTCD11 year_month_weekday_last operator/(const month_weekday_last& mwdl, int y) NOEXCEPT; // Detailed interface // day class day { unsigned char d_; public: explicit CONSTCD11 day(unsigned d) NOEXCEPT; CONSTCD14 day& operator++() NOEXCEPT; CONSTCD14 day operator++(int) NOEXCEPT; CONSTCD14 day& operator--() NOEXCEPT; CONSTCD14 day operator--(int) NOEXCEPT; CONSTCD14 day& operator+=(const days& d) NOEXCEPT; CONSTCD14 day& operator-=(const days& d) NOEXCEPT; CONSTCD11 explicit operator unsigned() const NOEXCEPT; CONSTCD11 bool ok() const NOEXCEPT; }; CONSTCD11 bool operator==(const day& x, const day& y) NOEXCEPT; CONSTCD11 bool operator!=(const day& x, const day& y) NOEXCEPT; CONSTCD11 bool operator< (const day& x, const day& y) NOEXCEPT; CONSTCD11 bool operator> (const day& x, const day& y) NOEXCEPT; CONSTCD11 bool operator<=(const day& x, const day& y) NOEXCEPT; CONSTCD11 bool operator>=(const day& x, const day& y) NOEXCEPT; CONSTCD11 day operator+(const day& x, const days& y) NOEXCEPT; CONSTCD11 day operator+(const days& x, const day& y) NOEXCEPT; CONSTCD11 day operator-(const day& x, const days& y) NOEXCEPT; CONSTCD11 days operator-(const day& x, const day& y) NOEXCEPT; template std::basic_ostream& operator<<(std::basic_ostream& os, const day& d); // month class month { unsigned char m_; public: explicit CONSTCD11 month(unsigned m) NOEXCEPT; CONSTCD14 month& operator++() NOEXCEPT; CONSTCD14 month operator++(int) NOEXCEPT; CONSTCD14 month& operator--() NOEXCEPT; CONSTCD14 month operator--(int) NOEXCEPT; CONSTCD14 month& operator+=(const months& m) NOEXCEPT; CONSTCD14 month& operator-=(const months& m) NOEXCEPT; CONSTCD11 explicit operator unsigned() const NOEXCEPT; CONSTCD11 bool ok() const NOEXCEPT; }; CONSTCD11 bool operator==(const month& x, const month& y) NOEXCEPT; CONSTCD11 bool operator!=(const month& x, const month& y) NOEXCEPT; CONSTCD11 bool operator< (const month& x, const month& y) NOEXCEPT; CONSTCD11 bool operator> (const month& x, const month& y) NOEXCEPT; CONSTCD11 bool operator<=(const month& x, const month& y) NOEXCEPT; CONSTCD11 bool operator>=(const month& x, const month& y) NOEXCEPT; CONSTCD14 month operator+(const month& x, const months& y) NOEXCEPT; CONSTCD14 month operator+(const months& x, const month& y) NOEXCEPT; CONSTCD14 month operator-(const month& x, const months& y) NOEXCEPT; CONSTCD14 months operator-(const month& x, const month& y) NOEXCEPT; template std::basic_ostream& operator<<(std::basic_ostream& os, const month& m); // year class year { short y_; public: explicit CONSTCD11 year(int y) NOEXCEPT; CONSTCD14 year& operator++() NOEXCEPT; CONSTCD14 year operator++(int) NOEXCEPT; CONSTCD14 year& operator--() NOEXCEPT; CONSTCD14 year operator--(int) NOEXCEPT; CONSTCD14 year& operator+=(const years& y) NOEXCEPT; CONSTCD14 year& operator-=(const years& y) NOEXCEPT; CONSTCD14 bool is_leap() const NOEXCEPT; CONSTCD11 explicit operator int() const NOEXCEPT; CONSTCD11 bool ok() const NOEXCEPT; static CONSTCD11 year min() NOEXCEPT; static CONSTCD11 year max() NOEXCEPT; }; CONSTCD11 bool operator==(const year& x, const year& y) NOEXCEPT; CONSTCD11 bool operator!=(const year& x, const year& y) NOEXCEPT; CONSTCD11 bool operator< (const year& x, const year& y) NOEXCEPT; CONSTCD11 bool operator> (const year& x, const year& y) NOEXCEPT; CONSTCD11 bool operator<=(const year& x, const year& y) NOEXCEPT; CONSTCD11 bool operator>=(const year& x, const year& y) NOEXCEPT; CONSTCD11 year operator+(const year& x, const years& y) NOEXCEPT; CONSTCD11 year operator+(const years& x, const year& y) NOEXCEPT; CONSTCD11 year operator-(const year& x, const years& y) NOEXCEPT; CONSTCD11 years operator-(const year& x, const year& y) NOEXCEPT; template std::basic_ostream& operator<<(std::basic_ostream& os, const year& y); // weekday class weekday { unsigned char wd_; public: explicit CONSTCD11 weekday(unsigned wd) NOEXCEPT; explicit weekday(int) = delete; CONSTCD11 weekday(const sys_days& dp) NOEXCEPT; CONSTCD11 explicit weekday(const local_days& dp) NOEXCEPT; CONSTCD14 weekday& operator++() NOEXCEPT; CONSTCD14 weekday operator++(int) NOEXCEPT; CONSTCD14 weekday& operator--() NOEXCEPT; CONSTCD14 weekday operator--(int) NOEXCEPT; CONSTCD14 weekday& operator+=(const days& d) NOEXCEPT; CONSTCD14 weekday& operator-=(const days& d) NOEXCEPT; CONSTCD11 explicit operator unsigned() const NOEXCEPT; CONSTCD11 bool ok() const NOEXCEPT; CONSTCD11 weekday_indexed operator[](unsigned index) const NOEXCEPT; CONSTCD11 weekday_last operator[](last_spec) const NOEXCEPT; private: static CONSTCD11 unsigned char weekday_from_days(int z) NOEXCEPT; }; CONSTCD11 bool operator==(const weekday& x, const weekday& y) NOEXCEPT; CONSTCD11 bool operator!=(const weekday& x, const weekday& y) NOEXCEPT; CONSTCD14 weekday operator+(const weekday& x, const days& y) NOEXCEPT; CONSTCD14 weekday operator+(const days& x, const weekday& y) NOEXCEPT; CONSTCD14 weekday operator-(const weekday& x, const days& y) NOEXCEPT; CONSTCD14 days operator-(const weekday& x, const weekday& y) NOEXCEPT; template std::basic_ostream& operator<<(std::basic_ostream& os, const weekday& wd); // weekday_indexed class weekday_indexed { unsigned char wd_ : 4; unsigned char index_ : 4; public: CONSTCD11 weekday_indexed(const islamic::weekday& wd, unsigned index) NOEXCEPT; CONSTCD11 islamic::weekday weekday() const NOEXCEPT; CONSTCD11 unsigned index() const NOEXCEPT; CONSTCD11 bool ok() const NOEXCEPT; }; CONSTCD11 bool operator==(const weekday_indexed& x, const weekday_indexed& y) NOEXCEPT; CONSTCD11 bool operator!=(const weekday_indexed& x, const weekday_indexed& y) NOEXCEPT; template std::basic_ostream& operator<<(std::basic_ostream& os, const weekday_indexed& wdi); // weekday_last class weekday_last { islamic::weekday wd_; public: explicit CONSTCD11 weekday_last(const islamic::weekday& wd) NOEXCEPT; CONSTCD11 islamic::weekday weekday() const NOEXCEPT; CONSTCD11 bool ok() const NOEXCEPT; }; CONSTCD11 bool operator==(const weekday_last& x, const weekday_last& y) NOEXCEPT; CONSTCD11 bool operator!=(const weekday_last& x, const weekday_last& y) NOEXCEPT; template std::basic_ostream& operator<<(std::basic_ostream& os, const weekday_last& wdl); // year_month class year_month { islamic::year y_; islamic::month m_; public: CONSTCD11 year_month(const islamic::year& y, const islamic::month& m) NOEXCEPT; CONSTCD11 islamic::year year() const NOEXCEPT; CONSTCD11 islamic::month month() const NOEXCEPT; CONSTCD14 year_month& operator+=(const months& dm) NOEXCEPT; CONSTCD14 year_month& operator-=(const months& dm) NOEXCEPT; CONSTCD14 year_month& operator+=(const years& dy) NOEXCEPT; CONSTCD14 year_month& operator-=(const years& dy) NOEXCEPT; CONSTCD11 bool ok() const NOEXCEPT; }; CONSTCD11 bool operator==(const year_month& x, const year_month& y) NOEXCEPT; CONSTCD11 bool operator!=(const year_month& x, const year_month& y) NOEXCEPT; CONSTCD11 bool operator< (const year_month& x, const year_month& y) NOEXCEPT; CONSTCD11 bool operator> (const year_month& x, const year_month& y) NOEXCEPT; CONSTCD11 bool operator<=(const year_month& x, const year_month& y) NOEXCEPT; CONSTCD11 bool operator>=(const year_month& x, const year_month& y) NOEXCEPT; CONSTCD14 year_month operator+(const year_month& ym, const months& dm) NOEXCEPT; CONSTCD14 year_month operator+(const months& dm, const year_month& ym) NOEXCEPT; CONSTCD14 year_month operator-(const year_month& ym, const months& dm) NOEXCEPT; CONSTCD11 months operator-(const year_month& x, const year_month& y) NOEXCEPT; CONSTCD11 year_month operator+(const year_month& ym, const years& dy) NOEXCEPT; CONSTCD11 year_month operator+(const years& dy, const year_month& ym) NOEXCEPT; CONSTCD11 year_month operator-(const year_month& ym, const years& dy) NOEXCEPT; template std::basic_ostream& operator<<(std::basic_ostream& os, const year_month& ym); // month_day class month_day { islamic::month m_; islamic::day d_; public: CONSTCD11 month_day(const islamic::month& m, const islamic::day& d) NOEXCEPT; CONSTCD11 islamic::month month() const NOEXCEPT; CONSTCD11 islamic::day day() const NOEXCEPT; CONSTCD14 bool ok() const NOEXCEPT; }; CONSTCD11 bool operator==(const month_day& x, const month_day& y) NOEXCEPT; CONSTCD11 bool operator!=(const month_day& x, const month_day& y) NOEXCEPT; CONSTCD11 bool operator< (const month_day& x, const month_day& y) NOEXCEPT; CONSTCD11 bool operator> (const month_day& x, const month_day& y) NOEXCEPT; CONSTCD11 bool operator<=(const month_day& x, const month_day& y) NOEXCEPT; CONSTCD11 bool operator>=(const month_day& x, const month_day& y) NOEXCEPT; template std::basic_ostream& operator<<(std::basic_ostream& os, const month_day& md); // month_day_last class month_day_last { islamic::month m_; public: CONSTCD11 explicit month_day_last(const islamic::month& m) NOEXCEPT; CONSTCD11 islamic::month month() const NOEXCEPT; CONSTCD11 bool ok() const NOEXCEPT; }; CONSTCD11 bool operator==(const month_day_last& x, const month_day_last& y) NOEXCEPT; CONSTCD11 bool operator!=(const month_day_last& x, const month_day_last& y) NOEXCEPT; CONSTCD11 bool operator< (const month_day_last& x, const month_day_last& y) NOEXCEPT; CONSTCD11 bool operator> (const month_day_last& x, const month_day_last& y) NOEXCEPT; CONSTCD11 bool operator<=(const month_day_last& x, const month_day_last& y) NOEXCEPT; CONSTCD11 bool operator>=(const month_day_last& x, const month_day_last& y) NOEXCEPT; template std::basic_ostream& operator<<(std::basic_ostream& os, const month_day_last& mdl); // month_weekday class month_weekday { islamic::month m_; islamic::weekday_indexed wdi_; public: CONSTCD11 month_weekday(const islamic::month& m, const islamic::weekday_indexed& wdi) NOEXCEPT; CONSTCD11 islamic::month month() const NOEXCEPT; CONSTCD11 islamic::weekday_indexed weekday_indexed() const NOEXCEPT; CONSTCD11 bool ok() const NOEXCEPT; }; CONSTCD11 bool operator==(const month_weekday& x, const month_weekday& y) NOEXCEPT; CONSTCD11 bool operator!=(const month_weekday& x, const month_weekday& y) NOEXCEPT; template std::basic_ostream& operator<<(std::basic_ostream& os, const month_weekday& mwd); // month_weekday_last class month_weekday_last { islamic::month m_; islamic::weekday_last wdl_; public: CONSTCD11 month_weekday_last(const islamic::month& m, const islamic::weekday_last& wd) NOEXCEPT; CONSTCD11 islamic::month month() const NOEXCEPT; CONSTCD11 islamic::weekday_last weekday_last() const NOEXCEPT; CONSTCD11 bool ok() const NOEXCEPT; }; CONSTCD11 bool operator==(const month_weekday_last& x, const month_weekday_last& y) NOEXCEPT; CONSTCD11 bool operator!=(const month_weekday_last& x, const month_weekday_last& y) NOEXCEPT; template std::basic_ostream& operator<<(std::basic_ostream& os, const month_weekday_last& mwdl); // class year_month_day class year_month_day { islamic::year y_; islamic::month m_; islamic::day d_; public: CONSTCD11 year_month_day(const islamic::year& y, const islamic::month& m, const islamic::day& d) NOEXCEPT; CONSTCD14 year_month_day(const year_month_day_last& ymdl) NOEXCEPT; CONSTCD14 year_month_day(sys_days dp) NOEXCEPT; CONSTCD14 explicit year_month_day(local_days dp) NOEXCEPT; CONSTCD14 year_month_day& operator+=(const months& m) NOEXCEPT; CONSTCD14 year_month_day& operator-=(const months& m) NOEXCEPT; CONSTCD14 year_month_day& operator+=(const years& y) NOEXCEPT; CONSTCD14 year_month_day& operator-=(const years& y) NOEXCEPT; CONSTCD11 islamic::year year() const NOEXCEPT; CONSTCD11 islamic::month month() const NOEXCEPT; CONSTCD11 islamic::day day() const NOEXCEPT; CONSTCD14 operator sys_days() const NOEXCEPT; CONSTCD14 explicit operator local_days() const NOEXCEPT; CONSTCD14 bool ok() const NOEXCEPT; private: static CONSTCD14 year_month_day from_days(days dp) NOEXCEPT; CONSTCD14 days to_days() const NOEXCEPT; }; CONSTCD11 bool operator==(const year_month_day& x, const year_month_day& y) NOEXCEPT; CONSTCD11 bool operator!=(const year_month_day& x, const year_month_day& y) NOEXCEPT; CONSTCD11 bool operator< (const year_month_day& x, const year_month_day& y) NOEXCEPT; CONSTCD11 bool operator> (const year_month_day& x, const year_month_day& y) NOEXCEPT; CONSTCD11 bool operator<=(const year_month_day& x, const year_month_day& y) NOEXCEPT; CONSTCD11 bool operator>=(const year_month_day& x, const year_month_day& y) NOEXCEPT; CONSTCD14 year_month_day operator+(const year_month_day& ymd, const months& dm) NOEXCEPT; CONSTCD14 year_month_day operator+(const months& dm, const year_month_day& ymd) NOEXCEPT; CONSTCD14 year_month_day operator-(const year_month_day& ymd, const months& dm) NOEXCEPT; CONSTCD11 year_month_day operator+(const year_month_day& ymd, const years& dy) NOEXCEPT; CONSTCD11 year_month_day operator+(const years& dy, const year_month_day& ymd) NOEXCEPT; CONSTCD11 year_month_day operator-(const year_month_day& ymd, const years& dy) NOEXCEPT; template std::basic_ostream& operator<<(std::basic_ostream& os, const year_month_day& ymd); // year_month_day_last class year_month_day_last { islamic::year y_; islamic::month_day_last mdl_; public: CONSTCD11 year_month_day_last(const islamic::year& y, const islamic::month_day_last& mdl) NOEXCEPT; CONSTCD14 year_month_day_last& operator+=(const months& m) NOEXCEPT; CONSTCD14 year_month_day_last& operator-=(const months& m) NOEXCEPT; CONSTCD14 year_month_day_last& operator+=(const years& y) NOEXCEPT; CONSTCD14 year_month_day_last& operator-=(const years& y) NOEXCEPT; CONSTCD11 islamic::year year() const NOEXCEPT; CONSTCD11 islamic::month month() const NOEXCEPT; CONSTCD11 islamic::month_day_last month_day_last() const NOEXCEPT; CONSTCD14 islamic::day day() const NOEXCEPT; CONSTCD14 operator sys_days() const NOEXCEPT; CONSTCD14 explicit operator local_days() const NOEXCEPT; CONSTCD11 bool ok() const NOEXCEPT; }; CONSTCD11 bool operator==(const year_month_day_last& x, const year_month_day_last& y) NOEXCEPT; CONSTCD11 bool operator!=(const year_month_day_last& x, const year_month_day_last& y) NOEXCEPT; CONSTCD11 bool operator< (const year_month_day_last& x, const year_month_day_last& y) NOEXCEPT; CONSTCD11 bool operator> (const year_month_day_last& x, const year_month_day_last& y) NOEXCEPT; CONSTCD11 bool operator<=(const year_month_day_last& x, const year_month_day_last& y) NOEXCEPT; CONSTCD11 bool operator>=(const year_month_day_last& x, const year_month_day_last& y) NOEXCEPT; CONSTCD14 year_month_day_last operator+(const year_month_day_last& ymdl, const months& dm) NOEXCEPT; CONSTCD14 year_month_day_last operator+(const months& dm, const year_month_day_last& ymdl) NOEXCEPT; CONSTCD11 year_month_day_last operator+(const year_month_day_last& ymdl, const years& dy) NOEXCEPT; CONSTCD11 year_month_day_last operator+(const years& dy, const year_month_day_last& ymdl) NOEXCEPT; CONSTCD14 year_month_day_last operator-(const year_month_day_last& ymdl, const months& dm) NOEXCEPT; CONSTCD11 year_month_day_last operator-(const year_month_day_last& ymdl, const years& dy) NOEXCEPT; template std::basic_ostream& operator<<(std::basic_ostream& os, const year_month_day_last& ymdl); // year_month_weekday class year_month_weekday { islamic::year y_; islamic::month m_; islamic::weekday_indexed wdi_; public: CONSTCD11 year_month_weekday(const islamic::year& y, const islamic::month& m, const islamic::weekday_indexed& wdi) NOEXCEPT; CONSTCD14 year_month_weekday(const sys_days& dp) NOEXCEPT; CONSTCD14 explicit year_month_weekday(const local_days& dp) NOEXCEPT; CONSTCD14 year_month_weekday& operator+=(const months& m) NOEXCEPT; CONSTCD14 year_month_weekday& operator-=(const months& m) NOEXCEPT; CONSTCD14 year_month_weekday& operator+=(const years& y) NOEXCEPT; CONSTCD14 year_month_weekday& operator-=(const years& y) NOEXCEPT; CONSTCD11 islamic::year year() const NOEXCEPT; CONSTCD11 islamic::month month() const NOEXCEPT; CONSTCD11 islamic::weekday weekday() const NOEXCEPT; CONSTCD11 unsigned index() const NOEXCEPT; CONSTCD11 islamic::weekday_indexed weekday_indexed() const NOEXCEPT; CONSTCD14 operator sys_days() const NOEXCEPT; CONSTCD14 explicit operator local_days() const NOEXCEPT; CONSTCD14 bool ok() const NOEXCEPT; private: static CONSTCD14 year_month_weekday from_days(days dp) NOEXCEPT; CONSTCD14 days to_days() const NOEXCEPT; }; CONSTCD11 bool operator==(const year_month_weekday& x, const year_month_weekday& y) NOEXCEPT; CONSTCD11 bool operator!=(const year_month_weekday& x, const year_month_weekday& y) NOEXCEPT; CONSTCD14 year_month_weekday operator+(const year_month_weekday& ymwd, const months& dm) NOEXCEPT; CONSTCD14 year_month_weekday operator+(const months& dm, const year_month_weekday& ymwd) NOEXCEPT; CONSTCD11 year_month_weekday operator+(const year_month_weekday& ymwd, const years& dy) NOEXCEPT; CONSTCD11 year_month_weekday operator+(const years& dy, const year_month_weekday& ymwd) NOEXCEPT; CONSTCD14 year_month_weekday operator-(const year_month_weekday& ymwd, const months& dm) NOEXCEPT; CONSTCD11 year_month_weekday operator-(const year_month_weekday& ymwd, const years& dy) NOEXCEPT; template std::basic_ostream& operator<<(std::basic_ostream& os, const year_month_weekday& ymwdi); // year_month_weekday_last class year_month_weekday_last { islamic::year y_; islamic::month m_; islamic::weekday_last wdl_; public: CONSTCD11 year_month_weekday_last(const islamic::year& y, const islamic::month& m, const islamic::weekday_last& wdl) NOEXCEPT; CONSTCD14 year_month_weekday_last& operator+=(const months& m) NOEXCEPT; CONSTCD14 year_month_weekday_last& operator-=(const months& m) NOEXCEPT; CONSTCD14 year_month_weekday_last& operator+=(const years& y) NOEXCEPT; CONSTCD14 year_month_weekday_last& operator-=(const years& y) NOEXCEPT; CONSTCD11 islamic::year year() const NOEXCEPT; CONSTCD11 islamic::month month() const NOEXCEPT; CONSTCD11 islamic::weekday weekday() const NOEXCEPT; CONSTCD11 islamic::weekday_last weekday_last() const NOEXCEPT; CONSTCD14 operator sys_days() const NOEXCEPT; CONSTCD14 explicit operator local_days() const NOEXCEPT; CONSTCD11 bool ok() const NOEXCEPT; private: CONSTCD14 days to_days() const NOEXCEPT; }; CONSTCD11 bool operator==(const year_month_weekday_last& x, const year_month_weekday_last& y) NOEXCEPT; CONSTCD11 bool operator!=(const year_month_weekday_last& x, const year_month_weekday_last& y) NOEXCEPT; CONSTCD14 year_month_weekday_last operator+(const year_month_weekday_last& ymwdl, const months& dm) NOEXCEPT; CONSTCD14 year_month_weekday_last operator+(const months& dm, const year_month_weekday_last& ymwdl) NOEXCEPT; CONSTCD11 year_month_weekday_last operator+(const year_month_weekday_last& ymwdl, const years& dy) NOEXCEPT; CONSTCD11 year_month_weekday_last operator+(const years& dy, const year_month_weekday_last& ymwdl) NOEXCEPT; CONSTCD14 year_month_weekday_last operator-(const year_month_weekday_last& ymwdl, const months& dm) NOEXCEPT; CONSTCD11 year_month_weekday_last operator-(const year_month_weekday_last& ymwdl, const years& dy) NOEXCEPT; template std::basic_ostream& operator<<(std::basic_ostream& os, const year_month_weekday_last& ymwdl); #if !defined(_MSC_VER) || (_MSC_VER >= 1900) inline namespace literals { CONSTCD11 islamic::day operator ""_d(unsigned long long d) NOEXCEPT; CONSTCD11 islamic::year operator ""_y(unsigned long long y) NOEXCEPT; } // inline namespace literals #endif // !defined(_MSC_VER) || (_MSC_VER >= 1900) //----------------+ // Implementation | //----------------+ // day CONSTCD11 inline day::day(unsigned d) NOEXCEPT : d_(static_cast(d)) {} CONSTCD14 inline day& day::operator++() NOEXCEPT {++d_; return *this;} CONSTCD14 inline day day::operator++(int) NOEXCEPT {auto tmp(*this); ++(*this); return tmp;} CONSTCD14 inline day& day::operator--() NOEXCEPT {--d_; return *this;} CONSTCD14 inline day day::operator--(int) NOEXCEPT {auto tmp(*this); --(*this); return tmp;} CONSTCD14 inline day& day::operator+=(const days& d) NOEXCEPT {*this = *this + d; return *this;} CONSTCD14 inline day& day::operator-=(const days& d) NOEXCEPT {*this = *this - d; return *this;} CONSTCD11 inline day::operator unsigned() const NOEXCEPT {return d_;} CONSTCD11 inline bool day::ok() const NOEXCEPT {return 1 <= d_ && d_ <= 30;} CONSTCD11 inline bool operator==(const day& x, const day& y) NOEXCEPT { return static_cast(x) == static_cast(y); } CONSTCD11 inline bool operator!=(const day& x, const day& y) NOEXCEPT { return !(x == y); } CONSTCD11 inline bool operator<(const day& x, const day& y) NOEXCEPT { return static_cast(x) < static_cast(y); } CONSTCD11 inline bool operator>(const day& x, const day& y) NOEXCEPT { return y < x; } CONSTCD11 inline bool operator<=(const day& x, const day& y) NOEXCEPT { return !(y < x); } CONSTCD11 inline bool operator>=(const day& x, const day& y) NOEXCEPT { return !(x < y); } CONSTCD11 inline days operator-(const day& x, const day& y) NOEXCEPT { return days{static_cast(static_cast(x) - static_cast(y))}; } CONSTCD11 inline day operator+(const day& x, const days& y) NOEXCEPT { return day{static_cast(x) + static_cast(y.count())}; } CONSTCD11 inline day operator+(const days& x, const day& y) NOEXCEPT { return y + x; } CONSTCD11 inline day operator-(const day& x, const days& y) NOEXCEPT { return x + -y; } template inline std::basic_ostream& operator<<(std::basic_ostream& os, const day& d) { date::detail::save_ostream _(os); os.fill('0'); os.flags(std::ios::dec | std::ios::right); os.width(2); os << static_cast(d); return os; } // month CONSTCD11 inline month::month(unsigned m) NOEXCEPT : m_(static_cast(m)) {} CONSTCD14 inline month& month::operator++() NOEXCEPT {if (++m_ == 13) m_ = 1; return *this;} CONSTCD14 inline month month::operator++(int) NOEXCEPT {auto tmp(*this); ++(*this); return tmp;} CONSTCD14 inline month& month::operator--() NOEXCEPT {if (--m_ == 0) m_ = 12; return *this;} CONSTCD14 inline month month::operator--(int) NOEXCEPT {auto tmp(*this); --(*this); return tmp;} CONSTCD14 inline month& month::operator+=(const months& m) NOEXCEPT { *this = *this + m; return *this; } CONSTCD14 inline month& month::operator-=(const months& m) NOEXCEPT { *this = *this - m; return *this; } CONSTCD11 inline month::operator unsigned() const NOEXCEPT {return m_;} CONSTCD11 inline bool month::ok() const NOEXCEPT {return 1 <= m_ && m_ <= 12;} CONSTCD11 inline bool operator==(const month& x, const month& y) NOEXCEPT { return static_cast(x) == static_cast(y); } CONSTCD11 inline bool operator!=(const month& x, const month& y) NOEXCEPT { return !(x == y); } CONSTCD11 inline bool operator<(const month& x, const month& y) NOEXCEPT { return static_cast(x) < static_cast(y); } CONSTCD11 inline bool operator>(const month& x, const month& y) NOEXCEPT { return y < x; } CONSTCD11 inline bool operator<=(const month& x, const month& y) NOEXCEPT { return !(y < x); } CONSTCD11 inline bool operator>=(const month& x, const month& y) NOEXCEPT { return !(x < y); } CONSTCD14 inline months operator-(const month& x, const month& y) NOEXCEPT { auto const d = static_cast(x) - static_cast(y); return months(d <= 11 ? d : d + 12); } CONSTCD14 inline month operator+(const month& x, const months& y) NOEXCEPT { auto const mu = static_cast(static_cast(x)) - 1 + y.count(); auto const yr = (mu >= 0 ? mu : mu-11) / 12; return month{static_cast(mu - yr * 12 + 1)}; } CONSTCD14 inline month operator+(const months& x, const month& y) NOEXCEPT { return y + x; } CONSTCD14 inline month operator-(const month& x, const months& y) NOEXCEPT { return x + -y; } template inline std::basic_ostream& operator<<(std::basic_ostream& os, const month& m) { switch (static_cast(m)) { case 1: os << "Muharram"; break; case 2: os << "Safar"; break; case 3: os << "Rabi' al-awwal"; break; case 4: os << "Rabi' al-thani"; break; case 5: os << "Jumada al-awwal"; break; case 6: os << "Jumada al-Thani"; break; case 7: os << "Rajab"; break; case 8: os << "Sha'ban"; break; case 9: os << "Ramadan"; break; case 10: os << "Shawwal"; break; case 11: os << "Dhu al-Qi'dah"; break; case 12: os << "Dhu al-Hijjah"; break; default: os << static_cast(m) << " is not a valid month"; break; } return os; } // year CONSTCD11 inline year::year(int y) NOEXCEPT : y_(static_cast(y)) {} CONSTCD14 inline year& year::operator++() NOEXCEPT {++y_; return *this;} CONSTCD14 inline year year::operator++(int) NOEXCEPT {auto tmp(*this); ++(*this); return tmp;} CONSTCD14 inline year& year::operator--() NOEXCEPT {--y_; return *this;} CONSTCD14 inline year year::operator--(int) NOEXCEPT {auto tmp(*this); --(*this); return tmp;} CONSTCD14 inline year& year::operator+=(const years& y) NOEXCEPT {*this = *this + y; return *this;} CONSTCD14 inline year& year::operator-=(const years& y) NOEXCEPT {*this = *this - y; return *this;} CONSTCD14 inline bool year::is_leap() const NOEXCEPT { int y = y_ - 1; const int era = (y >= 0 ? y : y-29) / 30; const unsigned yoe = static_cast(y - era * 30); switch (yoe) { case 1: case 4: case 6: case 9: case 12: case 15: case 17: case 20: case 23: case 25: case 28: return true; default: return false; } } CONSTCD11 inline year::operator int() const NOEXCEPT {return y_;} CONSTCD11 inline bool year::ok() const NOEXCEPT {return true;} CONSTCD11 inline year year::min() NOEXCEPT { return year{std::numeric_limits::min()}; } CONSTCD11 inline year year::max() NOEXCEPT { return year{std::numeric_limits::max()}; } CONSTCD11 inline bool operator==(const year& x, const year& y) NOEXCEPT { return static_cast(x) == static_cast(y); } CONSTCD11 inline bool operator!=(const year& x, const year& y) NOEXCEPT { return !(x == y); } CONSTCD11 inline bool operator<(const year& x, const year& y) NOEXCEPT { return static_cast(x) < static_cast(y); } CONSTCD11 inline bool operator>(const year& x, const year& y) NOEXCEPT { return y < x; } CONSTCD11 inline bool operator<=(const year& x, const year& y) NOEXCEPT { return !(y < x); } CONSTCD11 inline bool operator>=(const year& x, const year& y) NOEXCEPT { return !(x < y); } CONSTCD11 inline years operator-(const year& x, const year& y) NOEXCEPT { return years{static_cast(x) - static_cast(y)}; } CONSTCD11 inline year operator+(const year& x, const years& y) NOEXCEPT { return year{static_cast(x) + y.count()}; } CONSTCD11 inline year operator+(const years& x, const year& y) NOEXCEPT { return y + x; } CONSTCD11 inline year operator-(const year& x, const years& y) NOEXCEPT { return year{static_cast(x) - y.count()}; } template inline std::basic_ostream& operator<<(std::basic_ostream& os, const year& y) { date::detail::save_ostream _(os); os.fill('0'); os.flags(std::ios::dec | std::ios::internal); os.width(4 + (y < year{0})); os << static_cast(y); return os; } // weekday CONSTCD11 inline unsigned char weekday::weekday_from_days(int z) NOEXCEPT { return static_cast(static_cast( z >= -4 ? (z+4) % 7 : (z+5) % 7 + 6)); } CONSTCD11 inline weekday::weekday(unsigned wd) NOEXCEPT : wd_(static_cast(wd)) {} CONSTCD11 inline weekday::weekday(const sys_days& dp) NOEXCEPT : wd_(weekday_from_days(dp.time_since_epoch().count())) {} CONSTCD11 inline weekday::weekday(const local_days& dp) NOEXCEPT : wd_(weekday_from_days(dp.time_since_epoch().count())) {} CONSTCD14 inline weekday& weekday::operator++() NOEXCEPT {if (++wd_ == 7) wd_ = 0; return *this;} CONSTCD14 inline weekday weekday::operator++(int) NOEXCEPT {auto tmp(*this); ++(*this); return tmp;} CONSTCD14 inline weekday& weekday::operator--() NOEXCEPT {if (wd_-- == 0) wd_ = 6; return *this;} CONSTCD14 inline weekday weekday::operator--(int) NOEXCEPT {auto tmp(*this); --(*this); return tmp;} CONSTCD14 inline weekday& weekday::operator+=(const days& d) NOEXCEPT { *this = *this + d; return *this; } CONSTCD14 inline weekday& weekday::operator-=(const days& d) NOEXCEPT { *this = *this - d; return *this; } CONSTCD11 inline weekday::operator unsigned() const NOEXCEPT { return static_cast(wd_); } CONSTCD11 inline bool weekday::ok() const NOEXCEPT {return wd_ <= 6;} CONSTCD11 inline bool operator==(const weekday& x, const weekday& y) NOEXCEPT { return static_cast(x) == static_cast(y); } CONSTCD11 inline bool operator!=(const weekday& x, const weekday& y) NOEXCEPT { return !(x == y); } CONSTCD14 inline days operator-(const weekday& x, const weekday& y) NOEXCEPT { auto const diff = static_cast(x) - static_cast(y); return days{diff <= 6 ? diff : diff + 7}; } CONSTCD14 inline weekday operator+(const weekday& x, const days& y) NOEXCEPT { auto const wdu = static_cast(static_cast(x)) + y.count(); auto const wk = (wdu >= 0 ? wdu : wdu-6) / 7; return weekday{static_cast(wdu - wk * 7)}; } CONSTCD14 inline weekday operator+(const days& x, const weekday& y) NOEXCEPT { return y + x; } CONSTCD14 inline weekday operator-(const weekday& x, const days& y) NOEXCEPT { return x + -y; } template inline std::basic_ostream& operator<<(std::basic_ostream& os, const weekday& wd) { switch (static_cast(wd)) { case 0: os << "al-Aḥad"; break; case 1: os << "al-Ithnayn"; break; case 2: os << "ath-Thulāthā’"; break; case 3: os << "al-Arba‘ā’"; break; case 4: os << "al-Khamīs"; break; case 5: os << "al-Jum‘ah"; break; case 6: os << "as-Sabt"; break; default: os << static_cast(wd) << " is not a valid weekday"; break; } return os; } #if !defined(_MSC_VER) || (_MSC_VER >= 1900) inline namespace literals { CONSTCD11 inline islamic::day operator ""_d(unsigned long long d) NOEXCEPT { return islamic::day{static_cast(d)}; } CONSTCD11 inline islamic::year operator ""_y(unsigned long long y) NOEXCEPT { return islamic::year(static_cast(y)); } #endif // !defined(_MSC_VER) || (_MSC_VER >= 1900) CONSTDATA islamic::last_spec last{}; #if !defined(_MSC_VER) || (_MSC_VER >= 1900) } // inline namespace literals #endif // weekday_indexed CONSTCD11 inline weekday weekday_indexed::weekday() const NOEXCEPT { return islamic::weekday{static_cast(wd_)}; } CONSTCD11 inline unsigned weekday_indexed::index() const NOEXCEPT {return index_;} CONSTCD11 inline bool weekday_indexed::ok() const NOEXCEPT { return weekday().ok() && 1 <= index_ && index_ <= 5; } CONSTCD11 inline weekday_indexed::weekday_indexed(const islamic::weekday& wd, unsigned index) NOEXCEPT : wd_(static_cast(static_cast(wd))) , index_(static_cast(index)) {} template inline std::basic_ostream& operator<<(std::basic_ostream& os, const weekday_indexed& wdi) { return os << wdi.weekday() << '[' << wdi.index() << ']'; } CONSTCD11 inline weekday_indexed weekday::operator[](unsigned index) const NOEXCEPT { return {*this, index}; } CONSTCD11 inline bool operator==(const weekday_indexed& x, const weekday_indexed& y) NOEXCEPT { return x.weekday() == y.weekday() && x.index() == y.index(); } CONSTCD11 inline bool operator!=(const weekday_indexed& x, const weekday_indexed& y) NOEXCEPT { return !(x == y); } // weekday_last CONSTCD11 inline islamic::weekday weekday_last::weekday() const NOEXCEPT {return wd_;} CONSTCD11 inline bool weekday_last::ok() const NOEXCEPT {return wd_.ok();} CONSTCD11 inline weekday_last::weekday_last(const islamic::weekday& wd) NOEXCEPT : wd_(wd) {} CONSTCD11 inline bool operator==(const weekday_last& x, const weekday_last& y) NOEXCEPT { return x.weekday() == y.weekday(); } CONSTCD11 inline bool operator!=(const weekday_last& x, const weekday_last& y) NOEXCEPT { return !(x == y); } template inline std::basic_ostream& operator<<(std::basic_ostream& os, const weekday_last& wdl) { return os << wdl.weekday() << "[last]"; } CONSTCD11 inline weekday_last weekday::operator[](last_spec) const NOEXCEPT { return weekday_last{*this}; } // year_month CONSTCD11 inline year_month::year_month(const islamic::year& y, const islamic::month& m) NOEXCEPT : y_(y) , m_(m) {} CONSTCD11 inline year year_month::year() const NOEXCEPT {return y_;} CONSTCD11 inline month year_month::month() const NOEXCEPT {return m_;} CONSTCD11 inline bool year_month::ok() const NOEXCEPT {return y_.ok() && m_.ok();} CONSTCD14 inline year_month& year_month::operator+=(const months& dm) NOEXCEPT { *this = *this + dm; return *this; } CONSTCD14 inline year_month& year_month::operator-=(const months& dm) NOEXCEPT { *this = *this - dm; return *this; } CONSTCD14 inline year_month& year_month::operator+=(const years& dy) NOEXCEPT { *this = *this + dy; return *this; } CONSTCD14 inline year_month& year_month::operator-=(const years& dy) NOEXCEPT { *this = *this - dy; return *this; } CONSTCD11 inline bool operator==(const year_month& x, const year_month& y) NOEXCEPT { return x.year() == y.year() && x.month() == y.month(); } CONSTCD11 inline bool operator!=(const year_month& x, const year_month& y) NOEXCEPT { return !(x == y); } CONSTCD11 inline bool operator<(const year_month& x, const year_month& y) NOEXCEPT { return x.year() < y.year() ? true : (x.year() > y.year() ? false : (x.month() < y.month())); } CONSTCD11 inline bool operator>(const year_month& x, const year_month& y) NOEXCEPT { return y < x; } CONSTCD11 inline bool operator<=(const year_month& x, const year_month& y) NOEXCEPT { return !(y < x); } CONSTCD11 inline bool operator>=(const year_month& x, const year_month& y) NOEXCEPT { return !(x < y); } CONSTCD14 inline year_month operator+(const year_month& ym, const months& dm) NOEXCEPT { auto dmi = static_cast(static_cast(ym.month())) - 1 + dm.count(); auto dy = (dmi >= 0 ? dmi : dmi-11) / 12; dmi = dmi - dy * 12 + 1; return (ym.year() + years(dy)) / month(static_cast(dmi)); } CONSTCD14 inline year_month operator+(const months& dm, const year_month& ym) NOEXCEPT { return ym + dm; } CONSTCD14 inline year_month operator-(const year_month& ym, const months& dm) NOEXCEPT { return ym + -dm; } CONSTCD11 inline months operator-(const year_month& x, const year_month& y) NOEXCEPT { return (x.year() - y.year()) + months(static_cast(x.month()) - static_cast(y.month())); } CONSTCD11 inline year_month operator+(const year_month& ym, const years& dy) NOEXCEPT { return (ym.year() + dy) / ym.month(); } CONSTCD11 inline year_month operator+(const years& dy, const year_month& ym) NOEXCEPT { return ym + dy; } CONSTCD11 inline year_month operator-(const year_month& ym, const years& dy) NOEXCEPT { return ym + -dy; } template inline std::basic_ostream& operator<<(std::basic_ostream& os, const year_month& ym) { return os << ym.year() << '/' << ym.month(); } // month_day CONSTCD11 inline month_day::month_day(const islamic::month& m, const islamic::day& d) NOEXCEPT : m_(m) , d_(d) {} CONSTCD11 inline islamic::month month_day::month() const NOEXCEPT {return m_;} CONSTCD11 inline islamic::day month_day::day() const NOEXCEPT {return d_;} CONSTCD14 inline bool month_day::ok() const NOEXCEPT { CONSTDATA islamic::day d[] = {30_d, 29_d, 30_d, 29_d, 30_d, 29_d, 30_d, 29_d, 30_d, 29_d, 30_d, 30_d}; return m_.ok() && 1_d <= d_ && d_ <= d[static_cast(m_)-1]; } CONSTCD11 inline bool operator==(const month_day& x, const month_day& y) NOEXCEPT { return x.month() == y.month() && x.day() == y.day(); } CONSTCD11 inline bool operator!=(const month_day& x, const month_day& y) NOEXCEPT { return !(x == y); } CONSTCD11 inline bool operator<(const month_day& x, const month_day& y) NOEXCEPT { return x.month() < y.month() ? true : (x.month() > y.month() ? false : (x.day() < y.day())); } CONSTCD11 inline bool operator>(const month_day& x, const month_day& y) NOEXCEPT { return y < x; } CONSTCD11 inline bool operator<=(const month_day& x, const month_day& y) NOEXCEPT { return !(y < x); } CONSTCD11 inline bool operator>=(const month_day& x, const month_day& y) NOEXCEPT { return !(x < y); } template inline std::basic_ostream& operator<<(std::basic_ostream& os, const month_day& md) { return os << md.month() << '/' << md.day(); } // month_day_last CONSTCD11 inline month month_day_last::month() const NOEXCEPT {return m_;} CONSTCD11 inline bool month_day_last::ok() const NOEXCEPT {return m_.ok();} CONSTCD11 inline month_day_last::month_day_last(const islamic::month& m) NOEXCEPT : m_(m) {} CONSTCD11 inline bool operator==(const month_day_last& x, const month_day_last& y) NOEXCEPT { return x.month() == y.month(); } CONSTCD11 inline bool operator!=(const month_day_last& x, const month_day_last& y) NOEXCEPT { return !(x == y); } CONSTCD11 inline bool operator<(const month_day_last& x, const month_day_last& y) NOEXCEPT { return x.month() < y.month(); } CONSTCD11 inline bool operator>(const month_day_last& x, const month_day_last& y) NOEXCEPT { return y < x; } CONSTCD11 inline bool operator<=(const month_day_last& x, const month_day_last& y) NOEXCEPT { return !(y < x); } CONSTCD11 inline bool operator>=(const month_day_last& x, const month_day_last& y) NOEXCEPT { return !(x < y); } template inline std::basic_ostream& operator<<(std::basic_ostream& os, const month_day_last& mdl) { return os << mdl.month() << "/last"; } // month_weekday CONSTCD11 inline month_weekday::month_weekday(const islamic::month& m, const islamic::weekday_indexed& wdi) NOEXCEPT : m_(m) , wdi_(wdi) {} CONSTCD11 inline month month_weekday::month() const NOEXCEPT {return m_;} CONSTCD11 inline weekday_indexed month_weekday::weekday_indexed() const NOEXCEPT { return wdi_; } CONSTCD11 inline bool month_weekday::ok() const NOEXCEPT { return m_.ok() && wdi_.ok(); } CONSTCD11 inline bool operator==(const month_weekday& x, const month_weekday& y) NOEXCEPT { return x.month() == y.month() && x.weekday_indexed() == y.weekday_indexed(); } CONSTCD11 inline bool operator!=(const month_weekday& x, const month_weekday& y) NOEXCEPT { return !(x == y); } template inline std::basic_ostream& operator<<(std::basic_ostream& os, const month_weekday& mwd) { return os << mwd.month() << '/' << mwd.weekday_indexed(); } // month_weekday_last CONSTCD11 inline month_weekday_last::month_weekday_last(const islamic::month& m, const islamic::weekday_last& wdl) NOEXCEPT : m_(m) , wdl_(wdl) {} CONSTCD11 inline month month_weekday_last::month() const NOEXCEPT {return m_;} CONSTCD11 inline weekday_last month_weekday_last::weekday_last() const NOEXCEPT { return wdl_; } CONSTCD11 inline bool month_weekday_last::ok() const NOEXCEPT { return m_.ok() && wdl_.ok(); } CONSTCD11 inline bool operator==(const month_weekday_last& x, const month_weekday_last& y) NOEXCEPT { return x.month() == y.month() && x.weekday_last() == y.weekday_last(); } CONSTCD11 inline bool operator!=(const month_weekday_last& x, const month_weekday_last& y) NOEXCEPT { return !(x == y); } template inline std::basic_ostream& operator<<(std::basic_ostream& os, const month_weekday_last& mwdl) { return os << mwdl.month() << '/' << mwdl.weekday_last(); } // year_month_day_last CONSTCD11 inline year_month_day_last::year_month_day_last(const islamic::year& y, const islamic::month_day_last& mdl) NOEXCEPT : y_(y) , mdl_(mdl) {} CONSTCD14 inline year_month_day_last& year_month_day_last::operator+=(const months& m) NOEXCEPT { *this = *this + m; return *this; } CONSTCD14 inline year_month_day_last& year_month_day_last::operator-=(const months& m) NOEXCEPT { *this = *this - m; return *this; } CONSTCD14 inline year_month_day_last& year_month_day_last::operator+=(const years& y) NOEXCEPT { *this = *this + y; return *this; } CONSTCD14 inline year_month_day_last& year_month_day_last::operator-=(const years& y) NOEXCEPT { *this = *this - y; return *this; } CONSTCD11 inline year year_month_day_last::year() const NOEXCEPT {return y_;} CONSTCD11 inline month year_month_day_last::month() const NOEXCEPT {return mdl_.month();} CONSTCD11 inline month_day_last year_month_day_last::month_day_last() const NOEXCEPT { return mdl_; } CONSTCD14 inline day year_month_day_last::day() const NOEXCEPT { CONSTDATA islamic::day d[] = {30_d, 29_d, 30_d, 29_d, 30_d, 29_d, 30_d, 29_d, 30_d, 29_d, 30_d, 29_d}; return month() != islamic::month(12) || !y_.is_leap() ? d[static_cast(month())-1] : 30_d; } CONSTCD14 inline year_month_day_last::operator sys_days() const NOEXCEPT { return sys_days(year()/month()/day()); } CONSTCD14 inline year_month_day_last::operator local_days() const NOEXCEPT { return local_days(year()/month()/day()); } CONSTCD11 inline bool year_month_day_last::ok() const NOEXCEPT { return y_.ok() && mdl_.ok(); } CONSTCD11 inline bool operator==(const year_month_day_last& x, const year_month_day_last& y) NOEXCEPT { return x.year() == y.year() && x.month_day_last() == y.month_day_last(); } CONSTCD11 inline bool operator!=(const year_month_day_last& x, const year_month_day_last& y) NOEXCEPT { return !(x == y); } CONSTCD11 inline bool operator<(const year_month_day_last& x, const year_month_day_last& y) NOEXCEPT { return x.year() < y.year() ? true : (x.year() > y.year() ? false : (x.month_day_last() < y.month_day_last())); } CONSTCD11 inline bool operator>(const year_month_day_last& x, const year_month_day_last& y) NOEXCEPT { return y < x; } CONSTCD11 inline bool operator<=(const year_month_day_last& x, const year_month_day_last& y) NOEXCEPT { return !(y < x); } CONSTCD11 inline bool operator>=(const year_month_day_last& x, const year_month_day_last& y) NOEXCEPT { return !(x < y); } template inline std::basic_ostream& operator<<(std::basic_ostream& os, const year_month_day_last& ymdl) { return os << ymdl.year() << '/' << ymdl.month_day_last(); } CONSTCD14 inline year_month_day_last operator+(const year_month_day_last& ymdl, const months& dm) NOEXCEPT { return (ymdl.year() / ymdl.month() + dm) / last; } CONSTCD14 inline year_month_day_last operator+(const months& dm, const year_month_day_last& ymdl) NOEXCEPT { return ymdl + dm; } CONSTCD14 inline year_month_day_last operator-(const year_month_day_last& ymdl, const months& dm) NOEXCEPT { return ymdl + (-dm); } CONSTCD11 inline year_month_day_last operator+(const year_month_day_last& ymdl, const years& dy) NOEXCEPT { return {ymdl.year()+dy, ymdl.month_day_last()}; } CONSTCD11 inline year_month_day_last operator+(const years& dy, const year_month_day_last& ymdl) NOEXCEPT { return ymdl + dy; } CONSTCD11 inline year_month_day_last operator-(const year_month_day_last& ymdl, const years& dy) NOEXCEPT { return ymdl + (-dy); } // year_month_day CONSTCD11 inline year_month_day::year_month_day(const islamic::year& y, const islamic::month& m, const islamic::day& d) NOEXCEPT : y_(y) , m_(m) , d_(d) {} CONSTCD14 inline year_month_day::year_month_day(const year_month_day_last& ymdl) NOEXCEPT : y_(ymdl.year()) , m_(ymdl.month()) , d_(ymdl.day()) {} CONSTCD14 inline year_month_day::year_month_day(sys_days dp) NOEXCEPT : year_month_day(from_days(dp.time_since_epoch())) {} CONSTCD14 inline year_month_day::year_month_day(local_days dp) NOEXCEPT : year_month_day(from_days(dp.time_since_epoch())) {} CONSTCD11 inline year year_month_day::year() const NOEXCEPT {return y_;} CONSTCD11 inline month year_month_day::month() const NOEXCEPT {return m_;} CONSTCD11 inline day year_month_day::day() const NOEXCEPT {return d_;} CONSTCD14 inline year_month_day& year_month_day::operator+=(const months& m) NOEXCEPT { *this = *this + m; return *this; } CONSTCD14 inline year_month_day& year_month_day::operator-=(const months& m) NOEXCEPT { *this = *this - m; return *this; } CONSTCD14 inline year_month_day& year_month_day::operator+=(const years& y) NOEXCEPT { *this = *this + y; return *this; } CONSTCD14 inline year_month_day& year_month_day::operator-=(const years& y) NOEXCEPT { *this = *this - y; return *this; } CONSTCD14 inline days year_month_day::to_days() const NOEXCEPT { static_assert(std::numeric_limits::digits >= 18, "This algorithm has not been ported to a 16 bit unsigned integer"); static_assert(std::numeric_limits::digits >= 20, "This algorithm has not been ported to a 16 bit signed integer"); auto const y = static_cast(y_) - 1; auto const m = static_cast(m_); auto const d = static_cast(d_); auto const era = (y >= 0 ? y : y-29) / 30; auto const yoe = static_cast(y - era * 30); // [0, 29] auto const doy = 29*(m-1) + m/2 + d-1; // [0, 354] auto const doe = yoe * 354 + (11*(yoe+1)+3)/30 + doy; // [0, 10630] return days{era * 10631 + static_cast(doe) - 492148}; } CONSTCD14 inline year_month_day::operator sys_days() const NOEXCEPT { return sys_days{to_days()}; } CONSTCD14 inline year_month_day::operator local_days() const NOEXCEPT { return local_days{to_days()}; } CONSTCD14 inline bool year_month_day::ok() const NOEXCEPT { if (!(y_.ok() && m_.ok())) return false; return 1_d <= d_ && d_ <= (y_/m_/last).day(); } CONSTCD11 inline bool operator==(const year_month_day& x, const year_month_day& y) NOEXCEPT { return x.year() == y.year() && x.month() == y.month() && x.day() == y.day(); } CONSTCD11 inline bool operator!=(const year_month_day& x, const year_month_day& y) NOEXCEPT { return !(x == y); } CONSTCD11 inline bool operator<(const year_month_day& x, const year_month_day& y) NOEXCEPT { return x.year() < y.year() ? true : (x.year() > y.year() ? false : (x.month() < y.month() ? true : (x.month() > y.month() ? false : (x.day() < y.day())))); } CONSTCD11 inline bool operator>(const year_month_day& x, const year_month_day& y) NOEXCEPT { return y < x; } CONSTCD11 inline bool operator<=(const year_month_day& x, const year_month_day& y) NOEXCEPT { return !(y < x); } CONSTCD11 inline bool operator>=(const year_month_day& x, const year_month_day& y) NOEXCEPT { return !(x < y); } template inline std::basic_ostream& operator<<(std::basic_ostream& os, const year_month_day& ymd) { date::detail::save_ostream _(os); os.fill('0'); os.flags(std::ios::dec | std::ios::right); os << ymd.year() << '-'; os.width(2); os << static_cast(ymd.month()) << '-'; os << ymd.day(); return os; } CONSTCD14 inline year_month_day year_month_day::from_days(days dp) NOEXCEPT { static_assert(std::numeric_limits::digits >= 18, "This algorithm has not been ported to a 16 bit unsigned integer"); static_assert(std::numeric_limits::digits >= 20, "This algorithm has not been ported to a 16 bit signed integer"); auto const z = dp.count() + 492148; auto const era = (z >= 0 ? z : z - 10630) / 10631; auto const doe = static_cast(z - era * 10631); // [0, 10630] auto const yoe = (30*doe + 10646)/10631 - 1; // [0, 29] auto const y = static_cast(yoe) + era * 30 + 1; auto const doy = doe - (yoe * 354 + (11*(yoe+1)+3)/30); // [0, 354] auto const m = (11*doy + 330) / 325; // [1, 12] auto const d = doy - (29*(m-1) + m/2) + 1; // [1, 30] return year_month_day{islamic::year{y}, islamic::month(m), islamic::day(d)}; } CONSTCD14 inline year_month_day operator+(const year_month_day& ymd, const months& dm) NOEXCEPT { return (ymd.year() / ymd.month() + dm) / ymd.day(); } CONSTCD14 inline year_month_day operator+(const months& dm, const year_month_day& ymd) NOEXCEPT { return ymd + dm; } CONSTCD14 inline year_month_day operator-(const year_month_day& ymd, const months& dm) NOEXCEPT { return ymd + (-dm); } CONSTCD11 inline year_month_day operator+(const year_month_day& ymd, const years& dy) NOEXCEPT { return (ymd.year() + dy) / ymd.month() / ymd.day(); } CONSTCD11 inline year_month_day operator+(const years& dy, const year_month_day& ymd) NOEXCEPT { return ymd + dy; } CONSTCD11 inline year_month_day operator-(const year_month_day& ymd, const years& dy) NOEXCEPT { return ymd + (-dy); } // year_month_weekday CONSTCD11 inline year_month_weekday::year_month_weekday(const islamic::year& y, const islamic::month& m, const islamic::weekday_indexed& wdi) NOEXCEPT : y_(y) , m_(m) , wdi_(wdi) {} CONSTCD14 inline year_month_weekday::year_month_weekday(const sys_days& dp) NOEXCEPT : year_month_weekday(from_days(dp.time_since_epoch())) {} CONSTCD14 inline year_month_weekday::year_month_weekday(const local_days& dp) NOEXCEPT : year_month_weekday(from_days(dp.time_since_epoch())) {} CONSTCD14 inline year_month_weekday& year_month_weekday::operator+=(const months& m) NOEXCEPT { *this = *this + m; return *this; } CONSTCD14 inline year_month_weekday& year_month_weekday::operator-=(const months& m) NOEXCEPT { *this = *this - m; return *this; } CONSTCD14 inline year_month_weekday& year_month_weekday::operator+=(const years& y) NOEXCEPT { *this = *this + y; return *this; } CONSTCD14 inline year_month_weekday& year_month_weekday::operator-=(const years& y) NOEXCEPT { *this = *this - y; return *this; } CONSTCD11 inline year year_month_weekday::year() const NOEXCEPT {return y_;} CONSTCD11 inline month year_month_weekday::month() const NOEXCEPT {return m_;} CONSTCD11 inline weekday year_month_weekday::weekday() const NOEXCEPT { return wdi_.weekday(); } CONSTCD11 inline unsigned year_month_weekday::index() const NOEXCEPT { return wdi_.index(); } CONSTCD11 inline weekday_indexed year_month_weekday::weekday_indexed() const NOEXCEPT { return wdi_; } CONSTCD14 inline year_month_weekday::operator sys_days() const NOEXCEPT { return sys_days{to_days()}; } CONSTCD14 inline year_month_weekday::operator local_days() const NOEXCEPT { return local_days{to_days()}; } CONSTCD14 inline bool year_month_weekday::ok() const NOEXCEPT { if (!y_.ok() || !m_.ok() || !wdi_.weekday().ok() || wdi_.index() < 1) return false; if (wdi_.index() <= 4) return true; auto d2 = wdi_.weekday() - islamic::weekday(y_/m_/1) + days((wdi_.index()-1)*7 + 1); return static_cast(d2.count()) <= static_cast((y_/m_/last).day()); } CONSTCD14 inline year_month_weekday year_month_weekday::from_days(days d) NOEXCEPT { sys_days dp{d}; auto const wd = islamic::weekday(dp); auto const ymd = year_month_day(dp); return {ymd.year(), ymd.month(), wd[(static_cast(ymd.day())-1)/7+1]}; } CONSTCD14 inline days year_month_weekday::to_days() const NOEXCEPT { auto d = sys_days(y_/m_/1); return (d + (wdi_.weekday() - islamic::weekday(d) + days{(wdi_.index()-1)*7}) ).time_since_epoch(); } CONSTCD11 inline bool operator==(const year_month_weekday& x, const year_month_weekday& y) NOEXCEPT { return x.year() == y.year() && x.month() == y.month() && x.weekday_indexed() == y.weekday_indexed(); } CONSTCD11 inline bool operator!=(const year_month_weekday& x, const year_month_weekday& y) NOEXCEPT { return !(x == y); } template inline std::basic_ostream& operator<<(std::basic_ostream& os, const year_month_weekday& ymwdi) { return os << ymwdi.year() << '/' << ymwdi.month() << '/' << ymwdi.weekday_indexed(); } CONSTCD14 inline year_month_weekday operator+(const year_month_weekday& ymwd, const months& dm) NOEXCEPT { return (ymwd.year() / ymwd.month() + dm) / ymwd.weekday_indexed(); } CONSTCD14 inline year_month_weekday operator+(const months& dm, const year_month_weekday& ymwd) NOEXCEPT { return ymwd + dm; } CONSTCD14 inline year_month_weekday operator-(const year_month_weekday& ymwd, const months& dm) NOEXCEPT { return ymwd + (-dm); } CONSTCD11 inline year_month_weekday operator+(const year_month_weekday& ymwd, const years& dy) NOEXCEPT { return {ymwd.year()+dy, ymwd.month(), ymwd.weekday_indexed()}; } CONSTCD11 inline year_month_weekday operator+(const years& dy, const year_month_weekday& ymwd) NOEXCEPT { return ymwd + dy; } CONSTCD11 inline year_month_weekday operator-(const year_month_weekday& ymwd, const years& dy) NOEXCEPT { return ymwd + (-dy); } // year_month_weekday_last CONSTCD11 inline year_month_weekday_last::year_month_weekday_last(const islamic::year& y, const islamic::month& m, const islamic::weekday_last& wdl) NOEXCEPT : y_(y) , m_(m) , wdl_(wdl) {} CONSTCD14 inline year_month_weekday_last& year_month_weekday_last::operator+=(const months& m) NOEXCEPT { *this = *this + m; return *this; } CONSTCD14 inline year_month_weekday_last& year_month_weekday_last::operator-=(const months& m) NOEXCEPT { *this = *this - m; return *this; } CONSTCD14 inline year_month_weekday_last& year_month_weekday_last::operator+=(const years& y) NOEXCEPT { *this = *this + y; return *this; } CONSTCD14 inline year_month_weekday_last& year_month_weekday_last::operator-=(const years& y) NOEXCEPT { *this = *this - y; return *this; } CONSTCD11 inline year year_month_weekday_last::year() const NOEXCEPT {return y_;} CONSTCD11 inline month year_month_weekday_last::month() const NOEXCEPT {return m_;} CONSTCD11 inline weekday year_month_weekday_last::weekday() const NOEXCEPT { return wdl_.weekday(); } CONSTCD11 inline weekday_last year_month_weekday_last::weekday_last() const NOEXCEPT { return wdl_; } CONSTCD14 inline year_month_weekday_last::operator sys_days() const NOEXCEPT { return sys_days{to_days()}; } CONSTCD14 inline year_month_weekday_last::operator local_days() const NOEXCEPT { return local_days{to_days()}; } CONSTCD11 inline bool year_month_weekday_last::ok() const NOEXCEPT { return y_.ok() && m_.ok() && wdl_.ok(); } CONSTCD14 inline days year_month_weekday_last::to_days() const NOEXCEPT { auto const d = sys_days(y_/m_/last); return (d - (islamic::weekday{d} - wdl_.weekday())).time_since_epoch(); } CONSTCD11 inline bool operator==(const year_month_weekday_last& x, const year_month_weekday_last& y) NOEXCEPT { return x.year() == y.year() && x.month() == y.month() && x.weekday_last() == y.weekday_last(); } CONSTCD11 inline bool operator!=(const year_month_weekday_last& x, const year_month_weekday_last& y) NOEXCEPT { return !(x == y); } template inline std::basic_ostream& operator<<(std::basic_ostream& os, const year_month_weekday_last& ymwdl) { return os << ymwdl.year() << '/' << ymwdl.month() << '/' << ymwdl.weekday_last(); } CONSTCD14 inline year_month_weekday_last operator+(const year_month_weekday_last& ymwdl, const months& dm) NOEXCEPT { return (ymwdl.year() / ymwdl.month() + dm) / ymwdl.weekday_last(); } CONSTCD14 inline year_month_weekday_last operator+(const months& dm, const year_month_weekday_last& ymwdl) NOEXCEPT { return ymwdl + dm; } CONSTCD14 inline year_month_weekday_last operator-(const year_month_weekday_last& ymwdl, const months& dm) NOEXCEPT { return ymwdl + (-dm); } CONSTCD11 inline year_month_weekday_last operator+(const year_month_weekday_last& ymwdl, const years& dy) NOEXCEPT { return {ymwdl.year()+dy, ymwdl.month(), ymwdl.weekday_last()}; } CONSTCD11 inline year_month_weekday_last operator+(const years& dy, const year_month_weekday_last& ymwdl) NOEXCEPT { return ymwdl + dy; } CONSTCD11 inline year_month_weekday_last operator-(const year_month_weekday_last& ymwdl, const years& dy) NOEXCEPT { return ymwdl + (-dy); } // year_month from operator/() CONSTCD11 inline year_month operator/(const year& y, const month& m) NOEXCEPT { return {y, m}; } CONSTCD11 inline year_month operator/(const year& y, int m) NOEXCEPT { return y / month(static_cast(m)); } // month_day from operator/() CONSTCD11 inline month_day operator/(const month& m, const day& d) NOEXCEPT { return {m, d}; } CONSTCD11 inline month_day operator/(const day& d, const month& m) NOEXCEPT { return m / d; } CONSTCD11 inline month_day operator/(const month& m, int d) NOEXCEPT { return m / day(static_cast(d)); } CONSTCD11 inline month_day operator/(int m, const day& d) NOEXCEPT { return month(static_cast(m)) / d; } CONSTCD11 inline month_day operator/(const day& d, int m) NOEXCEPT {return m / d;} // month_day_last from operator/() CONSTCD11 inline month_day_last operator/(const month& m, last_spec) NOEXCEPT { return month_day_last{m}; } CONSTCD11 inline month_day_last operator/(last_spec, const month& m) NOEXCEPT { return m/last; } CONSTCD11 inline month_day_last operator/(int m, last_spec) NOEXCEPT { return month(static_cast(m))/last; } CONSTCD11 inline month_day_last operator/(last_spec, int m) NOEXCEPT { return m/last; } // month_weekday from operator/() CONSTCD11 inline month_weekday operator/(const month& m, const weekday_indexed& wdi) NOEXCEPT { return {m, wdi}; } CONSTCD11 inline month_weekday operator/(const weekday_indexed& wdi, const month& m) NOEXCEPT { return m / wdi; } CONSTCD11 inline month_weekday operator/(int m, const weekday_indexed& wdi) NOEXCEPT { return month(static_cast(m)) / wdi; } CONSTCD11 inline month_weekday operator/(const weekday_indexed& wdi, int m) NOEXCEPT { return m / wdi; } // month_weekday_last from operator/() CONSTCD11 inline month_weekday_last operator/(const month& m, const weekday_last& wdl) NOEXCEPT { return {m, wdl}; } CONSTCD11 inline month_weekday_last operator/(const weekday_last& wdl, const month& m) NOEXCEPT { return m / wdl; } CONSTCD11 inline month_weekday_last operator/(int m, const weekday_last& wdl) NOEXCEPT { return month(static_cast(m)) / wdl; } CONSTCD11 inline month_weekday_last operator/(const weekday_last& wdl, int m) NOEXCEPT { return m / wdl; } // year_month_day from operator/() CONSTCD11 inline year_month_day operator/(const year_month& ym, const day& d) NOEXCEPT { return {ym.year(), ym.month(), d}; } CONSTCD11 inline year_month_day operator/(const year_month& ym, int d) NOEXCEPT { return ym / day(static_cast(d)); } CONSTCD11 inline year_month_day operator/(const year& y, const month_day& md) NOEXCEPT { return y / md.month() / md.day(); } CONSTCD11 inline year_month_day operator/(int y, const month_day& md) NOEXCEPT { return year(y) / md; } CONSTCD11 inline year_month_day operator/(const month_day& md, const year& y) NOEXCEPT { return y / md; } CONSTCD11 inline year_month_day operator/(const month_day& md, int y) NOEXCEPT { return year(y) / md; } // year_month_day_last from operator/() CONSTCD11 inline year_month_day_last operator/(const year_month& ym, last_spec) NOEXCEPT { return {ym.year(), month_day_last{ym.month()}}; } CONSTCD11 inline year_month_day_last operator/(const year& y, const month_day_last& mdl) NOEXCEPT { return {y, mdl}; } CONSTCD11 inline year_month_day_last operator/(int y, const month_day_last& mdl) NOEXCEPT { return year(y) / mdl; } CONSTCD11 inline year_month_day_last operator/(const month_day_last& mdl, const year& y) NOEXCEPT { return y / mdl; } CONSTCD11 inline year_month_day_last operator/(const month_day_last& mdl, int y) NOEXCEPT { return year(y) / mdl; } // year_month_weekday from operator/() CONSTCD11 inline year_month_weekday operator/(const year_month& ym, const weekday_indexed& wdi) NOEXCEPT { return {ym.year(), ym.month(), wdi}; } CONSTCD11 inline year_month_weekday operator/(const year& y, const month_weekday& mwd) NOEXCEPT { return {y, mwd.month(), mwd.weekday_indexed()}; } CONSTCD11 inline year_month_weekday operator/(int y, const month_weekday& mwd) NOEXCEPT { return year(y) / mwd; } CONSTCD11 inline year_month_weekday operator/(const month_weekday& mwd, const year& y) NOEXCEPT { return y / mwd; } CONSTCD11 inline year_month_weekday operator/(const month_weekday& mwd, int y) NOEXCEPT { return year(y) / mwd; } // year_month_weekday_last from operator/() CONSTCD11 inline year_month_weekday_last operator/(const year_month& ym, const weekday_last& wdl) NOEXCEPT { return {ym.year(), ym.month(), wdl}; } CONSTCD11 inline year_month_weekday_last operator/(const year& y, const month_weekday_last& mwdl) NOEXCEPT { return {y, mwdl.month(), mwdl.weekday_last()}; } CONSTCD11 inline year_month_weekday_last operator/(int y, const month_weekday_last& mwdl) NOEXCEPT { return year(y) / mwdl; } CONSTCD11 inline year_month_weekday_last operator/(const month_weekday_last& mwdl, const year& y) NOEXCEPT { return y / mwdl; } CONSTCD11 inline year_month_weekday_last operator/(const month_weekday_last& mwdl, int y) NOEXCEPT { return year(y) / mwdl; } } // namespace islamic #endif // ISLAMIC_H pr0m1th3as-datatypes-9c9a8d3/src/date/iso_week.h000066400000000000000000001242001522766574100215360ustar00rootroot00000000000000#ifndef ISO_WEEK_H #define ISO_WEEK_H // The MIT License (MIT) // // Copyright (c) 2015, 2016, 2017 Howard Hinnant // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. #include "date.h" #include namespace iso_week { // y/wn/wd // wn/wd/y // wd/wn/y using days = date::days; using weeks = date::weeks; using years = date::years; // time_point using sys_days = date::sys_days; using local_days = date::local_days; // types struct last_week { explicit last_week() = default; }; class weekday; class weeknum; class year; class year_weeknum; class year_lastweek; class weeknum_weekday; class lastweek_weekday; class year_weeknum_weekday; class year_lastweek_weekday; // date composition operators CONSTCD11 year_weeknum operator/(const year& y, const weeknum& wn) NOEXCEPT; CONSTCD11 year_weeknum operator/(const year& y, int wn) NOEXCEPT; CONSTCD11 year_lastweek operator/(const year& y, last_week wn) NOEXCEPT; CONSTCD11 weeknum_weekday operator/(const weeknum& wn, const weekday& wd) NOEXCEPT; CONSTCD11 weeknum_weekday operator/(const weeknum& wn, int wd) NOEXCEPT; CONSTCD11 weeknum_weekday operator/(const weekday& wd, const weeknum& wn) NOEXCEPT; CONSTCD11 weeknum_weekday operator/(const weekday& wd, int wn) NOEXCEPT; CONSTCD11 lastweek_weekday operator/(const last_week& wn, const weekday& wd) NOEXCEPT; CONSTCD11 lastweek_weekday operator/(const last_week& wn, int wd) NOEXCEPT; CONSTCD11 lastweek_weekday operator/(const weekday& wd, const last_week& wn) NOEXCEPT; CONSTCD11 year_weeknum_weekday operator/(const year_weeknum& ywn, const weekday& wd) NOEXCEPT; CONSTCD11 year_weeknum_weekday operator/(const year_weeknum& ywn, int wd) NOEXCEPT; CONSTCD11 year_weeknum_weekday operator/(const weeknum_weekday& wnwd, const year& y) NOEXCEPT; CONSTCD11 year_weeknum_weekday operator/(const weeknum_weekday& wnwd, int y) NOEXCEPT; CONSTCD11 year_lastweek_weekday operator/(const year_lastweek& ylw, const weekday& wd) NOEXCEPT; CONSTCD11 year_lastweek_weekday operator/(const year_lastweek& ylw, int wd) NOEXCEPT; CONSTCD11 year_lastweek_weekday operator/(const lastweek_weekday& lwwd, const year& y) NOEXCEPT; CONSTCD11 year_lastweek_weekday operator/(const lastweek_weekday& lwwd, int y) NOEXCEPT; // weekday class weekday { unsigned char wd_; public: explicit CONSTCD11 weekday(unsigned wd) NOEXCEPT; CONSTCD11 weekday(date::weekday wd) NOEXCEPT; explicit weekday(int) = delete; CONSTCD11 weekday(const sys_days& dp) NOEXCEPT; CONSTCD11 explicit weekday(const local_days& dp) NOEXCEPT; weekday& operator++() NOEXCEPT; weekday operator++(int) NOEXCEPT; weekday& operator--() NOEXCEPT; weekday operator--(int) NOEXCEPT; weekday& operator+=(const days& d) NOEXCEPT; weekday& operator-=(const days& d) NOEXCEPT; CONSTCD11 explicit operator unsigned() const NOEXCEPT; CONSTCD11 operator date::weekday() const NOEXCEPT; CONSTCD11 bool ok() const NOEXCEPT; private: static CONSTCD11 unsigned char weekday_from_days(int z) NOEXCEPT; static CONSTCD11 unsigned char to_iso_encoding(unsigned char) NOEXCEPT; static CONSTCD11 unsigned from_iso_encoding(unsigned) NOEXCEPT; }; CONSTCD11 bool operator==(const weekday& x, const weekday& y) NOEXCEPT; CONSTCD11 bool operator!=(const weekday& x, const weekday& y) NOEXCEPT; CONSTCD14 weekday operator+(const weekday& x, const days& y) NOEXCEPT; CONSTCD14 weekday operator+(const days& x, const weekday& y) NOEXCEPT; CONSTCD14 weekday operator-(const weekday& x, const days& y) NOEXCEPT; CONSTCD14 days operator-(const weekday& x, const weekday& y) NOEXCEPT; template std::basic_ostream& operator<<(std::basic_ostream& os, const weekday& wd); // year class year { short y_; public: explicit CONSTCD11 year(int y) NOEXCEPT; year& operator++() NOEXCEPT; year operator++(int) NOEXCEPT; year& operator--() NOEXCEPT; year operator--(int) NOEXCEPT; year& operator+=(const years& y) NOEXCEPT; year& operator-=(const years& y) NOEXCEPT; CONSTCD14 bool is_leap() const NOEXCEPT; CONSTCD11 explicit operator int() const NOEXCEPT; CONSTCD11 bool ok() const NOEXCEPT; static CONSTCD11 year min() NOEXCEPT; static CONSTCD11 year max() NOEXCEPT; }; CONSTCD11 bool operator==(const year& x, const year& y) NOEXCEPT; CONSTCD11 bool operator!=(const year& x, const year& y) NOEXCEPT; CONSTCD11 bool operator< (const year& x, const year& y) NOEXCEPT; CONSTCD11 bool operator> (const year& x, const year& y) NOEXCEPT; CONSTCD11 bool operator<=(const year& x, const year& y) NOEXCEPT; CONSTCD11 bool operator>=(const year& x, const year& y) NOEXCEPT; CONSTCD11 year operator+(const year& x, const years& y) NOEXCEPT; CONSTCD11 year operator+(const years& x, const year& y) NOEXCEPT; CONSTCD11 year operator-(const year& x, const years& y) NOEXCEPT; CONSTCD11 years operator-(const year& x, const year& y) NOEXCEPT; template std::basic_ostream& operator<<(std::basic_ostream& os, const year& y); // weeknum class weeknum { unsigned char wn_; public: explicit CONSTCD11 weeknum(unsigned wn) NOEXCEPT; weeknum& operator++() NOEXCEPT; weeknum operator++(int) NOEXCEPT; weeknum& operator--() NOEXCEPT; weeknum operator--(int) NOEXCEPT; weeknum& operator+=(const weeks& y) NOEXCEPT; weeknum& operator-=(const weeks& y) NOEXCEPT; CONSTCD11 explicit operator unsigned() const NOEXCEPT; CONSTCD11 bool ok() const NOEXCEPT; }; CONSTCD11 bool operator==(const weeknum& x, const weeknum& y) NOEXCEPT; CONSTCD11 bool operator!=(const weeknum& x, const weeknum& y) NOEXCEPT; CONSTCD11 bool operator< (const weeknum& x, const weeknum& y) NOEXCEPT; CONSTCD11 bool operator> (const weeknum& x, const weeknum& y) NOEXCEPT; CONSTCD11 bool operator<=(const weeknum& x, const weeknum& y) NOEXCEPT; CONSTCD11 bool operator>=(const weeknum& x, const weeknum& y) NOEXCEPT; CONSTCD11 weeknum operator+(const weeknum& x, const weeks& y) NOEXCEPT; CONSTCD11 weeknum operator+(const weeks& x, const weeknum& y) NOEXCEPT; CONSTCD11 weeknum operator-(const weeknum& x, const weeks& y) NOEXCEPT; CONSTCD11 weeks operator-(const weeknum& x, const weeknum& y) NOEXCEPT; template std::basic_ostream& operator<<(std::basic_ostream& os, const weeknum& wn); // year_weeknum class year_weeknum { iso_week::year y_; iso_week::weeknum wn_; public: CONSTCD11 year_weeknum(const iso_week::year& y, const iso_week::weeknum& wn) NOEXCEPT; CONSTCD11 iso_week::year year() const NOEXCEPT; CONSTCD11 iso_week::weeknum weeknum() const NOEXCEPT; year_weeknum& operator+=(const years& dy) NOEXCEPT; year_weeknum& operator-=(const years& dy) NOEXCEPT; CONSTCD11 bool ok() const NOEXCEPT; }; CONSTCD11 bool operator==(const year_weeknum& x, const year_weeknum& y) NOEXCEPT; CONSTCD11 bool operator!=(const year_weeknum& x, const year_weeknum& y) NOEXCEPT; CONSTCD11 bool operator< (const year_weeknum& x, const year_weeknum& y) NOEXCEPT; CONSTCD11 bool operator> (const year_weeknum& x, const year_weeknum& y) NOEXCEPT; CONSTCD11 bool operator<=(const year_weeknum& x, const year_weeknum& y) NOEXCEPT; CONSTCD11 bool operator>=(const year_weeknum& x, const year_weeknum& y) NOEXCEPT; CONSTCD11 year_weeknum operator+(const year_weeknum& ym, const years& dy) NOEXCEPT; CONSTCD11 year_weeknum operator+(const years& dy, const year_weeknum& ym) NOEXCEPT; CONSTCD11 year_weeknum operator-(const year_weeknum& ym, const years& dy) NOEXCEPT; template std::basic_ostream& operator<<(std::basic_ostream& os, const year_weeknum& ym); // year_lastweek class year_lastweek { iso_week::year y_; public: CONSTCD11 explicit year_lastweek(const iso_week::year& y) NOEXCEPT; CONSTCD11 iso_week::year year() const NOEXCEPT; CONSTCD14 iso_week::weeknum weeknum() const NOEXCEPT; year_lastweek& operator+=(const years& dy) NOEXCEPT; year_lastweek& operator-=(const years& dy) NOEXCEPT; CONSTCD11 bool ok() const NOEXCEPT; }; CONSTCD11 bool operator==(const year_lastweek& x, const year_lastweek& y) NOEXCEPT; CONSTCD11 bool operator!=(const year_lastweek& x, const year_lastweek& y) NOEXCEPT; CONSTCD11 bool operator< (const year_lastweek& x, const year_lastweek& y) NOEXCEPT; CONSTCD11 bool operator> (const year_lastweek& x, const year_lastweek& y) NOEXCEPT; CONSTCD11 bool operator<=(const year_lastweek& x, const year_lastweek& y) NOEXCEPT; CONSTCD11 bool operator>=(const year_lastweek& x, const year_lastweek& y) NOEXCEPT; CONSTCD11 year_lastweek operator+(const year_lastweek& ym, const years& dy) NOEXCEPT; CONSTCD11 year_lastweek operator+(const years& dy, const year_lastweek& ym) NOEXCEPT; CONSTCD11 year_lastweek operator-(const year_lastweek& ym, const years& dy) NOEXCEPT; template std::basic_ostream& operator<<(std::basic_ostream& os, const year_lastweek& ym); // weeknum_weekday class weeknum_weekday { iso_week::weeknum wn_; iso_week::weekday wd_; public: CONSTCD11 weeknum_weekday(const iso_week::weeknum& wn, const iso_week::weekday& wd) NOEXCEPT; CONSTCD11 iso_week::weeknum weeknum() const NOEXCEPT; CONSTCD11 iso_week::weekday weekday() const NOEXCEPT; CONSTCD14 bool ok() const NOEXCEPT; }; CONSTCD11 bool operator==(const weeknum_weekday& x, const weeknum_weekday& y) NOEXCEPT; CONSTCD11 bool operator!=(const weeknum_weekday& x, const weeknum_weekday& y) NOEXCEPT; CONSTCD11 bool operator< (const weeknum_weekday& x, const weeknum_weekday& y) NOEXCEPT; CONSTCD11 bool operator> (const weeknum_weekday& x, const weeknum_weekday& y) NOEXCEPT; CONSTCD11 bool operator<=(const weeknum_weekday& x, const weeknum_weekday& y) NOEXCEPT; CONSTCD11 bool operator>=(const weeknum_weekday& x, const weeknum_weekday& y) NOEXCEPT; template std::basic_ostream& operator<<(std::basic_ostream& os, const weeknum_weekday& md); // lastweek_weekday class lastweek_weekday { iso_week::weekday wd_; public: CONSTCD11 explicit lastweek_weekday(const iso_week::weekday& wd) NOEXCEPT; CONSTCD11 iso_week::weekday weekday() const NOEXCEPT; CONSTCD14 bool ok() const NOEXCEPT; }; CONSTCD11 bool operator==(const lastweek_weekday& x, const lastweek_weekday& y) NOEXCEPT; CONSTCD11 bool operator!=(const lastweek_weekday& x, const lastweek_weekday& y) NOEXCEPT; CONSTCD11 bool operator< (const lastweek_weekday& x, const lastweek_weekday& y) NOEXCEPT; CONSTCD11 bool operator> (const lastweek_weekday& x, const lastweek_weekday& y) NOEXCEPT; CONSTCD11 bool operator<=(const lastweek_weekday& x, const lastweek_weekday& y) NOEXCEPT; CONSTCD11 bool operator>=(const lastweek_weekday& x, const lastweek_weekday& y) NOEXCEPT; template std::basic_ostream& operator<<(std::basic_ostream& os, const lastweek_weekday& md); // year_lastweek_weekday class year_lastweek_weekday { iso_week::year y_; iso_week::weekday wd_; public: CONSTCD11 year_lastweek_weekday(const iso_week::year& y, const iso_week::weekday& wd) NOEXCEPT; year_lastweek_weekday& operator+=(const years& y) NOEXCEPT; year_lastweek_weekday& operator-=(const years& y) NOEXCEPT; CONSTCD11 iso_week::year year() const NOEXCEPT; CONSTCD14 iso_week::weeknum weeknum() const NOEXCEPT; CONSTCD11 iso_week::weekday weekday() const NOEXCEPT; CONSTCD14 operator sys_days() const NOEXCEPT; CONSTCD14 explicit operator local_days() const NOEXCEPT; CONSTCD11 bool ok() const NOEXCEPT; }; CONSTCD11 bool operator==(const year_lastweek_weekday& x, const year_lastweek_weekday& y) NOEXCEPT; CONSTCD11 bool operator!=(const year_lastweek_weekday& x, const year_lastweek_weekday& y) NOEXCEPT; CONSTCD11 bool operator< (const year_lastweek_weekday& x, const year_lastweek_weekday& y) NOEXCEPT; CONSTCD11 bool operator> (const year_lastweek_weekday& x, const year_lastweek_weekday& y) NOEXCEPT; CONSTCD11 bool operator<=(const year_lastweek_weekday& x, const year_lastweek_weekday& y) NOEXCEPT; CONSTCD11 bool operator>=(const year_lastweek_weekday& x, const year_lastweek_weekday& y) NOEXCEPT; CONSTCD11 year_lastweek_weekday operator+(const year_lastweek_weekday& ywnwd, const years& y) NOEXCEPT; CONSTCD11 year_lastweek_weekday operator+(const years& y, const year_lastweek_weekday& ywnwd) NOEXCEPT; CONSTCD11 year_lastweek_weekday operator-(const year_lastweek_weekday& ywnwd, const years& y) NOEXCEPT; template std::basic_ostream& operator<<(std::basic_ostream& os, const year_lastweek_weekday& ywnwd); // class year_weeknum_weekday class year_weeknum_weekday { iso_week::year y_; iso_week::weeknum wn_; iso_week::weekday wd_; public: CONSTCD11 year_weeknum_weekday(const iso_week::year& y, const iso_week::weeknum& wn, const iso_week::weekday& wd) NOEXCEPT; CONSTCD14 year_weeknum_weekday(const year_lastweek_weekday& ylwwd) NOEXCEPT; CONSTCD14 year_weeknum_weekday(const sys_days& dp) NOEXCEPT; CONSTCD14 explicit year_weeknum_weekday(const local_days& dp) NOEXCEPT; year_weeknum_weekday& operator+=(const years& y) NOEXCEPT; year_weeknum_weekday& operator-=(const years& y) NOEXCEPT; CONSTCD11 iso_week::year year() const NOEXCEPT; CONSTCD11 iso_week::weeknum weeknum() const NOEXCEPT; CONSTCD11 iso_week::weekday weekday() const NOEXCEPT; CONSTCD14 operator sys_days() const NOEXCEPT; CONSTCD14 explicit operator local_days() const NOEXCEPT; CONSTCD14 bool ok() const NOEXCEPT; private: static CONSTCD14 year_weeknum_weekday from_days(days dp) NOEXCEPT; }; CONSTCD11 bool operator==(const year_weeknum_weekday& x, const year_weeknum_weekday& y) NOEXCEPT; CONSTCD11 bool operator!=(const year_weeknum_weekday& x, const year_weeknum_weekday& y) NOEXCEPT; CONSTCD11 bool operator< (const year_weeknum_weekday& x, const year_weeknum_weekday& y) NOEXCEPT; CONSTCD11 bool operator> (const year_weeknum_weekday& x, const year_weeknum_weekday& y) NOEXCEPT; CONSTCD11 bool operator<=(const year_weeknum_weekday& x, const year_weeknum_weekday& y) NOEXCEPT; CONSTCD11 bool operator>=(const year_weeknum_weekday& x, const year_weeknum_weekday& y) NOEXCEPT; CONSTCD11 year_weeknum_weekday operator+(const year_weeknum_weekday& ywnwd, const years& y) NOEXCEPT; CONSTCD11 year_weeknum_weekday operator+(const years& y, const year_weeknum_weekday& ywnwd) NOEXCEPT; CONSTCD11 year_weeknum_weekday operator-(const year_weeknum_weekday& ywnwd, const years& y) NOEXCEPT; template std::basic_ostream& operator<<(std::basic_ostream& os, const year_weeknum_weekday& ywnwd); //----------------+ // Implementation | //----------------+ // weekday CONSTCD11 inline unsigned char weekday::to_iso_encoding(unsigned char z) NOEXCEPT { return z != 0 ? z : (unsigned char)7; } CONSTCD11 inline unsigned weekday::from_iso_encoding(unsigned z) NOEXCEPT { return z != 7 ? z : 0u; } CONSTCD11 inline unsigned char weekday::weekday_from_days(int z) NOEXCEPT { return to_iso_encoding(static_cast(static_cast( z >= -4 ? (z+4) % 7 : (z+5) % 7 + 6))); } CONSTCD11 inline weekday::weekday(unsigned wd) NOEXCEPT : wd_(static_cast(wd)) {} CONSTCD11 inline weekday::weekday(date::weekday wd) NOEXCEPT : wd_(wd.iso_encoding()) {} CONSTCD11 inline weekday::weekday(const sys_days& dp) NOEXCEPT : wd_(weekday_from_days(dp.time_since_epoch().count())) {} CONSTCD11 inline weekday::weekday(const local_days& dp) NOEXCEPT : wd_(weekday_from_days(dp.time_since_epoch().count())) {} inline weekday& weekday::operator++() NOEXCEPT {if (++wd_ == 8) wd_ = 1; return *this;} inline weekday weekday::operator++(int) NOEXCEPT {auto tmp(*this); ++(*this); return tmp;} inline weekday& weekday::operator--() NOEXCEPT {if (wd_-- == 1) wd_ = 7; return *this;} inline weekday weekday::operator--(int) NOEXCEPT {auto tmp(*this); --(*this); return tmp;} inline weekday& weekday::operator+=(const days& d) NOEXCEPT { *this = *this + d; return *this; } inline weekday& weekday::operator-=(const days& d) NOEXCEPT { *this = *this - d; return *this; } CONSTCD11 inline weekday::operator unsigned() const NOEXCEPT { return wd_; } CONSTCD11 inline weekday::operator date::weekday() const NOEXCEPT { return date::weekday{from_iso_encoding(unsigned{wd_})}; } CONSTCD11 inline bool weekday::ok() const NOEXCEPT {return 1 <= wd_ && wd_ <= 7;} CONSTCD11 inline bool operator==(const weekday& x, const weekday& y) NOEXCEPT { return static_cast(x) == static_cast(y); } CONSTCD11 inline bool operator!=(const weekday& x, const weekday& y) NOEXCEPT { return !(x == y); } CONSTCD14 inline days operator-(const weekday& x, const weekday& y) NOEXCEPT { auto const diff = static_cast(x) - static_cast(y); return days{diff <= 6 ? diff : diff + 7}; } CONSTCD14 inline weekday operator+(const weekday& x, const days& y) NOEXCEPT { auto const wdu = static_cast(static_cast(x) - 1u) + y.count(); auto const wk = (wdu >= 0 ? wdu : wdu-6) / 7; return weekday{static_cast(wdu - wk * 7) + 1u}; } CONSTCD14 inline weekday operator+(const days& x, const weekday& y) NOEXCEPT { return y + x; } CONSTCD14 inline weekday operator-(const weekday& x, const days& y) NOEXCEPT { return x + -y; } template inline std::basic_ostream& operator<<(std::basic_ostream& os, const weekday& wd) { switch (static_cast(wd)) { case 7: os << "Sun"; break; case 1: os << "Mon"; break; case 2: os << "Tue"; break; case 3: os << "Wed"; break; case 4: os << "Thu"; break; case 5: os << "Fri"; break; case 6: os << "Sat"; break; default: os << static_cast(wd) << " is not a valid weekday"; break; } return os; } // year CONSTCD11 inline year::year(int y) NOEXCEPT : y_(static_cast(y)) {} inline year& year::operator++() NOEXCEPT {++y_; return *this;} inline year year::operator++(int) NOEXCEPT {auto tmp(*this); ++(*this); return tmp;} inline year& year::operator--() NOEXCEPT {--y_; return *this;} inline year year::operator--(int) NOEXCEPT {auto tmp(*this); --(*this); return tmp;} inline year& year::operator+=(const years& y) NOEXCEPT {*this = *this + y; return *this;} inline year& year::operator-=(const years& y) NOEXCEPT {*this = *this - y; return *this;} CONSTCD14 inline bool year::is_leap() const NOEXCEPT { const auto y = date::year{static_cast(y_)}; const auto s0 = sys_days((y-years{1})/12/date::thu[date::last]); const auto s1 = sys_days(y/12/date::thu[date::last]); return s1-s0 != days{7*52}; } CONSTCD11 inline year::operator int() const NOEXCEPT {return y_;} CONSTCD11 inline bool year::ok() const NOEXCEPT {return min() <= *this && *this <= max();} CONSTCD11 inline year year::min() NOEXCEPT { using std::chrono::seconds; using std::chrono::minutes; using std::chrono::hours; using std::chrono::duration_cast; static_assert(sizeof(seconds)*CHAR_BIT >= 41, "seconds may overflow"); static_assert(sizeof(hours)*CHAR_BIT >= 30, "hours may overflow"); return sizeof(minutes)*CHAR_BIT < 34 ? year{1970} + duration_cast(minutes::min()) : year{std::numeric_limits::min()}; } CONSTCD11 inline year year::max() NOEXCEPT { using std::chrono::seconds; using std::chrono::minutes; using std::chrono::hours; using std::chrono::duration_cast; static_assert(sizeof(seconds)*CHAR_BIT >= 41, "seconds may overflow"); static_assert(sizeof(hours)*CHAR_BIT >= 30, "hours may overflow"); return sizeof(minutes)*CHAR_BIT < 34 ? year{1969} + duration_cast(minutes::max()) : year{std::numeric_limits::max()}; } CONSTCD11 inline bool operator==(const year& x, const year& y) NOEXCEPT { return static_cast(x) == static_cast(y); } CONSTCD11 inline bool operator!=(const year& x, const year& y) NOEXCEPT { return !(x == y); } CONSTCD11 inline bool operator<(const year& x, const year& y) NOEXCEPT { return static_cast(x) < static_cast(y); } CONSTCD11 inline bool operator>(const year& x, const year& y) NOEXCEPT { return y < x; } CONSTCD11 inline bool operator<=(const year& x, const year& y) NOEXCEPT { return !(y < x); } CONSTCD11 inline bool operator>=(const year& x, const year& y) NOEXCEPT { return !(x < y); } CONSTCD11 inline years operator-(const year& x, const year& y) NOEXCEPT { return years{static_cast(x) - static_cast(y)}; } CONSTCD11 inline year operator+(const year& x, const years& y) NOEXCEPT { return year{static_cast(x) + y.count()}; } CONSTCD11 inline year operator+(const years& x, const year& y) NOEXCEPT { return y + x; } CONSTCD11 inline year operator-(const year& x, const years& y) NOEXCEPT { return year{static_cast(x) - y.count()}; } template inline std::basic_ostream& operator<<(std::basic_ostream& os, const year& y) { date::detail::save_ostream _(os); os.fill('0'); os.flags(std::ios::dec | std::ios::internal); os.width(4 + (y < year{0})); os << static_cast(y); return os; } #if !defined(_MSC_VER) || (_MSC_VER >= 1900) inline namespace literals { CONSTCD11 inline iso_week::year operator ""_y(unsigned long long y) NOEXCEPT { return iso_week::year(static_cast(y)); } CONSTCD11 inline iso_week::weeknum operator ""_w(unsigned long long wn) NOEXCEPT { return iso_week::weeknum(static_cast(wn)); } #endif // !defined(_MSC_VER) || (_MSC_VER >= 1900) CONSTDATA iso_week::last_week last{}; CONSTDATA iso_week::weekday sun{7u}; CONSTDATA iso_week::weekday mon{1u}; CONSTDATA iso_week::weekday tue{2u}; CONSTDATA iso_week::weekday wed{3u}; CONSTDATA iso_week::weekday thu{4u}; CONSTDATA iso_week::weekday fri{5u}; CONSTDATA iso_week::weekday sat{6u}; #if !defined(_MSC_VER) || (_MSC_VER >= 1900) } // inline namespace literals #endif // weeknum CONSTCD11 inline weeknum::weeknum(unsigned wn) NOEXCEPT : wn_(static_cast(wn)) {} inline weeknum& weeknum::operator++() NOEXCEPT {++wn_; return *this;} inline weeknum weeknum::operator++(int) NOEXCEPT {auto tmp(*this); ++(*this); return tmp;} inline weeknum& weeknum::operator--() NOEXCEPT {--wn_; return *this;} inline weeknum weeknum::operator--(int) NOEXCEPT {auto tmp(*this); --(*this); return tmp;} inline weeknum& weeknum::operator+=(const weeks& y) NOEXCEPT { *this = *this + y; return *this; } inline weeknum& weeknum::operator-=(const weeks& y) NOEXCEPT { *this = *this - y; return *this; } CONSTCD11 inline weeknum::operator unsigned() const NOEXCEPT {return wn_;} CONSTCD11 inline bool weeknum::ok() const NOEXCEPT {return 1 <= wn_ && wn_ <= 53;} CONSTCD11 inline bool operator==(const weeknum& x, const weeknum& y) NOEXCEPT { return static_cast(x) == static_cast(y); } CONSTCD11 inline bool operator!=(const weeknum& x, const weeknum& y) NOEXCEPT { return !(x == y); } CONSTCD11 inline bool operator<(const weeknum& x, const weeknum& y) NOEXCEPT { return static_cast(x) < static_cast(y); } CONSTCD11 inline bool operator>(const weeknum& x, const weeknum& y) NOEXCEPT { return y < x; } CONSTCD11 inline bool operator<=(const weeknum& x, const weeknum& y) NOEXCEPT { return !(y < x); } CONSTCD11 inline bool operator>=(const weeknum& x, const weeknum& y) NOEXCEPT { return !(x < y); } CONSTCD11 inline weeks operator-(const weeknum& x, const weeknum& y) NOEXCEPT { return weeks{static_cast(static_cast(x)) - static_cast(static_cast(y))}; } CONSTCD11 inline weeknum operator+(const weeknum& x, const weeks& y) NOEXCEPT { return weeknum{static_cast(x) + static_cast(y.count())}; } CONSTCD11 inline weeknum operator+(const weeks& x, const weeknum& y) NOEXCEPT { return y + x; } CONSTCD11 inline weeknum operator-(const weeknum& x, const weeks& y) NOEXCEPT { return x + -y; } template inline std::basic_ostream& operator<<(std::basic_ostream& os, const weeknum& wn) { date::detail::save_ostream _(os); os << 'W'; os.fill('0'); os.flags(std::ios::dec | std::ios::right); os.width(2); os << static_cast(wn); return os; } // year_weeknum CONSTCD11 inline year_weeknum::year_weeknum(const iso_week::year& y, const iso_week::weeknum& wn) NOEXCEPT : y_(y) , wn_(wn) {} CONSTCD11 inline year year_weeknum::year() const NOEXCEPT {return y_;} CONSTCD11 inline weeknum year_weeknum::weeknum() const NOEXCEPT {return wn_;} CONSTCD11 inline bool year_weeknum::ok() const NOEXCEPT { return y_.ok() && 1u <= static_cast(wn_) && wn_ <= (y_/last).weeknum(); } inline year_weeknum& year_weeknum::operator+=(const years& dy) NOEXCEPT { *this = *this + dy; return *this; } inline year_weeknum& year_weeknum::operator-=(const years& dy) NOEXCEPT { *this = *this - dy; return *this; } CONSTCD11 inline bool operator==(const year_weeknum& x, const year_weeknum& y) NOEXCEPT { return x.year() == y.year() && x.weeknum() == y.weeknum(); } CONSTCD11 inline bool operator!=(const year_weeknum& x, const year_weeknum& y) NOEXCEPT { return !(x == y); } CONSTCD11 inline bool operator<(const year_weeknum& x, const year_weeknum& y) NOEXCEPT { return x.year() < y.year() ? true : (x.year() > y.year() ? false : (x.weeknum() < y.weeknum())); } CONSTCD11 inline bool operator>(const year_weeknum& x, const year_weeknum& y) NOEXCEPT { return y < x; } CONSTCD11 inline bool operator<=(const year_weeknum& x, const year_weeknum& y) NOEXCEPT { return !(y < x); } CONSTCD11 inline bool operator>=(const year_weeknum& x, const year_weeknum& y) NOEXCEPT { return !(x < y); } CONSTCD11 inline year_weeknum operator+(const year_weeknum& ym, const years& dy) NOEXCEPT { return (ym.year() + dy) / ym.weeknum(); } CONSTCD11 inline year_weeknum operator+(const years& dy, const year_weeknum& ym) NOEXCEPT { return ym + dy; } CONSTCD11 inline year_weeknum operator-(const year_weeknum& ym, const years& dy) NOEXCEPT { return ym + -dy; } template inline std::basic_ostream& operator<<(std::basic_ostream& os, const year_weeknum& ywn) { return os << ywn.year() << '-' << ywn.weeknum(); } // year_lastweek CONSTCD11 inline year_lastweek::year_lastweek(const iso_week::year& y) NOEXCEPT : y_(y) {} CONSTCD11 inline year year_lastweek::year() const NOEXCEPT {return y_;} CONSTCD14 inline weeknum year_lastweek::weeknum() const NOEXCEPT { return iso_week::weeknum(y_.is_leap() ? 53u : 52u); } CONSTCD11 inline bool year_lastweek::ok() const NOEXCEPT {return y_.ok();} inline year_lastweek& year_lastweek::operator+=(const years& dy) NOEXCEPT { *this = *this + dy; return *this; } inline year_lastweek& year_lastweek::operator-=(const years& dy) NOEXCEPT { *this = *this - dy; return *this; } CONSTCD11 inline bool operator==(const year_lastweek& x, const year_lastweek& y) NOEXCEPT { return x.year() == y.year(); } CONSTCD11 inline bool operator!=(const year_lastweek& x, const year_lastweek& y) NOEXCEPT { return !(x == y); } CONSTCD11 inline bool operator<(const year_lastweek& x, const year_lastweek& y) NOEXCEPT { return x.year() < y.year(); } CONSTCD11 inline bool operator>(const year_lastweek& x, const year_lastweek& y) NOEXCEPT { return y < x; } CONSTCD11 inline bool operator<=(const year_lastweek& x, const year_lastweek& y) NOEXCEPT { return !(y < x); } CONSTCD11 inline bool operator>=(const year_lastweek& x, const year_lastweek& y) NOEXCEPT { return !(x < y); } CONSTCD11 inline year_lastweek operator+(const year_lastweek& ym, const years& dy) NOEXCEPT { return year_lastweek{ym.year() + dy}; } CONSTCD11 inline year_lastweek operator+(const years& dy, const year_lastweek& ym) NOEXCEPT { return ym + dy; } CONSTCD11 inline year_lastweek operator-(const year_lastweek& ym, const years& dy) NOEXCEPT { return ym + -dy; } template inline std::basic_ostream& operator<<(std::basic_ostream& os, const year_lastweek& ywn) { return os << ywn.year() << "-W last"; } // weeknum_weekday CONSTCD11 inline weeknum_weekday::weeknum_weekday(const iso_week::weeknum& wn, const iso_week::weekday& wd) NOEXCEPT : wn_(wn) , wd_(wd) {} CONSTCD11 inline weeknum weeknum_weekday::weeknum() const NOEXCEPT {return wn_;} CONSTCD11 inline weekday weeknum_weekday::weekday() const NOEXCEPT {return wd_;} CONSTCD14 inline bool weeknum_weekday::ok() const NOEXCEPT { return wn_.ok() && wd_.ok(); } CONSTCD11 inline bool operator==(const weeknum_weekday& x, const weeknum_weekday& y) NOEXCEPT { return x.weeknum() == y.weeknum() && x.weekday() == y.weekday(); } CONSTCD11 inline bool operator!=(const weeknum_weekday& x, const weeknum_weekday& y) NOEXCEPT { return !(x == y); } CONSTCD11 inline bool operator<(const weeknum_weekday& x, const weeknum_weekday& y) NOEXCEPT { return x.weeknum() < y.weeknum() ? true : (x.weeknum() > y.weeknum() ? false : (static_cast(x.weekday()) < static_cast(y.weekday()))); } CONSTCD11 inline bool operator>(const weeknum_weekday& x, const weeknum_weekday& y) NOEXCEPT { return y < x; } CONSTCD11 inline bool operator<=(const weeknum_weekday& x, const weeknum_weekday& y) NOEXCEPT { return !(y < x); } CONSTCD11 inline bool operator>=(const weeknum_weekday& x, const weeknum_weekday& y) NOEXCEPT { return !(x < y); } template inline std::basic_ostream& operator<<(std::basic_ostream& os, const weeknum_weekday& md) { return os << md.weeknum() << '-' << md.weekday(); } // lastweek_weekday CONSTCD11 inline lastweek_weekday::lastweek_weekday(const iso_week::weekday& wd) NOEXCEPT : wd_(wd) {} CONSTCD11 inline weekday lastweek_weekday::weekday() const NOEXCEPT {return wd_;} CONSTCD14 inline bool lastweek_weekday::ok() const NOEXCEPT { return wd_.ok(); } CONSTCD11 inline bool operator==(const lastweek_weekday& x, const lastweek_weekday& y) NOEXCEPT { return x.weekday() == y.weekday(); } CONSTCD11 inline bool operator!=(const lastweek_weekday& x, const lastweek_weekday& y) NOEXCEPT { return !(x == y); } CONSTCD11 inline bool operator<(const lastweek_weekday& x, const lastweek_weekday& y) NOEXCEPT { return static_cast(x.weekday()) < static_cast(y.weekday()); } CONSTCD11 inline bool operator>(const lastweek_weekday& x, const lastweek_weekday& y) NOEXCEPT { return y < x; } CONSTCD11 inline bool operator<=(const lastweek_weekday& x, const lastweek_weekday& y) NOEXCEPT { return !(y < x); } CONSTCD11 inline bool operator>=(const lastweek_weekday& x, const lastweek_weekday& y) NOEXCEPT { return !(x < y); } template inline std::basic_ostream& operator<<(std::basic_ostream& os, const lastweek_weekday& md) { return os << "W last-" << md.weekday(); } // year_lastweek_weekday CONSTCD11 inline year_lastweek_weekday::year_lastweek_weekday(const iso_week::year& y, const iso_week::weekday& wd) NOEXCEPT : y_(y) , wd_(wd) {} inline year_lastweek_weekday& year_lastweek_weekday::operator+=(const years& y) NOEXCEPT { *this = *this + y; return *this; } inline year_lastweek_weekday& year_lastweek_weekday::operator-=(const years& y) NOEXCEPT { *this = *this - y; return *this; } CONSTCD11 inline year year_lastweek_weekday::year() const NOEXCEPT {return y_;} CONSTCD14 inline weeknum year_lastweek_weekday::weeknum() const NOEXCEPT { return (y_ / last).weeknum(); } CONSTCD11 inline weekday year_lastweek_weekday::weekday() const NOEXCEPT {return wd_;} CONSTCD14 inline year_lastweek_weekday::operator sys_days() const NOEXCEPT { return sys_days(date::year{static_cast(y_)}/date::dec/date::thu[date::last]) + (sun - thu) - (sun - wd_); } CONSTCD14 inline year_lastweek_weekday::operator local_days() const NOEXCEPT { return local_days(date::year{static_cast(y_)}/date::dec/date::thu[date::last]) + (sun - thu) - (sun - wd_); } CONSTCD11 inline bool year_lastweek_weekday::ok() const NOEXCEPT { return y_.ok() && wd_.ok(); } CONSTCD11 inline bool operator==(const year_lastweek_weekday& x, const year_lastweek_weekday& y) NOEXCEPT { return x.year() == y.year() && x.weekday() == y.weekday(); } CONSTCD11 inline bool operator!=(const year_lastweek_weekday& x, const year_lastweek_weekday& y) NOEXCEPT { return !(x == y); } CONSTCD11 inline bool operator<(const year_lastweek_weekday& x, const year_lastweek_weekday& y) NOEXCEPT { return x.year() < y.year() ? true : (x.year() > y.year() ? false : (static_cast(x.weekday()) < static_cast(y.weekday()))); } CONSTCD11 inline bool operator>(const year_lastweek_weekday& x, const year_lastweek_weekday& y) NOEXCEPT { return y < x; } CONSTCD11 inline bool operator<=(const year_lastweek_weekday& x, const year_lastweek_weekday& y) NOEXCEPT { return !(y < x); } CONSTCD11 inline bool operator>=(const year_lastweek_weekday& x, const year_lastweek_weekday& y) NOEXCEPT { return !(x < y); } CONSTCD11 inline year_lastweek_weekday operator+(const year_lastweek_weekday& ywnwd, const years& y) NOEXCEPT { return (ywnwd.year() + y) / last / ywnwd.weekday(); } CONSTCD11 inline year_lastweek_weekday operator+(const years& y, const year_lastweek_weekday& ywnwd) NOEXCEPT { return ywnwd + y; } CONSTCD11 inline year_lastweek_weekday operator-(const year_lastweek_weekday& ywnwd, const years& y) NOEXCEPT { return ywnwd + -y; } template inline std::basic_ostream& operator<<(std::basic_ostream& os, const year_lastweek_weekday& ywnwd) { return os << ywnwd.year() << "-W last-" << ywnwd.weekday(); } // year_weeknum_weekday CONSTCD11 inline year_weeknum_weekday::year_weeknum_weekday(const iso_week::year& y, const iso_week::weeknum& wn, const iso_week::weekday& wd) NOEXCEPT : y_(y) , wn_(wn) , wd_(wd) {} CONSTCD14 inline year_weeknum_weekday::year_weeknum_weekday(const year_lastweek_weekday& ylwwd) NOEXCEPT : y_(ylwwd.year()) , wn_(ylwwd.weeknum()) , wd_(ylwwd.weekday()) {} CONSTCD14 inline year_weeknum_weekday::year_weeknum_weekday(const sys_days& dp) NOEXCEPT : year_weeknum_weekday(from_days(dp.time_since_epoch())) {} CONSTCD14 inline year_weeknum_weekday::year_weeknum_weekday(const local_days& dp) NOEXCEPT : year_weeknum_weekday(from_days(dp.time_since_epoch())) {} inline year_weeknum_weekday& year_weeknum_weekday::operator+=(const years& y) NOEXCEPT { *this = *this + y; return *this; } inline year_weeknum_weekday& year_weeknum_weekday::operator-=(const years& y) NOEXCEPT { *this = *this - y; return *this; } CONSTCD11 inline year year_weeknum_weekday::year() const NOEXCEPT {return y_;} CONSTCD11 inline weeknum year_weeknum_weekday::weeknum() const NOEXCEPT {return wn_;} CONSTCD11 inline weekday year_weeknum_weekday::weekday() const NOEXCEPT {return wd_;} CONSTCD14 inline year_weeknum_weekday::operator sys_days() const NOEXCEPT { return sys_days(date::year{static_cast(y_)-1}/date::dec/date::thu[date::last]) + (date::mon - date::thu) + weeks{static_cast(wn_)-1} + (wd_ - mon); } CONSTCD14 inline year_weeknum_weekday::operator local_days() const NOEXCEPT { return local_days(date::year{static_cast(y_)-1}/date::dec/date::thu[date::last]) + (date::mon - date::thu) + weeks{static_cast(wn_)-1} + (wd_ - mon); } CONSTCD14 inline bool year_weeknum_weekday::ok() const NOEXCEPT { return y_.ok() && wd_.ok() && iso_week::weeknum{1u} <= wn_ && wn_ <= year_lastweek{y_}.weeknum(); } CONSTCD14 inline year_weeknum_weekday year_weeknum_weekday::from_days(days d) NOEXCEPT { const auto dp = sys_days{d}; const auto wd = iso_week::weekday{dp}; auto y = date::year_month_day{dp + days{3}}.year(); auto start = sys_days((y - date::years{1})/date::dec/date::thu[date::last]) + (mon-thu); if (dp < start) { --y; start = sys_days((y - date::years{1})/date::dec/date::thu[date::last]) + (mon-thu); } const auto wn = iso_week::weeknum( static_cast(date::trunc(dp - start).count() + 1)); return {iso_week::year(static_cast(y)), wn, wd}; } CONSTCD11 inline bool operator==(const year_weeknum_weekday& x, const year_weeknum_weekday& y) NOEXCEPT { return x.year() == y.year() && x.weeknum() == y.weeknum() && x.weekday() == y.weekday(); } CONSTCD11 inline bool operator!=(const year_weeknum_weekday& x, const year_weeknum_weekday& y) NOEXCEPT { return !(x == y); } CONSTCD11 inline bool operator<(const year_weeknum_weekday& x, const year_weeknum_weekday& y) NOEXCEPT { return x.year() < y.year() ? true : (x.year() > y.year() ? false : (x.weeknum() < y.weeknum() ? true : (x.weeknum() > y.weeknum() ? false : (static_cast(x.weekday()) < static_cast(y.weekday()))))); } CONSTCD11 inline bool operator>(const year_weeknum_weekday& x, const year_weeknum_weekday& y) NOEXCEPT { return y < x; } CONSTCD11 inline bool operator<=(const year_weeknum_weekday& x, const year_weeknum_weekday& y) NOEXCEPT { return !(y < x); } CONSTCD11 inline bool operator>=(const year_weeknum_weekday& x, const year_weeknum_weekday& y) NOEXCEPT { return !(x < y); } CONSTCD11 inline year_weeknum_weekday operator+(const year_weeknum_weekday& ywnwd, const years& y) NOEXCEPT { return (ywnwd.year() + y) / ywnwd.weeknum() / ywnwd.weekday(); } CONSTCD11 inline year_weeknum_weekday operator+(const years& y, const year_weeknum_weekday& ywnwd) NOEXCEPT { return ywnwd + y; } CONSTCD11 inline year_weeknum_weekday operator-(const year_weeknum_weekday& ywnwd, const years& y) NOEXCEPT { return ywnwd + -y; } template inline std::basic_ostream& operator<<(std::basic_ostream& os, const year_weeknum_weekday& ywnwd) { return os << ywnwd.year() << '-' << ywnwd.weeknum() << '-' << ywnwd.weekday(); } // date composition operators CONSTCD11 inline year_weeknum operator/(const year& y, const weeknum& wn) NOEXCEPT { return {y, wn}; } CONSTCD11 inline year_weeknum operator/(const year& y, int wn) NOEXCEPT { return y/weeknum(static_cast(wn)); } CONSTCD11 inline year_lastweek operator/(const year& y, last_week) NOEXCEPT { return year_lastweek{y}; } CONSTCD11 inline weeknum_weekday operator/(const weeknum& wn, const weekday& wd) NOEXCEPT { return {wn, wd}; } CONSTCD11 inline weeknum_weekday operator/(const weeknum& wn, int wd) NOEXCEPT { return wn/weekday{static_cast(wd)}; } CONSTCD11 inline weeknum_weekday operator/(const weekday& wd, const weeknum& wn) NOEXCEPT { return wn/wd; } CONSTCD11 inline weeknum_weekday operator/(const weekday& wd, int wn) NOEXCEPT { return weeknum{static_cast(wn)}/wd; } CONSTCD11 inline lastweek_weekday operator/(const last_week&, const weekday& wd) NOEXCEPT { return lastweek_weekday{wd}; } CONSTCD11 inline lastweek_weekday operator/(const last_week& wn, int wd) NOEXCEPT { return wn / weekday{static_cast(wd)}; } CONSTCD11 inline lastweek_weekday operator/(const weekday& wd, const last_week& wn) NOEXCEPT { return wn / wd; } CONSTCD11 inline year_weeknum_weekday operator/(const year_weeknum& ywn, const weekday& wd) NOEXCEPT { return {ywn.year(), ywn.weeknum(), wd}; } CONSTCD11 inline year_weeknum_weekday operator/(const year_weeknum& ywn, int wd) NOEXCEPT { return ywn / weekday(static_cast(wd)); } CONSTCD11 inline year_weeknum_weekday operator/(const weeknum_weekday& wnwd, const year& y) NOEXCEPT { return {y, wnwd.weeknum(), wnwd.weekday()}; } CONSTCD11 inline year_weeknum_weekday operator/(const weeknum_weekday& wnwd, int y) NOEXCEPT { return wnwd / year{y}; } CONSTCD11 inline year_lastweek_weekday operator/(const year_lastweek& ylw, const weekday& wd) NOEXCEPT { return {ylw.year(), wd}; } CONSTCD11 inline year_lastweek_weekday operator/(const year_lastweek& ylw, int wd) NOEXCEPT { return ylw / weekday(static_cast(wd)); } CONSTCD11 inline year_lastweek_weekday operator/(const lastweek_weekday& lwwd, const year& y) NOEXCEPT { return {y, lwwd.weekday()}; } CONSTCD11 inline year_lastweek_weekday operator/(const lastweek_weekday& lwwd, int y) NOEXCEPT { return lwwd / year{y}; } } // namespace iso_week #endif // ISO_WEEK_H pr0m1th3as-datatypes-9c9a8d3/src/date/julian.h000066400000000000000000002144541522766574100212260ustar00rootroot00000000000000#ifndef JULIAN_H #define JULIAN_H // The MIT License (MIT) // // Copyright (c) 2016 Howard Hinnant // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. // // Our apologies. When the previous paragraph was written, lowercase had not yet // been invented (that would involve another several millennia of evolution). // We did not mean to shout. #include "date.h" namespace julian { // durations using days = date::days; using weeks = date::weeks; using years = std::chrono::duration , days::period>>; using months = std::chrono::duration >>; // time_point using sys_days = date::sys_days; using local_days = date::local_days; // types struct last_spec { explicit last_spec() = default; }; class day; class month; class year; class weekday; class weekday_indexed; class weekday_last; class month_day; class month_day_last; class month_weekday; class month_weekday_last; class year_month; class year_month_day; class year_month_day_last; class year_month_weekday; class year_month_weekday_last; // date composition operators CONSTCD11 year_month operator/(const year& y, const month& m) NOEXCEPT; CONSTCD11 year_month operator/(const year& y, int m) NOEXCEPT; CONSTCD11 month_day operator/(const day& d, const month& m) NOEXCEPT; CONSTCD11 month_day operator/(const day& d, int m) NOEXCEPT; CONSTCD11 month_day operator/(const month& m, const day& d) NOEXCEPT; CONSTCD11 month_day operator/(const month& m, int d) NOEXCEPT; CONSTCD11 month_day operator/(int m, const day& d) NOEXCEPT; CONSTCD11 month_day_last operator/(const month& m, last_spec) NOEXCEPT; CONSTCD11 month_day_last operator/(int m, last_spec) NOEXCEPT; CONSTCD11 month_day_last operator/(last_spec, const month& m) NOEXCEPT; CONSTCD11 month_day_last operator/(last_spec, int m) NOEXCEPT; CONSTCD11 month_weekday operator/(const month& m, const weekday_indexed& wdi) NOEXCEPT; CONSTCD11 month_weekday operator/(int m, const weekday_indexed& wdi) NOEXCEPT; CONSTCD11 month_weekday operator/(const weekday_indexed& wdi, const month& m) NOEXCEPT; CONSTCD11 month_weekday operator/(const weekday_indexed& wdi, int m) NOEXCEPT; CONSTCD11 month_weekday_last operator/(const month& m, const weekday_last& wdl) NOEXCEPT; CONSTCD11 month_weekday_last operator/(int m, const weekday_last& wdl) NOEXCEPT; CONSTCD11 month_weekday_last operator/(const weekday_last& wdl, const month& m) NOEXCEPT; CONSTCD11 month_weekday_last operator/(const weekday_last& wdl, int m) NOEXCEPT; CONSTCD11 year_month_day operator/(const year_month& ym, const day& d) NOEXCEPT; CONSTCD11 year_month_day operator/(const year_month& ym, int d) NOEXCEPT; CONSTCD11 year_month_day operator/(const year& y, const month_day& md) NOEXCEPT; CONSTCD11 year_month_day operator/(int y, const month_day& md) NOEXCEPT; CONSTCD11 year_month_day operator/(const month_day& md, const year& y) NOEXCEPT; CONSTCD11 year_month_day operator/(const month_day& md, int y) NOEXCEPT; CONSTCD11 year_month_day_last operator/(const year_month& ym, last_spec) NOEXCEPT; CONSTCD11 year_month_day_last operator/(const year& y, const month_day_last& mdl) NOEXCEPT; CONSTCD11 year_month_day_last operator/(int y, const month_day_last& mdl) NOEXCEPT; CONSTCD11 year_month_day_last operator/(const month_day_last& mdl, const year& y) NOEXCEPT; CONSTCD11 year_month_day_last operator/(const month_day_last& mdl, int y) NOEXCEPT; CONSTCD11 year_month_weekday operator/(const year_month& ym, const weekday_indexed& wdi) NOEXCEPT; CONSTCD11 year_month_weekday operator/(const year& y, const month_weekday& mwd) NOEXCEPT; CONSTCD11 year_month_weekday operator/(int y, const month_weekday& mwd) NOEXCEPT; CONSTCD11 year_month_weekday operator/(const month_weekday& mwd, const year& y) NOEXCEPT; CONSTCD11 year_month_weekday operator/(const month_weekday& mwd, int y) NOEXCEPT; CONSTCD11 year_month_weekday_last operator/(const year_month& ym, const weekday_last& wdl) NOEXCEPT; CONSTCD11 year_month_weekday_last operator/(const year& y, const month_weekday_last& mwdl) NOEXCEPT; CONSTCD11 year_month_weekday_last operator/(int y, const month_weekday_last& mwdl) NOEXCEPT; CONSTCD11 year_month_weekday_last operator/(const month_weekday_last& mwdl, const year& y) NOEXCEPT; CONSTCD11 year_month_weekday_last operator/(const month_weekday_last& mwdl, int y) NOEXCEPT; // Detailed interface // day class day { unsigned char d_; public: explicit CONSTCD11 day(unsigned d) NOEXCEPT; CONSTCD14 day& operator++() NOEXCEPT; CONSTCD14 day operator++(int) NOEXCEPT; CONSTCD14 day& operator--() NOEXCEPT; CONSTCD14 day operator--(int) NOEXCEPT; CONSTCD14 day& operator+=(const days& d) NOEXCEPT; CONSTCD14 day& operator-=(const days& d) NOEXCEPT; CONSTCD11 explicit operator unsigned() const NOEXCEPT; CONSTCD11 bool ok() const NOEXCEPT; }; CONSTCD11 bool operator==(const day& x, const day& y) NOEXCEPT; CONSTCD11 bool operator!=(const day& x, const day& y) NOEXCEPT; CONSTCD11 bool operator< (const day& x, const day& y) NOEXCEPT; CONSTCD11 bool operator> (const day& x, const day& y) NOEXCEPT; CONSTCD11 bool operator<=(const day& x, const day& y) NOEXCEPT; CONSTCD11 bool operator>=(const day& x, const day& y) NOEXCEPT; CONSTCD11 day operator+(const day& x, const days& y) NOEXCEPT; CONSTCD11 day operator+(const days& x, const day& y) NOEXCEPT; CONSTCD11 day operator-(const day& x, const days& y) NOEXCEPT; CONSTCD11 days operator-(const day& x, const day& y) NOEXCEPT; template std::basic_ostream& operator<<(std::basic_ostream& os, const day& d); // month class month { unsigned char m_; public: explicit CONSTCD11 month(unsigned m) NOEXCEPT; CONSTCD14 month& operator++() NOEXCEPT; CONSTCD14 month operator++(int) NOEXCEPT; CONSTCD14 month& operator--() NOEXCEPT; CONSTCD14 month operator--(int) NOEXCEPT; CONSTCD14 month& operator+=(const months& m) NOEXCEPT; CONSTCD14 month& operator-=(const months& m) NOEXCEPT; CONSTCD11 explicit operator unsigned() const NOEXCEPT; CONSTCD11 bool ok() const NOEXCEPT; }; CONSTCD11 bool operator==(const month& x, const month& y) NOEXCEPT; CONSTCD11 bool operator!=(const month& x, const month& y) NOEXCEPT; CONSTCD11 bool operator< (const month& x, const month& y) NOEXCEPT; CONSTCD11 bool operator> (const month& x, const month& y) NOEXCEPT; CONSTCD11 bool operator<=(const month& x, const month& y) NOEXCEPT; CONSTCD11 bool operator>=(const month& x, const month& y) NOEXCEPT; CONSTCD14 month operator+(const month& x, const months& y) NOEXCEPT; CONSTCD14 month operator+(const months& x, const month& y) NOEXCEPT; CONSTCD14 month operator-(const month& x, const months& y) NOEXCEPT; CONSTCD14 months operator-(const month& x, const month& y) NOEXCEPT; template std::basic_ostream& operator<<(std::basic_ostream& os, const month& m); // year class year { short y_; public: explicit CONSTCD11 year(int y) NOEXCEPT; CONSTCD14 year& operator++() NOEXCEPT; CONSTCD14 year operator++(int) NOEXCEPT; CONSTCD14 year& operator--() NOEXCEPT; CONSTCD14 year operator--(int) NOEXCEPT; CONSTCD14 year& operator+=(const years& y) NOEXCEPT; CONSTCD14 year& operator-=(const years& y) NOEXCEPT; CONSTCD11 bool is_leap() const NOEXCEPT; CONSTCD11 explicit operator int() const NOEXCEPT; CONSTCD11 bool ok() const NOEXCEPT; static CONSTCD11 year min() NOEXCEPT; static CONSTCD11 year max() NOEXCEPT; }; CONSTCD11 bool operator==(const year& x, const year& y) NOEXCEPT; CONSTCD11 bool operator!=(const year& x, const year& y) NOEXCEPT; CONSTCD11 bool operator< (const year& x, const year& y) NOEXCEPT; CONSTCD11 bool operator> (const year& x, const year& y) NOEXCEPT; CONSTCD11 bool operator<=(const year& x, const year& y) NOEXCEPT; CONSTCD11 bool operator>=(const year& x, const year& y) NOEXCEPT; CONSTCD11 year operator+(const year& x, const years& y) NOEXCEPT; CONSTCD11 year operator+(const years& x, const year& y) NOEXCEPT; CONSTCD11 year operator-(const year& x, const years& y) NOEXCEPT; CONSTCD11 years operator-(const year& x, const year& y) NOEXCEPT; template std::basic_ostream& operator<<(std::basic_ostream& os, const year& y); // weekday class weekday { unsigned char wd_; public: explicit CONSTCD11 weekday(unsigned wd) NOEXCEPT; explicit weekday(int) = delete; CONSTCD11 weekday(const sys_days& dp) NOEXCEPT; CONSTCD11 explicit weekday(const local_days& dp) NOEXCEPT; CONSTCD14 weekday& operator++() NOEXCEPT; CONSTCD14 weekday operator++(int) NOEXCEPT; CONSTCD14 weekday& operator--() NOEXCEPT; CONSTCD14 weekday operator--(int) NOEXCEPT; CONSTCD14 weekday& operator+=(const days& d) NOEXCEPT; CONSTCD14 weekday& operator-=(const days& d) NOEXCEPT; CONSTCD11 explicit operator unsigned() const NOEXCEPT; CONSTCD11 bool ok() const NOEXCEPT; CONSTCD11 weekday_indexed operator[](unsigned index) const NOEXCEPT; CONSTCD11 weekday_last operator[](last_spec) const NOEXCEPT; private: static CONSTCD11 unsigned char weekday_from_days(int z) NOEXCEPT; }; CONSTCD11 bool operator==(const weekday& x, const weekday& y) NOEXCEPT; CONSTCD11 bool operator!=(const weekday& x, const weekday& y) NOEXCEPT; CONSTCD14 weekday operator+(const weekday& x, const days& y) NOEXCEPT; CONSTCD14 weekday operator+(const days& x, const weekday& y) NOEXCEPT; CONSTCD14 weekday operator-(const weekday& x, const days& y) NOEXCEPT; CONSTCD14 days operator-(const weekday& x, const weekday& y) NOEXCEPT; template std::basic_ostream& operator<<(std::basic_ostream& os, const weekday& wd); // weekday_indexed class weekday_indexed { unsigned char wd_ : 4; unsigned char index_ : 4; public: CONSTCD11 weekday_indexed(const julian::weekday& wd, unsigned index) NOEXCEPT; CONSTCD11 julian::weekday weekday() const NOEXCEPT; CONSTCD11 unsigned index() const NOEXCEPT; CONSTCD11 bool ok() const NOEXCEPT; }; CONSTCD11 bool operator==(const weekday_indexed& x, const weekday_indexed& y) NOEXCEPT; CONSTCD11 bool operator!=(const weekday_indexed& x, const weekday_indexed& y) NOEXCEPT; template std::basic_ostream& operator<<(std::basic_ostream& os, const weekday_indexed& wdi); // weekday_last class weekday_last { julian::weekday wd_; public: explicit CONSTCD11 weekday_last(const julian::weekday& wd) NOEXCEPT; CONSTCD11 julian::weekday weekday() const NOEXCEPT; CONSTCD11 bool ok() const NOEXCEPT; }; CONSTCD11 bool operator==(const weekday_last& x, const weekday_last& y) NOEXCEPT; CONSTCD11 bool operator!=(const weekday_last& x, const weekday_last& y) NOEXCEPT; template std::basic_ostream& operator<<(std::basic_ostream& os, const weekday_last& wdl); // year_month class year_month { julian::year y_; julian::month m_; public: CONSTCD11 year_month(const julian::year& y, const julian::month& m) NOEXCEPT; CONSTCD11 julian::year year() const NOEXCEPT; CONSTCD11 julian::month month() const NOEXCEPT; CONSTCD14 year_month& operator+=(const months& dm) NOEXCEPT; CONSTCD14 year_month& operator-=(const months& dm) NOEXCEPT; CONSTCD14 year_month& operator+=(const years& dy) NOEXCEPT; CONSTCD14 year_month& operator-=(const years& dy) NOEXCEPT; CONSTCD11 bool ok() const NOEXCEPT; }; CONSTCD11 bool operator==(const year_month& x, const year_month& y) NOEXCEPT; CONSTCD11 bool operator!=(const year_month& x, const year_month& y) NOEXCEPT; CONSTCD11 bool operator< (const year_month& x, const year_month& y) NOEXCEPT; CONSTCD11 bool operator> (const year_month& x, const year_month& y) NOEXCEPT; CONSTCD11 bool operator<=(const year_month& x, const year_month& y) NOEXCEPT; CONSTCD11 bool operator>=(const year_month& x, const year_month& y) NOEXCEPT; CONSTCD14 year_month operator+(const year_month& ym, const months& dm) NOEXCEPT; CONSTCD14 year_month operator+(const months& dm, const year_month& ym) NOEXCEPT; CONSTCD14 year_month operator-(const year_month& ym, const months& dm) NOEXCEPT; CONSTCD11 months operator-(const year_month& x, const year_month& y) NOEXCEPT; CONSTCD11 year_month operator+(const year_month& ym, const years& dy) NOEXCEPT; CONSTCD11 year_month operator+(const years& dy, const year_month& ym) NOEXCEPT; CONSTCD11 year_month operator-(const year_month& ym, const years& dy) NOEXCEPT; template std::basic_ostream& operator<<(std::basic_ostream& os, const year_month& ym); // month_day class month_day { julian::month m_; julian::day d_; public: CONSTCD11 month_day(const julian::month& m, const julian::day& d) NOEXCEPT; CONSTCD11 julian::month month() const NOEXCEPT; CONSTCD11 julian::day day() const NOEXCEPT; CONSTCD14 bool ok() const NOEXCEPT; }; CONSTCD11 bool operator==(const month_day& x, const month_day& y) NOEXCEPT; CONSTCD11 bool operator!=(const month_day& x, const month_day& y) NOEXCEPT; CONSTCD11 bool operator< (const month_day& x, const month_day& y) NOEXCEPT; CONSTCD11 bool operator> (const month_day& x, const month_day& y) NOEXCEPT; CONSTCD11 bool operator<=(const month_day& x, const month_day& y) NOEXCEPT; CONSTCD11 bool operator>=(const month_day& x, const month_day& y) NOEXCEPT; template std::basic_ostream& operator<<(std::basic_ostream& os, const month_day& md); // month_day_last class month_day_last { julian::month m_; public: CONSTCD11 explicit month_day_last(const julian::month& m) NOEXCEPT; CONSTCD11 julian::month month() const NOEXCEPT; CONSTCD11 bool ok() const NOEXCEPT; }; CONSTCD11 bool operator==(const month_day_last& x, const month_day_last& y) NOEXCEPT; CONSTCD11 bool operator!=(const month_day_last& x, const month_day_last& y) NOEXCEPT; CONSTCD11 bool operator< (const month_day_last& x, const month_day_last& y) NOEXCEPT; CONSTCD11 bool operator> (const month_day_last& x, const month_day_last& y) NOEXCEPT; CONSTCD11 bool operator<=(const month_day_last& x, const month_day_last& y) NOEXCEPT; CONSTCD11 bool operator>=(const month_day_last& x, const month_day_last& y) NOEXCEPT; template std::basic_ostream& operator<<(std::basic_ostream& os, const month_day_last& mdl); // month_weekday class month_weekday { julian::month m_; julian::weekday_indexed wdi_; public: CONSTCD11 month_weekday(const julian::month& m, const julian::weekday_indexed& wdi) NOEXCEPT; CONSTCD11 julian::month month() const NOEXCEPT; CONSTCD11 julian::weekday_indexed weekday_indexed() const NOEXCEPT; CONSTCD11 bool ok() const NOEXCEPT; }; CONSTCD11 bool operator==(const month_weekday& x, const month_weekday& y) NOEXCEPT; CONSTCD11 bool operator!=(const month_weekday& x, const month_weekday& y) NOEXCEPT; template std::basic_ostream& operator<<(std::basic_ostream& os, const month_weekday& mwd); // month_weekday_last class month_weekday_last { julian::month m_; julian::weekday_last wdl_; public: CONSTCD11 month_weekday_last(const julian::month& m, const julian::weekday_last& wd) NOEXCEPT; CONSTCD11 julian::month month() const NOEXCEPT; CONSTCD11 julian::weekday_last weekday_last() const NOEXCEPT; CONSTCD11 bool ok() const NOEXCEPT; }; CONSTCD11 bool operator==(const month_weekday_last& x, const month_weekday_last& y) NOEXCEPT; CONSTCD11 bool operator!=(const month_weekday_last& x, const month_weekday_last& y) NOEXCEPT; template std::basic_ostream& operator<<(std::basic_ostream& os, const month_weekday_last& mwdl); // class year_month_day class year_month_day { julian::year y_; julian::month m_; julian::day d_; public: CONSTCD11 year_month_day(const julian::year& y, const julian::month& m, const julian::day& d) NOEXCEPT; CONSTCD14 year_month_day(const year_month_day_last& ymdl) NOEXCEPT; CONSTCD14 year_month_day(sys_days dp) NOEXCEPT; CONSTCD14 explicit year_month_day(local_days dp) NOEXCEPT; CONSTCD14 year_month_day& operator+=(const months& m) NOEXCEPT; CONSTCD14 year_month_day& operator-=(const months& m) NOEXCEPT; CONSTCD14 year_month_day& operator+=(const years& y) NOEXCEPT; CONSTCD14 year_month_day& operator-=(const years& y) NOEXCEPT; CONSTCD11 julian::year year() const NOEXCEPT; CONSTCD11 julian::month month() const NOEXCEPT; CONSTCD11 julian::day day() const NOEXCEPT; CONSTCD14 operator sys_days() const NOEXCEPT; CONSTCD14 explicit operator local_days() const NOEXCEPT; CONSTCD14 bool ok() const NOEXCEPT; private: static CONSTCD14 year_month_day from_days(days dp) NOEXCEPT; CONSTCD14 days to_days() const NOEXCEPT; }; CONSTCD11 bool operator==(const year_month_day& x, const year_month_day& y) NOEXCEPT; CONSTCD11 bool operator!=(const year_month_day& x, const year_month_day& y) NOEXCEPT; CONSTCD11 bool operator< (const year_month_day& x, const year_month_day& y) NOEXCEPT; CONSTCD11 bool operator> (const year_month_day& x, const year_month_day& y) NOEXCEPT; CONSTCD11 bool operator<=(const year_month_day& x, const year_month_day& y) NOEXCEPT; CONSTCD11 bool operator>=(const year_month_day& x, const year_month_day& y) NOEXCEPT; CONSTCD14 year_month_day operator+(const year_month_day& ymd, const months& dm) NOEXCEPT; CONSTCD14 year_month_day operator+(const months& dm, const year_month_day& ymd) NOEXCEPT; CONSTCD14 year_month_day operator-(const year_month_day& ymd, const months& dm) NOEXCEPT; CONSTCD11 year_month_day operator+(const year_month_day& ymd, const years& dy) NOEXCEPT; CONSTCD11 year_month_day operator+(const years& dy, const year_month_day& ymd) NOEXCEPT; CONSTCD11 year_month_day operator-(const year_month_day& ymd, const years& dy) NOEXCEPT; template std::basic_ostream& operator<<(std::basic_ostream& os, const year_month_day& ymd); // year_month_day_last class year_month_day_last { julian::year y_; julian::month_day_last mdl_; public: CONSTCD11 year_month_day_last(const julian::year& y, const julian::month_day_last& mdl) NOEXCEPT; CONSTCD14 year_month_day_last& operator+=(const months& m) NOEXCEPT; CONSTCD14 year_month_day_last& operator-=(const months& m) NOEXCEPT; CONSTCD14 year_month_day_last& operator+=(const years& y) NOEXCEPT; CONSTCD14 year_month_day_last& operator-=(const years& y) NOEXCEPT; CONSTCD11 julian::year year() const NOEXCEPT; CONSTCD11 julian::month month() const NOEXCEPT; CONSTCD11 julian::month_day_last month_day_last() const NOEXCEPT; CONSTCD14 julian::day day() const NOEXCEPT; CONSTCD14 operator sys_days() const NOEXCEPT; CONSTCD14 explicit operator local_days() const NOEXCEPT; CONSTCD11 bool ok() const NOEXCEPT; }; CONSTCD11 bool operator==(const year_month_day_last& x, const year_month_day_last& y) NOEXCEPT; CONSTCD11 bool operator!=(const year_month_day_last& x, const year_month_day_last& y) NOEXCEPT; CONSTCD11 bool operator< (const year_month_day_last& x, const year_month_day_last& y) NOEXCEPT; CONSTCD11 bool operator> (const year_month_day_last& x, const year_month_day_last& y) NOEXCEPT; CONSTCD11 bool operator<=(const year_month_day_last& x, const year_month_day_last& y) NOEXCEPT; CONSTCD11 bool operator>=(const year_month_day_last& x, const year_month_day_last& y) NOEXCEPT; CONSTCD14 year_month_day_last operator+(const year_month_day_last& ymdl, const months& dm) NOEXCEPT; CONSTCD14 year_month_day_last operator+(const months& dm, const year_month_day_last& ymdl) NOEXCEPT; CONSTCD11 year_month_day_last operator+(const year_month_day_last& ymdl, const years& dy) NOEXCEPT; CONSTCD11 year_month_day_last operator+(const years& dy, const year_month_day_last& ymdl) NOEXCEPT; CONSTCD14 year_month_day_last operator-(const year_month_day_last& ymdl, const months& dm) NOEXCEPT; CONSTCD11 year_month_day_last operator-(const year_month_day_last& ymdl, const years& dy) NOEXCEPT; template std::basic_ostream& operator<<(std::basic_ostream& os, const year_month_day_last& ymdl); // year_month_weekday class year_month_weekday { julian::year y_; julian::month m_; julian::weekday_indexed wdi_; public: CONSTCD11 year_month_weekday(const julian::year& y, const julian::month& m, const julian::weekday_indexed& wdi) NOEXCEPT; CONSTCD14 year_month_weekday(const sys_days& dp) NOEXCEPT; CONSTCD14 explicit year_month_weekday(const local_days& dp) NOEXCEPT; CONSTCD14 year_month_weekday& operator+=(const months& m) NOEXCEPT; CONSTCD14 year_month_weekday& operator-=(const months& m) NOEXCEPT; CONSTCD14 year_month_weekday& operator+=(const years& y) NOEXCEPT; CONSTCD14 year_month_weekday& operator-=(const years& y) NOEXCEPT; CONSTCD11 julian::year year() const NOEXCEPT; CONSTCD11 julian::month month() const NOEXCEPT; CONSTCD11 julian::weekday weekday() const NOEXCEPT; CONSTCD11 unsigned index() const NOEXCEPT; CONSTCD11 julian::weekday_indexed weekday_indexed() const NOEXCEPT; CONSTCD14 operator sys_days() const NOEXCEPT; CONSTCD14 explicit operator local_days() const NOEXCEPT; CONSTCD14 bool ok() const NOEXCEPT; private: static CONSTCD14 year_month_weekday from_days(days dp) NOEXCEPT; CONSTCD14 days to_days() const NOEXCEPT; }; CONSTCD11 bool operator==(const year_month_weekday& x, const year_month_weekday& y) NOEXCEPT; CONSTCD11 bool operator!=(const year_month_weekday& x, const year_month_weekday& y) NOEXCEPT; CONSTCD14 year_month_weekday operator+(const year_month_weekday& ymwd, const months& dm) NOEXCEPT; CONSTCD14 year_month_weekday operator+(const months& dm, const year_month_weekday& ymwd) NOEXCEPT; CONSTCD11 year_month_weekday operator+(const year_month_weekday& ymwd, const years& dy) NOEXCEPT; CONSTCD11 year_month_weekday operator+(const years& dy, const year_month_weekday& ymwd) NOEXCEPT; CONSTCD14 year_month_weekday operator-(const year_month_weekday& ymwd, const months& dm) NOEXCEPT; CONSTCD11 year_month_weekday operator-(const year_month_weekday& ymwd, const years& dy) NOEXCEPT; template std::basic_ostream& operator<<(std::basic_ostream& os, const year_month_weekday& ymwdi); // year_month_weekday_last class year_month_weekday_last { julian::year y_; julian::month m_; julian::weekday_last wdl_; public: CONSTCD11 year_month_weekday_last(const julian::year& y, const julian::month& m, const julian::weekday_last& wdl) NOEXCEPT; CONSTCD14 year_month_weekday_last& operator+=(const months& m) NOEXCEPT; CONSTCD14 year_month_weekday_last& operator-=(const months& m) NOEXCEPT; CONSTCD14 year_month_weekday_last& operator+=(const years& y) NOEXCEPT; CONSTCD14 year_month_weekday_last& operator-=(const years& y) NOEXCEPT; CONSTCD11 julian::year year() const NOEXCEPT; CONSTCD11 julian::month month() const NOEXCEPT; CONSTCD11 julian::weekday weekday() const NOEXCEPT; CONSTCD11 julian::weekday_last weekday_last() const NOEXCEPT; CONSTCD14 operator sys_days() const NOEXCEPT; CONSTCD14 explicit operator local_days() const NOEXCEPT; CONSTCD11 bool ok() const NOEXCEPT; private: CONSTCD14 days to_days() const NOEXCEPT; }; CONSTCD11 bool operator==(const year_month_weekday_last& x, const year_month_weekday_last& y) NOEXCEPT; CONSTCD11 bool operator!=(const year_month_weekday_last& x, const year_month_weekday_last& y) NOEXCEPT; CONSTCD14 year_month_weekday_last operator+(const year_month_weekday_last& ymwdl, const months& dm) NOEXCEPT; CONSTCD14 year_month_weekday_last operator+(const months& dm, const year_month_weekday_last& ymwdl) NOEXCEPT; CONSTCD11 year_month_weekday_last operator+(const year_month_weekday_last& ymwdl, const years& dy) NOEXCEPT; CONSTCD11 year_month_weekday_last operator+(const years& dy, const year_month_weekday_last& ymwdl) NOEXCEPT; CONSTCD14 year_month_weekday_last operator-(const year_month_weekday_last& ymwdl, const months& dm) NOEXCEPT; CONSTCD11 year_month_weekday_last operator-(const year_month_weekday_last& ymwdl, const years& dy) NOEXCEPT; template std::basic_ostream& operator<<(std::basic_ostream& os, const year_month_weekday_last& ymwdl); #if !defined(_MSC_VER) || (_MSC_VER >= 1900) inline namespace literals { CONSTCD11 julian::day operator ""_d(unsigned long long d) NOEXCEPT; CONSTCD11 julian::year operator ""_y(unsigned long long y) NOEXCEPT; // CONSTDATA julian::month jan{1}; // CONSTDATA julian::month feb{2}; // CONSTDATA julian::month mar{3}; // CONSTDATA julian::month apr{4}; // CONSTDATA julian::month may{5}; // CONSTDATA julian::month jun{6}; // CONSTDATA julian::month jul{7}; // CONSTDATA julian::month aug{8}; // CONSTDATA julian::month sep{9}; // CONSTDATA julian::month oct{10}; // CONSTDATA julian::month nov{11}; // CONSTDATA julian::month dec{12}; } // inline namespace literals #endif // !defined(_MSC_VER) || (_MSC_VER >= 1900) //----------------+ // Implementation | //----------------+ // day CONSTCD11 inline day::day(unsigned d) NOEXCEPT : d_(static_cast(d)) {} CONSTCD14 inline day& day::operator++() NOEXCEPT {++d_; return *this;} CONSTCD14 inline day day::operator++(int) NOEXCEPT {auto tmp(*this); ++(*this); return tmp;} CONSTCD14 inline day& day::operator--() NOEXCEPT {--d_; return *this;} CONSTCD14 inline day day::operator--(int) NOEXCEPT {auto tmp(*this); --(*this); return tmp;} CONSTCD14 inline day& day::operator+=(const days& d) NOEXCEPT {*this = *this + d; return *this;} CONSTCD14 inline day& day::operator-=(const days& d) NOEXCEPT {*this = *this - d; return *this;} CONSTCD11 inline day::operator unsigned() const NOEXCEPT {return d_;} CONSTCD11 inline bool day::ok() const NOEXCEPT {return 1 <= d_ && d_ <= 31;} CONSTCD11 inline bool operator==(const day& x, const day& y) NOEXCEPT { return static_cast(x) == static_cast(y); } CONSTCD11 inline bool operator!=(const day& x, const day& y) NOEXCEPT { return !(x == y); } CONSTCD11 inline bool operator<(const day& x, const day& y) NOEXCEPT { return static_cast(x) < static_cast(y); } CONSTCD11 inline bool operator>(const day& x, const day& y) NOEXCEPT { return y < x; } CONSTCD11 inline bool operator<=(const day& x, const day& y) NOEXCEPT { return !(y < x); } CONSTCD11 inline bool operator>=(const day& x, const day& y) NOEXCEPT { return !(x < y); } CONSTCD11 inline days operator-(const day& x, const day& y) NOEXCEPT { return days{static_cast(static_cast(x) - static_cast(y))}; } CONSTCD11 inline day operator+(const day& x, const days& y) NOEXCEPT { return day{static_cast(x) + static_cast(y.count())}; } CONSTCD11 inline day operator+(const days& x, const day& y) NOEXCEPT { return y + x; } CONSTCD11 inline day operator-(const day& x, const days& y) NOEXCEPT { return x + -y; } template inline std::basic_ostream& operator<<(std::basic_ostream& os, const day& d) { date::detail::save_ostream _(os); os.fill('0'); os.flags(std::ios::dec | std::ios::right); os.width(2); os << static_cast(d); return os; } // month CONSTCD11 inline month::month(unsigned m) NOEXCEPT : m_(static_cast(m)) {} CONSTCD14 inline month& month::operator++() NOEXCEPT {if (++m_ == 13) m_ = 1; return *this;} CONSTCD14 inline month month::operator++(int) NOEXCEPT {auto tmp(*this); ++(*this); return tmp;} CONSTCD14 inline month& month::operator--() NOEXCEPT {if (--m_ == 0) m_ = 12; return *this;} CONSTCD14 inline month month::operator--(int) NOEXCEPT {auto tmp(*this); --(*this); return tmp;} CONSTCD14 inline month& month::operator+=(const months& m) NOEXCEPT { *this = *this + m; return *this; } CONSTCD14 inline month& month::operator-=(const months& m) NOEXCEPT { *this = *this - m; return *this; } CONSTCD11 inline month::operator unsigned() const NOEXCEPT {return m_;} CONSTCD11 inline bool month::ok() const NOEXCEPT {return 1 <= m_ && m_ <= 12;} CONSTCD11 inline bool operator==(const month& x, const month& y) NOEXCEPT { return static_cast(x) == static_cast(y); } CONSTCD11 inline bool operator!=(const month& x, const month& y) NOEXCEPT { return !(x == y); } CONSTCD11 inline bool operator<(const month& x, const month& y) NOEXCEPT { return static_cast(x) < static_cast(y); } CONSTCD11 inline bool operator>(const month& x, const month& y) NOEXCEPT { return y < x; } CONSTCD11 inline bool operator<=(const month& x, const month& y) NOEXCEPT { return !(y < x); } CONSTCD11 inline bool operator>=(const month& x, const month& y) NOEXCEPT { return !(x < y); } CONSTCD14 inline months operator-(const month& x, const month& y) NOEXCEPT { auto const d = static_cast(x) - static_cast(y); return months(d <= 11 ? d : d + 12); } CONSTCD14 inline month operator+(const month& x, const months& y) NOEXCEPT { auto const mu = static_cast(static_cast(x)) - 1 + y.count(); auto const yr = (mu >= 0 ? mu : mu-11) / 12; return month{static_cast(mu - yr * 12 + 1)}; } CONSTCD14 inline month operator+(const months& x, const month& y) NOEXCEPT { return y + x; } CONSTCD14 inline month operator-(const month& x, const months& y) NOEXCEPT { return x + -y; } template inline std::basic_ostream& operator<<(std::basic_ostream& os, const month& m) { switch (static_cast(m)) { case 1: os << "Jan"; break; case 2: os << "Feb"; break; case 3: os << "Mar"; break; case 4: os << "Apr"; break; case 5: os << "May"; break; case 6: os << "Jun"; break; case 7: os << "Jul"; break; case 8: os << "Aug"; break; case 9: os << "Sep"; break; case 10: os << "Oct"; break; case 11: os << "Nov"; break; case 12: os << "Dec"; break; default: os << static_cast(m) << " is not a valid month"; break; } return os; } // year CONSTCD11 inline year::year(int y) NOEXCEPT : y_(static_cast(y)) {} CONSTCD14 inline year& year::operator++() NOEXCEPT {++y_; return *this;} CONSTCD14 inline year year::operator++(int) NOEXCEPT {auto tmp(*this); ++(*this); return tmp;} CONSTCD14 inline year& year::operator--() NOEXCEPT {--y_; return *this;} CONSTCD14 inline year year::operator--(int) NOEXCEPT {auto tmp(*this); --(*this); return tmp;} CONSTCD14 inline year& year::operator+=(const years& y) NOEXCEPT {*this = *this + y; return *this;} CONSTCD14 inline year& year::operator-=(const years& y) NOEXCEPT {*this = *this - y; return *this;} CONSTCD11 inline bool year::is_leap() const NOEXCEPT { return y_ % 4 == 0; } CONSTCD11 inline year::operator int() const NOEXCEPT {return y_;} CONSTCD11 inline bool year::ok() const NOEXCEPT {return true;} CONSTCD11 inline year year::min() NOEXCEPT { return year{std::numeric_limits::min()}; } CONSTCD11 inline year year::max() NOEXCEPT { return year{std::numeric_limits::max()}; } CONSTCD11 inline bool operator==(const year& x, const year& y) NOEXCEPT { return static_cast(x) == static_cast(y); } CONSTCD11 inline bool operator!=(const year& x, const year& y) NOEXCEPT { return !(x == y); } CONSTCD11 inline bool operator<(const year& x, const year& y) NOEXCEPT { return static_cast(x) < static_cast(y); } CONSTCD11 inline bool operator>(const year& x, const year& y) NOEXCEPT { return y < x; } CONSTCD11 inline bool operator<=(const year& x, const year& y) NOEXCEPT { return !(y < x); } CONSTCD11 inline bool operator>=(const year& x, const year& y) NOEXCEPT { return !(x < y); } CONSTCD11 inline years operator-(const year& x, const year& y) NOEXCEPT { return years{static_cast(x) - static_cast(y)}; } CONSTCD11 inline year operator+(const year& x, const years& y) NOEXCEPT { return year{static_cast(x) + y.count()}; } CONSTCD11 inline year operator+(const years& x, const year& y) NOEXCEPT { return y + x; } CONSTCD11 inline year operator-(const year& x, const years& y) NOEXCEPT { return year{static_cast(x) - y.count()}; } template inline std::basic_ostream& operator<<(std::basic_ostream& os, const year& y) { date::detail::save_ostream _(os); os.fill('0'); os.flags(std::ios::dec | std::ios::internal); os.width(4 + (y < year{0})); os << static_cast(y); return os; } // weekday CONSTCD11 inline unsigned char weekday::weekday_from_days(int z) NOEXCEPT { return static_cast(static_cast( z >= -4 ? (z+4) % 7 : (z+5) % 7 + 6)); } CONSTCD11 inline weekday::weekday(unsigned wd) NOEXCEPT : wd_(static_cast(wd)) {} CONSTCD11 inline weekday::weekday(const sys_days& dp) NOEXCEPT : wd_(weekday_from_days(dp.time_since_epoch().count())) {} CONSTCD11 inline weekday::weekday(const local_days& dp) NOEXCEPT : wd_(weekday_from_days(dp.time_since_epoch().count())) {} CONSTCD14 inline weekday& weekday::operator++() NOEXCEPT {if (++wd_ == 7) wd_ = 0; return *this;} CONSTCD14 inline weekday weekday::operator++(int) NOEXCEPT {auto tmp(*this); ++(*this); return tmp;} CONSTCD14 inline weekday& weekday::operator--() NOEXCEPT {if (wd_-- == 0) wd_ = 6; return *this;} CONSTCD14 inline weekday weekday::operator--(int) NOEXCEPT {auto tmp(*this); --(*this); return tmp;} CONSTCD14 inline weekday& weekday::operator+=(const days& d) NOEXCEPT { *this = *this + d; return *this; } CONSTCD14 inline weekday& weekday::operator-=(const days& d) NOEXCEPT { *this = *this - d; return *this; } CONSTCD11 inline weekday::operator unsigned() const NOEXCEPT { return static_cast(wd_); } CONSTCD11 inline bool weekday::ok() const NOEXCEPT {return wd_ <= 6;} CONSTCD11 inline bool operator==(const weekday& x, const weekday& y) NOEXCEPT { return static_cast(x) == static_cast(y); } CONSTCD11 inline bool operator!=(const weekday& x, const weekday& y) NOEXCEPT { return !(x == y); } CONSTCD14 inline days operator-(const weekday& x, const weekday& y) NOEXCEPT { auto const diff = static_cast(x) - static_cast(y); return days{diff <= 6 ? diff : diff + 7}; } CONSTCD14 inline weekday operator+(const weekday& x, const days& y) NOEXCEPT { auto const wdu = static_cast(static_cast(x)) + y.count(); auto const wk = (wdu >= 0 ? wdu : wdu-6) / 7; return weekday{static_cast(wdu - wk * 7)}; } CONSTCD14 inline weekday operator+(const days& x, const weekday& y) NOEXCEPT { return y + x; } CONSTCD14 inline weekday operator-(const weekday& x, const days& y) NOEXCEPT { return x + -y; } template inline std::basic_ostream& operator<<(std::basic_ostream& os, const weekday& wd) { switch (static_cast(wd)) { case 0: os << "Sun"; break; case 1: os << "Mon"; break; case 2: os << "Tue"; break; case 3: os << "Wed"; break; case 4: os << "Thu"; break; case 5: os << "Fri"; break; case 6: os << "Sat"; break; default: os << static_cast(wd) << " is not a valid weekday"; break; } return os; } #if !defined(_MSC_VER) || (_MSC_VER >= 1900) inline namespace literals { CONSTCD11 inline julian::day operator ""_d(unsigned long long d) NOEXCEPT { return julian::day{static_cast(d)}; } CONSTCD11 inline julian::year operator ""_y(unsigned long long y) NOEXCEPT { return julian::year(static_cast(y)); } #endif // !defined(_MSC_VER) || (_MSC_VER >= 1900) CONSTDATA julian::last_spec last{}; CONSTDATA julian::month jan{1}; CONSTDATA julian::month feb{2}; CONSTDATA julian::month mar{3}; CONSTDATA julian::month apr{4}; CONSTDATA julian::month may{5}; CONSTDATA julian::month jun{6}; CONSTDATA julian::month jul{7}; CONSTDATA julian::month aug{8}; CONSTDATA julian::month sep{9}; CONSTDATA julian::month oct{10}; CONSTDATA julian::month nov{11}; CONSTDATA julian::month dec{12}; CONSTDATA julian::weekday sun{0u}; CONSTDATA julian::weekday mon{1u}; CONSTDATA julian::weekday tue{2u}; CONSTDATA julian::weekday wed{3u}; CONSTDATA julian::weekday thu{4u}; CONSTDATA julian::weekday fri{5u}; CONSTDATA julian::weekday sat{6u}; #if !defined(_MSC_VER) || (_MSC_VER >= 1900) } // inline namespace literals #endif // weekday_indexed CONSTCD11 inline weekday weekday_indexed::weekday() const NOEXCEPT { return julian::weekday{static_cast(wd_)}; } CONSTCD11 inline unsigned weekday_indexed::index() const NOEXCEPT {return index_;} CONSTCD11 inline bool weekday_indexed::ok() const NOEXCEPT { return weekday().ok() && 1 <= index_ && index_ <= 5; } CONSTCD11 inline weekday_indexed::weekday_indexed(const julian::weekday& wd, unsigned index) NOEXCEPT : wd_(static_cast(static_cast(wd))) , index_(static_cast(index)) {} template inline std::basic_ostream& operator<<(std::basic_ostream& os, const weekday_indexed& wdi) { return os << wdi.weekday() << '[' << wdi.index() << ']'; } CONSTCD11 inline weekday_indexed weekday::operator[](unsigned index) const NOEXCEPT { return {*this, index}; } CONSTCD11 inline bool operator==(const weekday_indexed& x, const weekday_indexed& y) NOEXCEPT { return x.weekday() == y.weekday() && x.index() == y.index(); } CONSTCD11 inline bool operator!=(const weekday_indexed& x, const weekday_indexed& y) NOEXCEPT { return !(x == y); } // weekday_last CONSTCD11 inline julian::weekday weekday_last::weekday() const NOEXCEPT {return wd_;} CONSTCD11 inline bool weekday_last::ok() const NOEXCEPT {return wd_.ok();} CONSTCD11 inline weekday_last::weekday_last(const julian::weekday& wd) NOEXCEPT : wd_(wd) {} CONSTCD11 inline bool operator==(const weekday_last& x, const weekday_last& y) NOEXCEPT { return x.weekday() == y.weekday(); } CONSTCD11 inline bool operator!=(const weekday_last& x, const weekday_last& y) NOEXCEPT { return !(x == y); } template inline std::basic_ostream& operator<<(std::basic_ostream& os, const weekday_last& wdl) { return os << wdl.weekday() << "[last]"; } CONSTCD11 inline weekday_last weekday::operator[](last_spec) const NOEXCEPT { return weekday_last{*this}; } // year_month CONSTCD11 inline year_month::year_month(const julian::year& y, const julian::month& m) NOEXCEPT : y_(y) , m_(m) {} CONSTCD11 inline year year_month::year() const NOEXCEPT {return y_;} CONSTCD11 inline month year_month::month() const NOEXCEPT {return m_;} CONSTCD11 inline bool year_month::ok() const NOEXCEPT {return y_.ok() && m_.ok();} CONSTCD14 inline year_month& year_month::operator+=(const months& dm) NOEXCEPT { *this = *this + dm; return *this; } CONSTCD14 inline year_month& year_month::operator-=(const months& dm) NOEXCEPT { *this = *this - dm; return *this; } CONSTCD14 inline year_month& year_month::operator+=(const years& dy) NOEXCEPT { *this = *this + dy; return *this; } CONSTCD14 inline year_month& year_month::operator-=(const years& dy) NOEXCEPT { *this = *this - dy; return *this; } CONSTCD11 inline bool operator==(const year_month& x, const year_month& y) NOEXCEPT { return x.year() == y.year() && x.month() == y.month(); } CONSTCD11 inline bool operator!=(const year_month& x, const year_month& y) NOEXCEPT { return !(x == y); } CONSTCD11 inline bool operator<(const year_month& x, const year_month& y) NOEXCEPT { return x.year() < y.year() ? true : (x.year() > y.year() ? false : (x.month() < y.month())); } CONSTCD11 inline bool operator>(const year_month& x, const year_month& y) NOEXCEPT { return y < x; } CONSTCD11 inline bool operator<=(const year_month& x, const year_month& y) NOEXCEPT { return !(y < x); } CONSTCD11 inline bool operator>=(const year_month& x, const year_month& y) NOEXCEPT { return !(x < y); } CONSTCD14 inline year_month operator+(const year_month& ym, const months& dm) NOEXCEPT { auto dmi = static_cast(static_cast(ym.month())) - 1 + dm.count(); auto dy = (dmi >= 0 ? dmi : dmi-11) / 12; dmi = dmi - dy * 12 + 1; return (ym.year() + years(dy)) / month(static_cast(dmi)); } CONSTCD14 inline year_month operator+(const months& dm, const year_month& ym) NOEXCEPT { return ym + dm; } CONSTCD14 inline year_month operator-(const year_month& ym, const months& dm) NOEXCEPT { return ym + -dm; } CONSTCD11 inline months operator-(const year_month& x, const year_month& y) NOEXCEPT { return (x.year() - y.year()) + months(static_cast(x.month()) - static_cast(y.month())); } CONSTCD11 inline year_month operator+(const year_month& ym, const years& dy) NOEXCEPT { return (ym.year() + dy) / ym.month(); } CONSTCD11 inline year_month operator+(const years& dy, const year_month& ym) NOEXCEPT { return ym + dy; } CONSTCD11 inline year_month operator-(const year_month& ym, const years& dy) NOEXCEPT { return ym + -dy; } template inline std::basic_ostream& operator<<(std::basic_ostream& os, const year_month& ym) { return os << ym.year() << '/' << ym.month(); } // month_day CONSTCD11 inline month_day::month_day(const julian::month& m, const julian::day& d) NOEXCEPT : m_(m) , d_(d) {} CONSTCD11 inline julian::month month_day::month() const NOEXCEPT {return m_;} CONSTCD11 inline julian::day month_day::day() const NOEXCEPT {return d_;} CONSTCD14 inline bool month_day::ok() const NOEXCEPT { CONSTDATA julian::day d[] = { julian::day(31), julian::day(29), julian::day(31), julian::day(30), julian::day(31), julian::day(30), julian::day(31), julian::day(31), julian::day(30), julian::day(31), julian::day(30), julian::day(31) }; return m_.ok() && julian::day(1) <= d_ && d_ <= d[static_cast(m_)-1]; } CONSTCD11 inline bool operator==(const month_day& x, const month_day& y) NOEXCEPT { return x.month() == y.month() && x.day() == y.day(); } CONSTCD11 inline bool operator!=(const month_day& x, const month_day& y) NOEXCEPT { return !(x == y); } CONSTCD11 inline bool operator<(const month_day& x, const month_day& y) NOEXCEPT { return x.month() < y.month() ? true : (x.month() > y.month() ? false : (x.day() < y.day())); } CONSTCD11 inline bool operator>(const month_day& x, const month_day& y) NOEXCEPT { return y < x; } CONSTCD11 inline bool operator<=(const month_day& x, const month_day& y) NOEXCEPT { return !(y < x); } CONSTCD11 inline bool operator>=(const month_day& x, const month_day& y) NOEXCEPT { return !(x < y); } template inline std::basic_ostream& operator<<(std::basic_ostream& os, const month_day& md) { return os << md.month() << '/' << md.day(); } // month_day_last CONSTCD11 inline month month_day_last::month() const NOEXCEPT {return m_;} CONSTCD11 inline bool month_day_last::ok() const NOEXCEPT {return m_.ok();} CONSTCD11 inline month_day_last::month_day_last(const julian::month& m) NOEXCEPT : m_(m) {} CONSTCD11 inline bool operator==(const month_day_last& x, const month_day_last& y) NOEXCEPT { return x.month() == y.month(); } CONSTCD11 inline bool operator!=(const month_day_last& x, const month_day_last& y) NOEXCEPT { return !(x == y); } CONSTCD11 inline bool operator<(const month_day_last& x, const month_day_last& y) NOEXCEPT { return x.month() < y.month(); } CONSTCD11 inline bool operator>(const month_day_last& x, const month_day_last& y) NOEXCEPT { return y < x; } CONSTCD11 inline bool operator<=(const month_day_last& x, const month_day_last& y) NOEXCEPT { return !(y < x); } CONSTCD11 inline bool operator>=(const month_day_last& x, const month_day_last& y) NOEXCEPT { return !(x < y); } template inline std::basic_ostream& operator<<(std::basic_ostream& os, const month_day_last& mdl) { return os << mdl.month() << "/last"; } // month_weekday CONSTCD11 inline month_weekday::month_weekday(const julian::month& m, const julian::weekday_indexed& wdi) NOEXCEPT : m_(m) , wdi_(wdi) {} CONSTCD11 inline month month_weekday::month() const NOEXCEPT {return m_;} CONSTCD11 inline weekday_indexed month_weekday::weekday_indexed() const NOEXCEPT { return wdi_; } CONSTCD11 inline bool month_weekday::ok() const NOEXCEPT { return m_.ok() && wdi_.ok(); } CONSTCD11 inline bool operator==(const month_weekday& x, const month_weekday& y) NOEXCEPT { return x.month() == y.month() && x.weekday_indexed() == y.weekday_indexed(); } CONSTCD11 inline bool operator!=(const month_weekday& x, const month_weekday& y) NOEXCEPT { return !(x == y); } template inline std::basic_ostream& operator<<(std::basic_ostream& os, const month_weekday& mwd) { return os << mwd.month() << '/' << mwd.weekday_indexed(); } // month_weekday_last CONSTCD11 inline month_weekday_last::month_weekday_last(const julian::month& m, const julian::weekday_last& wdl) NOEXCEPT : m_(m) , wdl_(wdl) {} CONSTCD11 inline month month_weekday_last::month() const NOEXCEPT {return m_;} CONSTCD11 inline weekday_last month_weekday_last::weekday_last() const NOEXCEPT { return wdl_; } CONSTCD11 inline bool month_weekday_last::ok() const NOEXCEPT { return m_.ok() && wdl_.ok(); } CONSTCD11 inline bool operator==(const month_weekday_last& x, const month_weekday_last& y) NOEXCEPT { return x.month() == y.month() && x.weekday_last() == y.weekday_last(); } CONSTCD11 inline bool operator!=(const month_weekday_last& x, const month_weekday_last& y) NOEXCEPT { return !(x == y); } template inline std::basic_ostream& operator<<(std::basic_ostream& os, const month_weekday_last& mwdl) { return os << mwdl.month() << '/' << mwdl.weekday_last(); } // year_month_day_last CONSTCD11 inline year_month_day_last::year_month_day_last(const julian::year& y, const julian::month_day_last& mdl) NOEXCEPT : y_(y) , mdl_(mdl) {} CONSTCD14 inline year_month_day_last& year_month_day_last::operator+=(const months& m) NOEXCEPT { *this = *this + m; return *this; } CONSTCD14 inline year_month_day_last& year_month_day_last::operator-=(const months& m) NOEXCEPT { *this = *this - m; return *this; } CONSTCD14 inline year_month_day_last& year_month_day_last::operator+=(const years& y) NOEXCEPT { *this = *this + y; return *this; } CONSTCD14 inline year_month_day_last& year_month_day_last::operator-=(const years& y) NOEXCEPT { *this = *this - y; return *this; } CONSTCD11 inline year year_month_day_last::year() const NOEXCEPT {return y_;} CONSTCD11 inline month year_month_day_last::month() const NOEXCEPT {return mdl_.month();} CONSTCD11 inline month_day_last year_month_day_last::month_day_last() const NOEXCEPT { return mdl_; } CONSTCD14 inline day year_month_day_last::day() const NOEXCEPT { CONSTDATA julian::day d[] = { julian::day(31), julian::day(28), julian::day(31), julian::day(30), julian::day(31), julian::day(30), julian::day(31), julian::day(31), julian::day(30), julian::day(31), julian::day(30), julian::day(31) }; return month() != feb || !y_.is_leap() ? d[static_cast(month())-1] : julian::day(29); } CONSTCD14 inline year_month_day_last::operator sys_days() const NOEXCEPT { return sys_days(year()/month()/day()); } CONSTCD14 inline year_month_day_last::operator local_days() const NOEXCEPT { return local_days(year()/month()/day()); } CONSTCD11 inline bool year_month_day_last::ok() const NOEXCEPT { return y_.ok() && mdl_.ok(); } CONSTCD11 inline bool operator==(const year_month_day_last& x, const year_month_day_last& y) NOEXCEPT { return x.year() == y.year() && x.month_day_last() == y.month_day_last(); } CONSTCD11 inline bool operator!=(const year_month_day_last& x, const year_month_day_last& y) NOEXCEPT { return !(x == y); } CONSTCD11 inline bool operator<(const year_month_day_last& x, const year_month_day_last& y) NOEXCEPT { return x.year() < y.year() ? true : (x.year() > y.year() ? false : (x.month_day_last() < y.month_day_last())); } CONSTCD11 inline bool operator>(const year_month_day_last& x, const year_month_day_last& y) NOEXCEPT { return y < x; } CONSTCD11 inline bool operator<=(const year_month_day_last& x, const year_month_day_last& y) NOEXCEPT { return !(y < x); } CONSTCD11 inline bool operator>=(const year_month_day_last& x, const year_month_day_last& y) NOEXCEPT { return !(x < y); } template inline std::basic_ostream& operator<<(std::basic_ostream& os, const year_month_day_last& ymdl) { return os << ymdl.year() << '/' << ymdl.month_day_last(); } CONSTCD14 inline year_month_day_last operator+(const year_month_day_last& ymdl, const months& dm) NOEXCEPT { return (ymdl.year() / ymdl.month() + dm) / last; } CONSTCD14 inline year_month_day_last operator+(const months& dm, const year_month_day_last& ymdl) NOEXCEPT { return ymdl + dm; } CONSTCD14 inline year_month_day_last operator-(const year_month_day_last& ymdl, const months& dm) NOEXCEPT { return ymdl + (-dm); } CONSTCD11 inline year_month_day_last operator+(const year_month_day_last& ymdl, const years& dy) NOEXCEPT { return {ymdl.year()+dy, ymdl.month_day_last()}; } CONSTCD11 inline year_month_day_last operator+(const years& dy, const year_month_day_last& ymdl) NOEXCEPT { return ymdl + dy; } CONSTCD11 inline year_month_day_last operator-(const year_month_day_last& ymdl, const years& dy) NOEXCEPT { return ymdl + (-dy); } // year_month_day CONSTCD11 inline year_month_day::year_month_day(const julian::year& y, const julian::month& m, const julian::day& d) NOEXCEPT : y_(y) , m_(m) , d_(d) {} CONSTCD14 inline year_month_day::year_month_day(const year_month_day_last& ymdl) NOEXCEPT : y_(ymdl.year()) , m_(ymdl.month()) , d_(ymdl.day()) {} CONSTCD14 inline year_month_day::year_month_day(sys_days dp) NOEXCEPT : year_month_day(from_days(dp.time_since_epoch())) {} CONSTCD14 inline year_month_day::year_month_day(local_days dp) NOEXCEPT : year_month_day(from_days(dp.time_since_epoch())) {} CONSTCD11 inline year year_month_day::year() const NOEXCEPT {return y_;} CONSTCD11 inline month year_month_day::month() const NOEXCEPT {return m_;} CONSTCD11 inline day year_month_day::day() const NOEXCEPT {return d_;} CONSTCD14 inline year_month_day& year_month_day::operator+=(const months& m) NOEXCEPT { *this = *this + m; return *this; } CONSTCD14 inline year_month_day& year_month_day::operator-=(const months& m) NOEXCEPT { *this = *this - m; return *this; } CONSTCD14 inline year_month_day& year_month_day::operator+=(const years& y) NOEXCEPT { *this = *this + y; return *this; } CONSTCD14 inline year_month_day& year_month_day::operator-=(const years& y) NOEXCEPT { *this = *this - y; return *this; } CONSTCD14 inline days year_month_day::to_days() const NOEXCEPT { static_assert(std::numeric_limits::digits >= 18, "This algorithm has not been ported to a 16 bit unsigned integer"); static_assert(std::numeric_limits::digits >= 20, "This algorithm has not been ported to a 16 bit signed integer"); auto const y = static_cast(y_) - (m_ <= feb); auto const m = static_cast(m_); auto const d = static_cast(d_); auto const era = (y >= 0 ? y : y-3) / 4; auto const yoe = static_cast(y - era * 4); // [0, 3] auto const doy = (153*(m > 2 ? m-3 : m+9) + 2)/5 + d-1; // [0, 365] auto const doe = yoe * 365 + doy; // [0, 1460] return days{era * 1461 + static_cast(doe) - 719470}; } CONSTCD14 inline year_month_day::operator sys_days() const NOEXCEPT { return sys_days{to_days()}; } CONSTCD14 inline year_month_day::operator local_days() const NOEXCEPT { return local_days{to_days()}; } CONSTCD14 inline bool year_month_day::ok() const NOEXCEPT { if (!(y_.ok() && m_.ok())) return false; return julian::day(1) <= d_ && d_ <= (y_/m_/last).day(); } CONSTCD11 inline bool operator==(const year_month_day& x, const year_month_day& y) NOEXCEPT { return x.year() == y.year() && x.month() == y.month() && x.day() == y.day(); } CONSTCD11 inline bool operator!=(const year_month_day& x, const year_month_day& y) NOEXCEPT { return !(x == y); } CONSTCD11 inline bool operator<(const year_month_day& x, const year_month_day& y) NOEXCEPT { return x.year() < y.year() ? true : (x.year() > y.year() ? false : (x.month() < y.month() ? true : (x.month() > y.month() ? false : (x.day() < y.day())))); } CONSTCD11 inline bool operator>(const year_month_day& x, const year_month_day& y) NOEXCEPT { return y < x; } CONSTCD11 inline bool operator<=(const year_month_day& x, const year_month_day& y) NOEXCEPT { return !(y < x); } CONSTCD11 inline bool operator>=(const year_month_day& x, const year_month_day& y) NOEXCEPT { return !(x < y); } template inline std::basic_ostream& operator<<(std::basic_ostream& os, const year_month_day& ymd) { date::detail::save_ostream _(os); os.fill('0'); os.flags(std::ios::dec | std::ios::right); os << ymd.year() << '-'; os.width(2); os << static_cast(ymd.month()) << '-'; os << ymd.day(); return os; } CONSTCD14 inline year_month_day year_month_day::from_days(days dp) NOEXCEPT { static_assert(std::numeric_limits::digits >= 18, "This algorithm has not been ported to a 16 bit unsigned integer"); static_assert(std::numeric_limits::digits >= 20, "This algorithm has not been ported to a 16 bit signed integer"); auto const z = dp.count() + 719470; auto const era = (z >= 0 ? z : z - 1460) / 1461; auto const doe = static_cast(z - era * 1461); // [0, 1460] auto const yoe = (doe - doe/1460) / 365; // [0, 3] auto const y = static_cast(yoe) + era * 4; auto const doy = doe - 365*yoe; // [0, 365] auto const mp = (5*doy + 2)/153; // [0, 11] auto const d = doy - (153*mp+2)/5 + 1; // [1, 31] auto const m = mp < 10 ? mp+3 : mp-9; // [1, 12] return year_month_day{julian::year{y + (m <= 2)}, julian::month(m), julian::day(d)}; } CONSTCD14 inline year_month_day operator+(const year_month_day& ymd, const months& dm) NOEXCEPT { return (ymd.year() / ymd.month() + dm) / ymd.day(); } CONSTCD14 inline year_month_day operator+(const months& dm, const year_month_day& ymd) NOEXCEPT { return ymd + dm; } CONSTCD14 inline year_month_day operator-(const year_month_day& ymd, const months& dm) NOEXCEPT { return ymd + (-dm); } CONSTCD11 inline year_month_day operator+(const year_month_day& ymd, const years& dy) NOEXCEPT { return (ymd.year() + dy) / ymd.month() / ymd.day(); } CONSTCD11 inline year_month_day operator+(const years& dy, const year_month_day& ymd) NOEXCEPT { return ymd + dy; } CONSTCD11 inline year_month_day operator-(const year_month_day& ymd, const years& dy) NOEXCEPT { return ymd + (-dy); } // year_month_weekday CONSTCD11 inline year_month_weekday::year_month_weekday(const julian::year& y, const julian::month& m, const julian::weekday_indexed& wdi) NOEXCEPT : y_(y) , m_(m) , wdi_(wdi) {} CONSTCD14 inline year_month_weekday::year_month_weekday(const sys_days& dp) NOEXCEPT : year_month_weekday(from_days(dp.time_since_epoch())) {} CONSTCD14 inline year_month_weekday::year_month_weekday(const local_days& dp) NOEXCEPT : year_month_weekday(from_days(dp.time_since_epoch())) {} CONSTCD14 inline year_month_weekday& year_month_weekday::operator+=(const months& m) NOEXCEPT { *this = *this + m; return *this; } CONSTCD14 inline year_month_weekday& year_month_weekday::operator-=(const months& m) NOEXCEPT { *this = *this - m; return *this; } CONSTCD14 inline year_month_weekday& year_month_weekday::operator+=(const years& y) NOEXCEPT { *this = *this + y; return *this; } CONSTCD14 inline year_month_weekday& year_month_weekday::operator-=(const years& y) NOEXCEPT { *this = *this - y; return *this; } CONSTCD11 inline year year_month_weekday::year() const NOEXCEPT {return y_;} CONSTCD11 inline month year_month_weekday::month() const NOEXCEPT {return m_;} CONSTCD11 inline weekday year_month_weekday::weekday() const NOEXCEPT { return wdi_.weekday(); } CONSTCD11 inline unsigned year_month_weekday::index() const NOEXCEPT { return wdi_.index(); } CONSTCD11 inline weekday_indexed year_month_weekday::weekday_indexed() const NOEXCEPT { return wdi_; } CONSTCD14 inline year_month_weekday::operator sys_days() const NOEXCEPT { return sys_days{to_days()}; } CONSTCD14 inline year_month_weekday::operator local_days() const NOEXCEPT { return local_days{to_days()}; } CONSTCD14 inline bool year_month_weekday::ok() const NOEXCEPT { if (!y_.ok() || !m_.ok() || !wdi_.weekday().ok() || wdi_.index() < 1) return false; if (wdi_.index() <= 4) return true; auto d2 = wdi_.weekday() - julian::weekday(y_/m_/1) + days((wdi_.index()-1)*7 + 1); return static_cast(d2.count()) <= static_cast((y_/m_/last).day()); } CONSTCD14 inline year_month_weekday year_month_weekday::from_days(days d) NOEXCEPT { sys_days dp{d}; auto const wd = julian::weekday(dp); auto const ymd = year_month_day(dp); return {ymd.year(), ymd.month(), wd[(static_cast(ymd.day())-1)/7+1]}; } CONSTCD14 inline days year_month_weekday::to_days() const NOEXCEPT { auto d = sys_days(y_/m_/1); return (d + (wdi_.weekday() - julian::weekday(d) + days{(wdi_.index()-1)*7}) ).time_since_epoch(); } CONSTCD11 inline bool operator==(const year_month_weekday& x, const year_month_weekday& y) NOEXCEPT { return x.year() == y.year() && x.month() == y.month() && x.weekday_indexed() == y.weekday_indexed(); } CONSTCD11 inline bool operator!=(const year_month_weekday& x, const year_month_weekday& y) NOEXCEPT { return !(x == y); } template inline std::basic_ostream& operator<<(std::basic_ostream& os, const year_month_weekday& ymwdi) { return os << ymwdi.year() << '/' << ymwdi.month() << '/' << ymwdi.weekday_indexed(); } CONSTCD14 inline year_month_weekday operator+(const year_month_weekday& ymwd, const months& dm) NOEXCEPT { return (ymwd.year() / ymwd.month() + dm) / ymwd.weekday_indexed(); } CONSTCD14 inline year_month_weekday operator+(const months& dm, const year_month_weekday& ymwd) NOEXCEPT { return ymwd + dm; } CONSTCD14 inline year_month_weekday operator-(const year_month_weekday& ymwd, const months& dm) NOEXCEPT { return ymwd + (-dm); } CONSTCD11 inline year_month_weekday operator+(const year_month_weekday& ymwd, const years& dy) NOEXCEPT { return {ymwd.year()+dy, ymwd.month(), ymwd.weekday_indexed()}; } CONSTCD11 inline year_month_weekday operator+(const years& dy, const year_month_weekday& ymwd) NOEXCEPT { return ymwd + dy; } CONSTCD11 inline year_month_weekday operator-(const year_month_weekday& ymwd, const years& dy) NOEXCEPT { return ymwd + (-dy); } // year_month_weekday_last CONSTCD11 inline year_month_weekday_last::year_month_weekday_last(const julian::year& y, const julian::month& m, const julian::weekday_last& wdl) NOEXCEPT : y_(y) , m_(m) , wdl_(wdl) {} CONSTCD14 inline year_month_weekday_last& year_month_weekday_last::operator+=(const months& m) NOEXCEPT { *this = *this + m; return *this; } CONSTCD14 inline year_month_weekday_last& year_month_weekday_last::operator-=(const months& m) NOEXCEPT { *this = *this - m; return *this; } CONSTCD14 inline year_month_weekday_last& year_month_weekday_last::operator+=(const years& y) NOEXCEPT { *this = *this + y; return *this; } CONSTCD14 inline year_month_weekday_last& year_month_weekday_last::operator-=(const years& y) NOEXCEPT { *this = *this - y; return *this; } CONSTCD11 inline year year_month_weekday_last::year() const NOEXCEPT {return y_;} CONSTCD11 inline month year_month_weekday_last::month() const NOEXCEPT {return m_;} CONSTCD11 inline weekday year_month_weekday_last::weekday() const NOEXCEPT { return wdl_.weekday(); } CONSTCD11 inline weekday_last year_month_weekday_last::weekday_last() const NOEXCEPT { return wdl_; } CONSTCD14 inline year_month_weekday_last::operator sys_days() const NOEXCEPT { return sys_days{to_days()}; } CONSTCD14 inline year_month_weekday_last::operator local_days() const NOEXCEPT { return local_days{to_days()}; } CONSTCD11 inline bool year_month_weekday_last::ok() const NOEXCEPT { return y_.ok() && m_.ok() && wdl_.ok(); } CONSTCD14 inline days year_month_weekday_last::to_days() const NOEXCEPT { auto const d = sys_days(y_/m_/last); return (d - (julian::weekday{d} - wdl_.weekday())).time_since_epoch(); } CONSTCD11 inline bool operator==(const year_month_weekday_last& x, const year_month_weekday_last& y) NOEXCEPT { return x.year() == y.year() && x.month() == y.month() && x.weekday_last() == y.weekday_last(); } CONSTCD11 inline bool operator!=(const year_month_weekday_last& x, const year_month_weekday_last& y) NOEXCEPT { return !(x == y); } template inline std::basic_ostream& operator<<(std::basic_ostream& os, const year_month_weekday_last& ymwdl) { return os << ymwdl.year() << '/' << ymwdl.month() << '/' << ymwdl.weekday_last(); } CONSTCD14 inline year_month_weekday_last operator+(const year_month_weekday_last& ymwdl, const months& dm) NOEXCEPT { return (ymwdl.year() / ymwdl.month() + dm) / ymwdl.weekday_last(); } CONSTCD14 inline year_month_weekday_last operator+(const months& dm, const year_month_weekday_last& ymwdl) NOEXCEPT { return ymwdl + dm; } CONSTCD14 inline year_month_weekday_last operator-(const year_month_weekday_last& ymwdl, const months& dm) NOEXCEPT { return ymwdl + (-dm); } CONSTCD11 inline year_month_weekday_last operator+(const year_month_weekday_last& ymwdl, const years& dy) NOEXCEPT { return {ymwdl.year()+dy, ymwdl.month(), ymwdl.weekday_last()}; } CONSTCD11 inline year_month_weekday_last operator+(const years& dy, const year_month_weekday_last& ymwdl) NOEXCEPT { return ymwdl + dy; } CONSTCD11 inline year_month_weekday_last operator-(const year_month_weekday_last& ymwdl, const years& dy) NOEXCEPT { return ymwdl + (-dy); } // year_month from operator/() CONSTCD11 inline year_month operator/(const year& y, const month& m) NOEXCEPT { return {y, m}; } CONSTCD11 inline year_month operator/(const year& y, int m) NOEXCEPT { return y / month(static_cast(m)); } // month_day from operator/() CONSTCD11 inline month_day operator/(const month& m, const day& d) NOEXCEPT { return {m, d}; } CONSTCD11 inline month_day operator/(const day& d, const month& m) NOEXCEPT { return m / d; } CONSTCD11 inline month_day operator/(const month& m, int d) NOEXCEPT { return m / day(static_cast(d)); } CONSTCD11 inline month_day operator/(int m, const day& d) NOEXCEPT { return month(static_cast(m)) / d; } CONSTCD11 inline month_day operator/(const day& d, int m) NOEXCEPT {return m / d;} // month_day_last from operator/() CONSTCD11 inline month_day_last operator/(const month& m, last_spec) NOEXCEPT { return month_day_last{m}; } CONSTCD11 inline month_day_last operator/(last_spec, const month& m) NOEXCEPT { return m/last; } CONSTCD11 inline month_day_last operator/(int m, last_spec) NOEXCEPT { return month(static_cast(m))/last; } CONSTCD11 inline month_day_last operator/(last_spec, int m) NOEXCEPT { return m/last; } // month_weekday from operator/() CONSTCD11 inline month_weekday operator/(const month& m, const weekday_indexed& wdi) NOEXCEPT { return {m, wdi}; } CONSTCD11 inline month_weekday operator/(const weekday_indexed& wdi, const month& m) NOEXCEPT { return m / wdi; } CONSTCD11 inline month_weekday operator/(int m, const weekday_indexed& wdi) NOEXCEPT { return month(static_cast(m)) / wdi; } CONSTCD11 inline month_weekday operator/(const weekday_indexed& wdi, int m) NOEXCEPT { return m / wdi; } // month_weekday_last from operator/() CONSTCD11 inline month_weekday_last operator/(const month& m, const weekday_last& wdl) NOEXCEPT { return {m, wdl}; } CONSTCD11 inline month_weekday_last operator/(const weekday_last& wdl, const month& m) NOEXCEPT { return m / wdl; } CONSTCD11 inline month_weekday_last operator/(int m, const weekday_last& wdl) NOEXCEPT { return month(static_cast(m)) / wdl; } CONSTCD11 inline month_weekday_last operator/(const weekday_last& wdl, int m) NOEXCEPT { return m / wdl; } // year_month_day from operator/() CONSTCD11 inline year_month_day operator/(const year_month& ym, const day& d) NOEXCEPT { return {ym.year(), ym.month(), d}; } CONSTCD11 inline year_month_day operator/(const year_month& ym, int d) NOEXCEPT { return ym / day(static_cast(d)); } CONSTCD11 inline year_month_day operator/(const year& y, const month_day& md) NOEXCEPT { return y / md.month() / md.day(); } CONSTCD11 inline year_month_day operator/(int y, const month_day& md) NOEXCEPT { return year(y) / md; } CONSTCD11 inline year_month_day operator/(const month_day& md, const year& y) NOEXCEPT { return y / md; } CONSTCD11 inline year_month_day operator/(const month_day& md, int y) NOEXCEPT { return year(y) / md; } // year_month_day_last from operator/() CONSTCD11 inline year_month_day_last operator/(const year_month& ym, last_spec) NOEXCEPT { return {ym.year(), month_day_last{ym.month()}}; } CONSTCD11 inline year_month_day_last operator/(const year& y, const month_day_last& mdl) NOEXCEPT { return {y, mdl}; } CONSTCD11 inline year_month_day_last operator/(int y, const month_day_last& mdl) NOEXCEPT { return year(y) / mdl; } CONSTCD11 inline year_month_day_last operator/(const month_day_last& mdl, const year& y) NOEXCEPT { return y / mdl; } CONSTCD11 inline year_month_day_last operator/(const month_day_last& mdl, int y) NOEXCEPT { return year(y) / mdl; } // year_month_weekday from operator/() CONSTCD11 inline year_month_weekday operator/(const year_month& ym, const weekday_indexed& wdi) NOEXCEPT { return {ym.year(), ym.month(), wdi}; } CONSTCD11 inline year_month_weekday operator/(const year& y, const month_weekday& mwd) NOEXCEPT { return {y, mwd.month(), mwd.weekday_indexed()}; } CONSTCD11 inline year_month_weekday operator/(int y, const month_weekday& mwd) NOEXCEPT { return year(y) / mwd; } CONSTCD11 inline year_month_weekday operator/(const month_weekday& mwd, const year& y) NOEXCEPT { return y / mwd; } CONSTCD11 inline year_month_weekday operator/(const month_weekday& mwd, int y) NOEXCEPT { return year(y) / mwd; } // year_month_weekday_last from operator/() CONSTCD11 inline year_month_weekday_last operator/(const year_month& ym, const weekday_last& wdl) NOEXCEPT { return {ym.year(), ym.month(), wdl}; } CONSTCD11 inline year_month_weekday_last operator/(const year& y, const month_weekday_last& mwdl) NOEXCEPT { return {y, mwdl.month(), mwdl.weekday_last()}; } CONSTCD11 inline year_month_weekday_last operator/(int y, const month_weekday_last& mwdl) NOEXCEPT { return year(y) / mwdl; } CONSTCD11 inline year_month_weekday_last operator/(const month_weekday_last& mwdl, const year& y) NOEXCEPT { return y / mwdl; } CONSTCD11 inline year_month_weekday_last operator/(const month_weekday_last& mwdl, int y) NOEXCEPT { return year(y) / mwdl; } } // namespace julian #endif // JULIAN_H pr0m1th3as-datatypes-9c9a8d3/src/date/ptz.h000066400000000000000000000653341522766574100205620ustar00rootroot00000000000000#ifndef PTZ_H #define PTZ_H // The MIT License (MIT) // // Copyright (c) 2017 Howard Hinnant // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. // This header allows Posix-style time zones as specified for TZ here: // http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html#tag_08_03 // // Posix::time_zone can be constructed with a posix-style string and then used in // a zoned_time like so: // // zoned_time zt{"EST5EDT,M3.2.0,M11.1.0", // system_clock::now()}; // or: // // Posix::time_zone tz{"EST5EDT,M3.2.0,M11.1.0"}; // zoned_time zt{tz, system_clock::now()}; // // In C++17 CTAD simplifies this to: // // Posix::time_zone tz{"EST5EDT,M3.2.0,M11.1.0"}; // zoned_time zt{tz, system_clock::now()}; // // Extension to the Posix rules to allow a constant daylight saving offset: // // If the rule set is missing (everything starting with ','), then // there must be exactly one abbreviation (std or daylight) with // length 3 or greater, and that will be used as the constant offset. If // there are two, the std abbreviation is silently set to "", and the // result is constant daylight saving. If there are zero abbreviations // with no rule set, an exception is thrown. // // Example: // "EST5" yields a constant offset of -5h with 0h save and "EST abbreviation. // "5EDT" yields a constant offset of -4h with 1h save and "EDT" abbreviation. // "EST5EDT" and "5EDT4" are both equal to "5EDT". // // Note, Posix-style time zones are not recommended for all of the reasons described here: // https://stackoverflow.com/tags/timezone/info // // They are provided here as a non-trivial custom time zone example, and if you really // have to have Posix time zones, you're welcome to use this one. #include "date/tz.h" #include #include #include #include namespace Posix { namespace detail { #if HAS_STRING_VIEW using string_t = std::string_view; #else // !HAS_STRING_VIEW using string_t = std::string; #endif // !HAS_STRING_VIEW class rule; void throw_invalid(const string_t& s, unsigned i, const string_t& message); unsigned read_date(const string_t& s, unsigned i, rule& r); unsigned read_name(const string_t& s, unsigned i, std::string& name); unsigned read_signed_time(const string_t& s, unsigned i, std::chrono::seconds& t); unsigned read_unsigned_time(const string_t& s, unsigned i, std::chrono::seconds& t); unsigned read_unsigned(const string_t& s, unsigned i, unsigned limit, unsigned& u, const string_t& message = string_t{}); class rule { enum {off, J, M, N}; date::month m_; date::weekday wd_; unsigned short n_ : 14; unsigned short mode_ : 2; std::chrono::duration time_ = std::chrono::hours{2}; public: rule() : mode_(off) {} bool ok() const {return mode_ != off;} date::local_seconds operator()(date::year y) const; std::string to_string() const; friend std::ostream& operator<<(std::ostream& os, const rule& r); friend unsigned read_date(const string_t& s, unsigned i, rule& r); friend bool operator==(const rule& x, const rule& y); }; inline bool operator==(const rule& x, const rule& y) { if (x.mode_ != y.mode_) return false; switch (x.mode_) { case rule::J: case rule::N: return x.n_ == y.n_; case rule::M: return x.m_ == y.m_ && x.n_ == y.n_ && x.wd_ == y.wd_; default: return true; } } inline bool operator!=(const rule& x, const rule& y) { return !(x == y); } inline date::local_seconds rule::operator()(date::year y) const { using date::local_days; using date::January; using date::days; using date::last; using sec = std::chrono::seconds; date::local_seconds t; switch (mode_) { case J: t = local_days{y/January/0} + days{n_ + (y.is_leap() && n_ > 59)} + sec{time_}; break; case M: t = (n_ == 5 ? local_days{y/m_/wd_[last]} : local_days{y/m_/wd_[n_]}) + sec{time_}; break; case N: t = local_days{y/January/1} + days{n_} + sec{time_}; break; default: assert(!"rule called with bad mode"); } return t; } inline std::string rule::to_string() const { using namespace std::chrono; auto print_offset = [](seconds off) { std::string nm; if (off != hours{2}) { date::hh_mm_ss offset{off}; nm = '/'; nm += std::to_string(offset.hours().count()); if (offset.minutes() != minutes{0} || offset.seconds() != seconds{0}) { nm += ':'; if (offset.minutes() < minutes{10}) nm += '0'; nm += std::to_string(offset.minutes().count()); if (offset.seconds() != seconds{0}) { nm += ':'; if (offset.seconds() < seconds{10}) nm += '0'; nm += std::to_string(offset.seconds().count()); } } } return nm; }; std::string nm; switch (mode_) { case rule::J: nm = 'J'; nm += std::to_string(n_); break; case rule::M: nm = 'M'; nm += std::to_string(static_cast(m_)); nm += '.'; nm += std::to_string(n_); nm += '.'; nm += std::to_string(wd_.c_encoding()); break; case rule::N: nm = std::to_string(n_); break; default: break; } nm += print_offset(time_); return nm; } inline std::ostream& operator<<(std::ostream& os, const rule& r) { switch (r.mode_) { case rule::J: os << 'J' << r.n_ << date::format(" %T", r.time_); break; case rule::M: if (r.n_ == 5) os << r.m_/r.wd_[date::last]; else os << r.m_/r.wd_[r.n_]; os << date::format(" %T", r.time_); break; case rule::N: os << r.n_ << date::format(" %T", r.time_); break; default: break; } return os; } } // namespace detail class time_zone { std::string std_abbrev_; std::string dst_abbrev_ = {}; std::chrono::seconds offset_; std::chrono::seconds save_ = std::chrono::hours{1}; detail::rule start_rule_; detail::rule end_rule_; public: explicit time_zone(const detail::string_t& name); template date::sys_info get_info(date::sys_time st) const; template date::local_info get_info(date::local_time tp) const; template date::sys_time::type> to_sys(date::local_time tp) const; template date::sys_time::type> to_sys(date::local_time tp, date::choose z) const; template date::local_time::type> to_local(date::sys_time tp) const; friend std::ostream& operator<<(std::ostream& os, const time_zone& z); const time_zone* operator->() const {return this;} std::string name() const; friend bool operator==(const time_zone& x, const time_zone& y); private: date::sys_seconds get_start(date::year y) const; date::sys_seconds get_prev_start(date::year y) const; date::sys_seconds get_next_start(date::year y) const; date::sys_seconds get_end(date::year y) const; date::sys_seconds get_prev_end(date::year y) const; date::sys_seconds get_next_end(date::year y) const; date::sys_info contant_offset() const; }; inline date::sys_seconds time_zone::get_start(date::year y) const { return date::sys_seconds{(start_rule_(y) - offset_).time_since_epoch()}; } inline date::sys_seconds time_zone::get_prev_start(date::year y) const { return date::sys_seconds{(start_rule_(--y) - offset_).time_since_epoch()}; } inline date::sys_seconds time_zone::get_next_start(date::year y) const { return date::sys_seconds{(start_rule_(++y) - offset_).time_since_epoch()}; } inline date::sys_seconds time_zone::get_end(date::year y) const { return date::sys_seconds{(end_rule_(y) - (offset_ + save_)).time_since_epoch()}; } inline date::sys_seconds time_zone::get_prev_end(date::year y) const { return date::sys_seconds{(end_rule_(--y) - (offset_ + save_)).time_since_epoch()}; } inline date::sys_seconds time_zone::get_next_end(date::year y) const { return date::sys_seconds{(end_rule_(++y) - (offset_ + save_)).time_since_epoch()}; } inline date::sys_info time_zone::contant_offset() const { using date::year; using date::sys_info; using date::sys_days; using date::January; using date::December; using date::last; using date::days; using std::chrono::minutes; sys_info r; r.begin = sys_days{year::min()/January/1}; r.end = sys_days{year::max()/December/last} + days{1} - std::chrono::seconds{1}; if (std_abbrev_.size() > 0) { r.abbrev = std_abbrev_; r.offset = offset_; r.save = {}; } else { r.abbrev = dst_abbrev_; r.offset = offset_ + save_; r.save = date::ceil(save_); } return r; } inline time_zone::time_zone(const detail::string_t& s) { using detail::read_name; using detail::read_signed_time; using detail::throw_invalid; auto i = read_name(s, 0, std_abbrev_); auto std_name_i = i; auto abbrev_name_i = i; i = read_signed_time(s, i, offset_); offset_ = -offset_; if (i != s.size()) { i = read_name(s, i, dst_abbrev_); abbrev_name_i = i; if (i != s.size()) { if (s[i] != ',') { i = read_signed_time(s, i, save_); save_ = -save_ - offset_; } if (i != s.size()) { if (s[i] != ',') throw_invalid(s, i, "Expecting end of string or ',' to start rule"); ++i; i = read_date(s, i, start_rule_); if (i == s.size() || s[i] != ',') throw_invalid(s, i, "Expecting ',' and then the ending rule"); ++i; i = read_date(s, i, end_rule_); if (i != s.size()) throw_invalid(s, i, "Found unexpected trailing characters"); } } } if (start_rule_.ok()) { if (std_abbrev_.size() < 3) throw_invalid(s, std_name_i, "Zone with rules must have a std" " abbreviation of length 3 or greater"); if (dst_abbrev_.size() < 3) throw_invalid(s, abbrev_name_i, "Zone with rules must have a daylight" " abbreviation of length 3 or greater"); } else { if (dst_abbrev_.size() >= 3) { std_abbrev_.clear(); } else if (std_abbrev_.size() < 3) { throw_invalid(s, std_name_i, "Zone must have at least one abbreviation" " of length 3 or greater"); } else { dst_abbrev_.clear(); save_ = {}; } } } template date::sys_info time_zone::get_info(date::sys_time st) const { using date::sys_info; using date::year_month_day; using date::sys_days; using date::floor; using date::ceil; using date::days; using date::year; using date::January; using date::December; using date::last; using std::chrono::minutes; sys_info r{}; r.offset = offset_; if (start_rule_.ok()) { auto y = year_month_day{floor(st)}.year(); if (st >= get_next_start(y)) ++y; else if (st < get_prev_end(y)) --y; auto start = get_start(y); auto end = get_end(y); if (start <= end) // (northern hemisphere) { if (start <= st && st < end) { r.begin = start; r.end = end; r.offset += save_; r.save = ceil(save_); r.abbrev = dst_abbrev_; } else if (st < start) { r.begin = get_prev_end(y); r.end = start; r.abbrev = std_abbrev_; } else // st >= end { r.begin = end; r.end = get_next_start(y); r.abbrev = std_abbrev_; } } else // end < start (southern hemisphere) { if (end <= st && st < start) { r.begin = end; r.end = start; r.abbrev = std_abbrev_; } else if (st < end) { r.begin = get_prev_start(y); r.end = end; r.offset += save_; r.save = ceil(save_); r.abbrev = dst_abbrev_; } else // st >= start { r.begin = start; r.end = get_next_end(y); r.offset += save_; r.save = ceil(save_); r.abbrev = dst_abbrev_; } } } else r = contant_offset(); using seconds = std::chrono::seconds; assert(r.begin <= floor(st) && floor(st) <= r.end); return r; } template date::local_info time_zone::get_info(date::local_time tp) const { using date::local_info; using date::year_month_day; using date::days; using date::sys_days; using date::sys_seconds; using date::year; using date::ceil; using date::January; using date::December; using date::last; using std::chrono::seconds; using std::chrono::minutes; local_info r{}; using date::floor; if (start_rule_.ok()) { auto y = year_month_day{floor(tp)}.year(); auto start = get_start(y); auto end = get_end(y); auto utcs = sys_seconds{floor(tp - offset_).time_since_epoch()}; auto utcd = sys_seconds{floor(tp - (offset_ + save_)).time_since_epoch()}; auto northern = start <= end; if ((utcs < start) != (utcd < start)) { if (northern) r.first.begin = get_prev_end(y); else r.first.begin = end; r.first.end = start; r.first.offset = offset_; r.first.abbrev = std_abbrev_; r.second.begin = start; if (northern) r.second.end = end; else r.second.end = get_next_end(y); r.second.abbrev = dst_abbrev_; r.second.offset = offset_ + save_; r.second.save = ceil(save_); r.result = save_ > seconds{0} ? local_info::nonexistent : local_info::ambiguous; } else if ((utcs < end) != (utcd < end)) { if (northern) r.first.begin = start; else r.first.begin = get_prev_start(y); r.first.end = end; r.first.offset = offset_ + save_; r.first.save = ceil(save_); r.first.abbrev = dst_abbrev_; r.second.begin = end; if (northern) r.second.end = get_next_start(y); else r.second.end = start; r.second.abbrev = std_abbrev_; r.second.offset = offset_; r.result = save_ > seconds{0} ? local_info::ambiguous : local_info::nonexistent; } else r.first = get_info(utcs); } else r.first = contant_offset(); return r; } template date::sys_time::type> time_zone::to_sys(date::local_time tp) const { using date::local_info; using date::sys_time; using date::ambiguous_local_time; using date::nonexistent_local_time; auto i = get_info(tp); if (i.result == local_info::nonexistent) throw nonexistent_local_time(tp, i); else if (i.result == local_info::ambiguous) throw ambiguous_local_time(tp, i); return sys_time{tp.time_since_epoch()} - i.first.offset; } template date::sys_time::type> time_zone::to_sys(date::local_time tp, date::choose z) const { using date::local_info; using date::sys_time; using date::choose; auto i = get_info(tp); if (i.result == local_info::nonexistent) { return i.first.end; } else if (i.result == local_info::ambiguous) { if (z == choose::latest) return sys_time{tp.time_since_epoch()} - i.second.offset; } return sys_time{tp.time_since_epoch()} - i.first.offset; } template date::local_time::type> time_zone::to_local(date::sys_time tp) const { using date::local_time; using std::chrono::seconds; using LT = local_time::type>; auto i = get_info(tp); return LT{(tp + i.offset).time_since_epoch()}; } inline std::ostream& operator<<(std::ostream& os, const time_zone& z) { using date::operator<<; os << '{'; os << z.std_abbrev_ << ", " << z.dst_abbrev_ << date::format(", %T, ", z.offset_) << date::format("%T, [", z.save_) << z.start_rule_ << ", " << z.end_rule_ << ")}"; return os; } inline std::string time_zone::name() const { using namespace date; using namespace std::chrono; auto print_abbrev = [](std::string const& nm) { if (std::any_of(nm.begin(), nm.end(), [](char c) { return !std::isalpha(c); })) { return '<' + nm + '>'; } return nm; }; auto print_offset = [](seconds off) { std::string nm; date::hh_mm_ss offset{-off}; if (offset.is_negative()) nm += '-'; nm += std::to_string(offset.hours().count()); if (offset.minutes() != minutes{0} || offset.seconds() != seconds{0}) { nm += ':'; if (offset.minutes() < minutes{10}) nm += '0'; nm += std::to_string(offset.minutes().count()); if (offset.seconds() != seconds{0}) { nm += ':'; if (offset.seconds() < seconds{10}) nm += '0'; nm += std::to_string(offset.seconds().count()); } } return nm; }; auto nm = print_abbrev(std_abbrev_); nm += print_offset(offset_); if (!dst_abbrev_.empty()) { nm += print_abbrev(dst_abbrev_); if (save_ != hours{1}) nm += print_offset(offset_+save_); if (start_rule_.ok()) { nm += ','; nm += start_rule_.to_string(); nm += ','; nm += end_rule_.to_string(); } } return nm; } inline bool operator==(const time_zone& x, const time_zone& y) { return x.std_abbrev_ == y.std_abbrev_ && x.dst_abbrev_ == y. dst_abbrev_ && x.offset_ == y.offset_ && x.save_ == y.save_ && x.start_rule_ == y.start_rule_ && x.end_rule_ == y.end_rule_; } inline bool operator!=(const time_zone& x, const time_zone& y) { return !(x == y); } namespace detail { inline void throw_invalid(const string_t& s, unsigned i, const string_t& message) { throw std::runtime_error(std::string("Invalid time_zone initializer.\n") + std::string(message) + ":\n" + std::string(s) + '\n' + "\x1b[1;32m" + std::string(i, '~') + '^' + std::string(i < s.size() ? s.size()-i-1 : 0, '~') + "\x1b[0m"); } inline unsigned read_date(const string_t& s, unsigned i, rule& r) { using date::month; using date::weekday; if (i == s.size()) throw_invalid(s, i, "Expected rule but found end of string"); if (s[i] == 'J') { ++i; unsigned n; i = read_unsigned(s, i, 3, n, "Expected to find the Julian day [1, 365]"); if (!(1 <= n && n <= 365)) throw_invalid(s, i-1, "Expected Julian day to be in the range [1, 365]"); r.mode_ = rule::J; r.n_ = n; } else if (s[i] == 'M') { ++i; unsigned m; i = read_unsigned(s, i, 2, m, "Expected to find month [1, 12]"); if (!(1 <= m && m <= 12)) throw_invalid(s, i-1, "Expected month to be in the range [1, 12]"); if (i == s.size() || s[i] != '.') throw_invalid(s, i, "Expected '.' after month"); ++i; unsigned n; i = read_unsigned(s, i, 1, n, "Expected to find week number [1, 5]"); if (!(1 <= n && n <= 5)) throw_invalid(s, i-1, "Expected week number to be in the range [1, 5]"); if (i == s.size() || s[i] != '.') throw_invalid(s, i, "Expected '.' after weekday index"); ++i; unsigned wd; i = read_unsigned(s, i, 1, wd, "Expected to find day of week [0, 6]"); if (wd > 6) throw_invalid(s, i-1, "Expected day of week to be in the range [0, 6]"); r.mode_ = rule::M; r.m_ = month{m}; r.wd_ = weekday{wd}; r.n_ = n; } else if (std::isdigit(s[i])) { unsigned n; i = read_unsigned(s, i, 3, n); if (n > 365) throw_invalid(s, i-1, "Expected Julian day to be in the range [0, 365]"); r.mode_ = rule::N; r.n_ = n; } else throw_invalid(s, i, "Expected 'J', 'M', or a digit to start rule"); if (i != s.size() && s[i] == '/') { ++i; std::chrono::seconds t; i = read_unsigned_time(s, i, t); r.time_ = t; } return i; } inline unsigned read_name(const string_t& s, unsigned i, std::string& name) { if (i == s.size()) throw_invalid(s, i, "Expected a name but found end of string"); if (s[i] == '<') { ++i; while (true) { if (i == s.size()) throw_invalid(s, i, "Expected to find closing '>', but found end of string"); if (s[i] == '>') break; name.push_back(s[i]); ++i; } ++i; } else { while (i != s.size() && std::isalpha(s[i])) { name.push_back(s[i]); ++i; } } return i; } inline unsigned read_signed_time(const string_t& s, unsigned i, std::chrono::seconds& t) { if (i == s.size()) throw_invalid(s, i, "Expected to read signed time, but found end of string"); bool negative = false; if (s[i] == '-') { negative = true; ++i; } else if (s[i] == '+') ++i; i = read_unsigned_time(s, i, t); if (negative) t = -t; return i; } inline unsigned read_unsigned_time(const string_t& s, unsigned i, std::chrono::seconds& t) { using std::chrono::seconds; using std::chrono::minutes; using std::chrono::hours; if (i == s.size()) throw_invalid(s, i, "Expected to read unsigned time, but found end of string"); unsigned x; i = read_unsigned(s, i, 2, x, "Expected to find hours [0, 24]"); if (x > 24) throw_invalid(s, i-1, "Expected hours to be in the range [0, 24]"); t = hours{x}; if (i != s.size() && s[i] == ':') { ++i; i = read_unsigned(s, i, 2, x, "Expected to find minutes [0, 59]"); if (x > 59) throw_invalid(s, i-1, "Expected minutes to be in the range [0, 59]"); t += minutes{x}; if (i != s.size() && s[i] == ':') { ++i; i = read_unsigned(s, i, 2, x, "Expected to find seconds [0, 59]"); if (x > 59) throw_invalid(s, i-1, "Expected seconds to be in the range [0, 59]"); t += seconds{x}; } } return i; } inline unsigned read_unsigned(const string_t& s, unsigned i, unsigned limit, unsigned& u, const string_t& message) { if (i == s.size() || !std::isdigit(s[i])) throw_invalid(s, i, message); u = static_cast(s[i] - '0'); unsigned count = 1; for (++i; count < limit && i != s.size() && std::isdigit(s[i]); ++i, ++count) u = u * 10 + static_cast(s[i] - '0'); return i; } } // namespace detail } // namespace Posix namespace date { template <> struct zoned_traits { #if HAS_STRING_VIEW static Posix::time_zone locate_zone(std::string_view name) { return Posix::time_zone{name}; } #else // !HAS_STRING_VIEW static Posix::time_zone locate_zone(const std::string& name) { return Posix::time_zone{name}; } static Posix::time_zone locate_zone(const char* name) { return Posix::time_zone{name}; } #endif // !HAS_STRING_VIEW }; } // namespace date #endif // PTZ_H pr0m1th3as-datatypes-9c9a8d3/src/date/solar_hijri.h000066400000000000000000002316041522766574100222450ustar00rootroot00000000000000#ifndef SOLAR_HIJRI_H #define SOLAR_HIJRI_H // The MIT License (MIT) // // Copyright (c) 2016 Howard Hinnant // Copyright (c) 2019 Asad. Gharighi // // Calculations are based on: // https://www.timeanddate.com/calendar/persian-calendar.html // and follow style // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. // // Our apologies. When the previous paragraph was written, lowercase had not yet // been invented (that would involve another several millennia of evolution). // We did not mean to shout. #include "date.h" namespace solar_hijri { namespace internal { static const auto epoch = static_cast(2121446); static const auto days_in_era = static_cast(1029983); static const auto years_in_era = static_cast(2820); static const auto unix_time_shift = static_cast(2440588); auto const years_in_first_cycle = static_cast(29); auto const years_in_other_cycles = static_cast(33); auto const years_in_period = static_cast(128); // 29 + 3*33 auto const days_in_first_cycle = static_cast(10592); // 28/4 + 29*365 auto const days_in_other_cycles = static_cast(12053); // 32/4 + 33*365 auto const days_in_period = static_cast(46751); // days_in_first_cycle + 3*days_in_other_cycles; } // durations using days = date::days; using weeks = date::weeks; using years = std::chrono::duration , days::period>>; using months = std::chrono::duration >>; // time_point using sys_days = date::sys_days; using local_days = date::local_days; // types struct last_spec { explicit last_spec() = default; }; class day; class month; class year; class weekday; class weekday_indexed; class weekday_last; class month_day; class month_day_last; class month_weekday; class month_weekday_last; class year_month; class year_month_day; class year_month_day_last; class year_month_weekday; class year_month_weekday_last; // date composition operators CONSTCD11 year_month operator/(const year& y, const month& m) NOEXCEPT; CONSTCD11 year_month operator/(const year& y, int m) NOEXCEPT; CONSTCD11 month_day operator/(const day& d, const month& m) NOEXCEPT; CONSTCD11 month_day operator/(const day& d, int m) NOEXCEPT; CONSTCD11 month_day operator/(const month& m, const day& d) NOEXCEPT; CONSTCD11 month_day operator/(const month& m, int d) NOEXCEPT; CONSTCD11 month_day operator/(int m, const day& d) NOEXCEPT; CONSTCD11 month_day_last operator/(const month& m, last_spec) NOEXCEPT; CONSTCD11 month_day_last operator/(int m, last_spec) NOEXCEPT; CONSTCD11 month_day_last operator/(last_spec, const month& m) NOEXCEPT; CONSTCD11 month_day_last operator/(last_spec, int m) NOEXCEPT; CONSTCD11 month_weekday operator/(const month& m, const weekday_indexed& wdi) NOEXCEPT; CONSTCD11 month_weekday operator/(int m, const weekday_indexed& wdi) NOEXCEPT; CONSTCD11 month_weekday operator/(const weekday_indexed& wdi, const month& m) NOEXCEPT; CONSTCD11 month_weekday operator/(const weekday_indexed& wdi, int m) NOEXCEPT; CONSTCD11 month_weekday_last operator/(const month& m, const weekday_last& wdl) NOEXCEPT; CONSTCD11 month_weekday_last operator/(int m, const weekday_last& wdl) NOEXCEPT; CONSTCD11 month_weekday_last operator/(const weekday_last& wdl, const month& m) NOEXCEPT; CONSTCD11 month_weekday_last operator/(const weekday_last& wdl, int m) NOEXCEPT; CONSTCD11 year_month_day operator/(const year_month& ym, const day& d) NOEXCEPT; CONSTCD11 year_month_day operator/(const year_month& ym, int d) NOEXCEPT; CONSTCD11 year_month_day operator/(const year& y, const month_day& md) NOEXCEPT; CONSTCD11 year_month_day operator/(int y, const month_day& md) NOEXCEPT; CONSTCD11 year_month_day operator/(const month_day& md, const year& y) NOEXCEPT; CONSTCD11 year_month_day operator/(const month_day& md, int y) NOEXCEPT; CONSTCD11 year_month_day_last operator/(const year_month& ym, last_spec) NOEXCEPT; CONSTCD11 year_month_day_last operator/(const year& y, const month_day_last& mdl) NOEXCEPT; CONSTCD11 year_month_day_last operator/(int y, const month_day_last& mdl) NOEXCEPT; CONSTCD11 year_month_day_last operator/(const month_day_last& mdl, const year& y) NOEXCEPT; CONSTCD11 year_month_day_last operator/(const month_day_last& mdl, int y) NOEXCEPT; CONSTCD11 year_month_weekday operator/(const year_month& ym, const weekday_indexed& wdi) NOEXCEPT; CONSTCD11 year_month_weekday operator/(const year& y, const month_weekday& mwd) NOEXCEPT; CONSTCD11 year_month_weekday operator/(int y, const month_weekday& mwd) NOEXCEPT; CONSTCD11 year_month_weekday operator/(const month_weekday& mwd, const year& y) NOEXCEPT; CONSTCD11 year_month_weekday operator/(const month_weekday& mwd, int y) NOEXCEPT; CONSTCD11 year_month_weekday_last operator/(const year_month& ym, const weekday_last& wdl) NOEXCEPT; CONSTCD11 year_month_weekday_last operator/(const year& y, const month_weekday_last& mwdl) NOEXCEPT; CONSTCD11 year_month_weekday_last operator/(int y, const month_weekday_last& mwdl) NOEXCEPT; CONSTCD11 year_month_weekday_last operator/(const month_weekday_last& mwdl, const year& y) NOEXCEPT; CONSTCD11 year_month_weekday_last operator/(const month_weekday_last& mwdl, int y) NOEXCEPT; // Detailed interface // day class day { unsigned char d_; public: day() = default; explicit CONSTCD11 day(unsigned d) NOEXCEPT; CONSTCD14 day& operator++() NOEXCEPT; CONSTCD14 day operator++(int) NOEXCEPT; CONSTCD14 day& operator--() NOEXCEPT; CONSTCD14 day operator--(int) NOEXCEPT; CONSTCD14 day& operator+=(const days& d) NOEXCEPT; CONSTCD14 day& operator-=(const days& d) NOEXCEPT; CONSTCD11 explicit operator unsigned() const NOEXCEPT; CONSTCD11 bool ok() const NOEXCEPT; }; CONSTCD11 bool operator==(const day& x, const day& y) NOEXCEPT; CONSTCD11 bool operator!=(const day& x, const day& y) NOEXCEPT; CONSTCD11 bool operator< (const day& x, const day& y) NOEXCEPT; CONSTCD11 bool operator> (const day& x, const day& y) NOEXCEPT; CONSTCD11 bool operator<=(const day& x, const day& y) NOEXCEPT; CONSTCD11 bool operator>=(const day& x, const day& y) NOEXCEPT; CONSTCD11 day operator+(const day& x, const days& y) NOEXCEPT; CONSTCD11 day operator+(const days& x, const day& y) NOEXCEPT; CONSTCD11 day operator-(const day& x, const days& y) NOEXCEPT; CONSTCD11 days operator-(const day& x, const day& y) NOEXCEPT; template std::basic_ostream& operator<<(std::basic_ostream& os, const day& d); // month class month { unsigned char m_; public: month() = default; explicit CONSTCD11 month(unsigned m) NOEXCEPT; CONSTCD14 month& operator++() NOEXCEPT; CONSTCD14 month operator++(int) NOEXCEPT; CONSTCD14 month& operator--() NOEXCEPT; CONSTCD14 month operator--(int) NOEXCEPT; CONSTCD14 month& operator+=(const months& m) NOEXCEPT; CONSTCD14 month& operator-=(const months& m) NOEXCEPT; CONSTCD11 explicit operator unsigned() const NOEXCEPT; CONSTCD11 bool ok() const NOEXCEPT; }; CONSTCD11 bool operator==(const month& x, const month& y) NOEXCEPT; CONSTCD11 bool operator!=(const month& x, const month& y) NOEXCEPT; CONSTCD11 bool operator< (const month& x, const month& y) NOEXCEPT; CONSTCD11 bool operator> (const month& x, const month& y) NOEXCEPT; CONSTCD11 bool operator<=(const month& x, const month& y) NOEXCEPT; CONSTCD11 bool operator>=(const month& x, const month& y) NOEXCEPT; CONSTCD14 month operator+(const month& x, const months& y) NOEXCEPT; CONSTCD14 month operator+(const months& x, const month& y) NOEXCEPT; CONSTCD14 month operator-(const month& x, const months& y) NOEXCEPT; CONSTCD14 months operator-(const month& x, const month& y) NOEXCEPT; template std::basic_ostream& operator<<(std::basic_ostream& os, const month& m); // year class year { short y_; public: year() = default; explicit CONSTCD11 year(int y) NOEXCEPT; CONSTCD14 year& operator++() NOEXCEPT; CONSTCD14 year operator++(int) NOEXCEPT; CONSTCD14 year& operator--() NOEXCEPT; CONSTCD14 year operator--(int) NOEXCEPT; CONSTCD14 year& operator+=(const years& y) NOEXCEPT; CONSTCD14 year& operator-=(const years& y) NOEXCEPT; CONSTCD14 bool is_leap() const NOEXCEPT; CONSTCD11 explicit operator int() const NOEXCEPT; CONSTCD11 bool ok() const NOEXCEPT; static CONSTCD11 year min() NOEXCEPT; static CONSTCD11 year max() NOEXCEPT; }; CONSTCD11 bool operator==(const year& x, const year& y) NOEXCEPT; CONSTCD11 bool operator!=(const year& x, const year& y) NOEXCEPT; CONSTCD11 bool operator< (const year& x, const year& y) NOEXCEPT; CONSTCD11 bool operator> (const year& x, const year& y) NOEXCEPT; CONSTCD11 bool operator<=(const year& x, const year& y) NOEXCEPT; CONSTCD11 bool operator>=(const year& x, const year& y) NOEXCEPT; CONSTCD11 year operator+(const year& x, const years& y) NOEXCEPT; CONSTCD11 year operator+(const years& x, const year& y) NOEXCEPT; CONSTCD11 year operator-(const year& x, const years& y) NOEXCEPT; CONSTCD11 years operator-(const year& x, const year& y) NOEXCEPT; template std::basic_ostream& operator<<(std::basic_ostream& os, const year& y); // weekday class weekday { unsigned char wd_; public: weekday() = default; explicit CONSTCD11 weekday(unsigned wd) NOEXCEPT; explicit weekday(int) = delete; CONSTCD11 weekday(const sys_days& dp) NOEXCEPT; CONSTCD11 explicit weekday(const local_days& dp) NOEXCEPT; CONSTCD14 weekday& operator++() NOEXCEPT; CONSTCD14 weekday operator++(int) NOEXCEPT; CONSTCD14 weekday& operator--() NOEXCEPT; CONSTCD14 weekday operator--(int) NOEXCEPT; CONSTCD14 weekday& operator+=(const days& d) NOEXCEPT; CONSTCD14 weekday& operator-=(const days& d) NOEXCEPT; CONSTCD11 explicit operator unsigned() const NOEXCEPT; CONSTCD11 bool ok() const NOEXCEPT; CONSTCD11 weekday_indexed operator[](unsigned index) const NOEXCEPT; CONSTCD11 weekday_last operator[](last_spec) const NOEXCEPT; private: static CONSTCD11 unsigned char weekday_from_days(int z) NOEXCEPT; }; CONSTCD11 bool operator==(const weekday& x, const weekday& y) NOEXCEPT; CONSTCD11 bool operator!=(const weekday& x, const weekday& y) NOEXCEPT; CONSTCD14 weekday operator+(const weekday& x, const days& y) NOEXCEPT; CONSTCD14 weekday operator+(const days& x, const weekday& y) NOEXCEPT; CONSTCD14 weekday operator-(const weekday& x, const days& y) NOEXCEPT; CONSTCD14 days operator-(const weekday& x, const weekday& y) NOEXCEPT; template std::basic_ostream& operator<<(std::basic_ostream& os, const weekday& wd); // weekday_indexed class weekday_indexed { unsigned char wd_ : 4; unsigned char index_ : 4; public: weekday_indexed() = default; CONSTCD11 weekday_indexed(const solar_hijri::weekday& wd, unsigned index) NOEXCEPT; CONSTCD11 solar_hijri::weekday weekday() const NOEXCEPT; CONSTCD11 unsigned index() const NOEXCEPT; CONSTCD11 bool ok() const NOEXCEPT; }; CONSTCD11 bool operator==(const weekday_indexed& x, const weekday_indexed& y) NOEXCEPT; CONSTCD11 bool operator!=(const weekday_indexed& x, const weekday_indexed& y) NOEXCEPT; template std::basic_ostream& operator<<(std::basic_ostream& os, const weekday_indexed& wdi); // weekday_last class weekday_last { solar_hijri::weekday wd_; public: weekday_last() = default; explicit CONSTCD11 weekday_last(const solar_hijri::weekday& wd) NOEXCEPT; CONSTCD11 solar_hijri::weekday weekday() const NOEXCEPT; CONSTCD11 bool ok() const NOEXCEPT; }; CONSTCD11 bool operator==(const weekday_last& x, const weekday_last& y) NOEXCEPT; CONSTCD11 bool operator!=(const weekday_last& x, const weekday_last& y) NOEXCEPT; template std::basic_ostream& operator<<(std::basic_ostream& os, const weekday_last& wdl); // year_month class year_month { solar_hijri::year y_; solar_hijri::month m_; public: year_month() = default; CONSTCD11 year_month(const solar_hijri::year& y, const solar_hijri::month& m) NOEXCEPT; CONSTCD11 solar_hijri::year year() const NOEXCEPT; CONSTCD11 solar_hijri::month month() const NOEXCEPT; CONSTCD14 year_month& operator+=(const months& dm) NOEXCEPT; CONSTCD14 year_month& operator-=(const months& dm) NOEXCEPT; CONSTCD14 year_month& operator+=(const years& dy) NOEXCEPT; CONSTCD14 year_month& operator-=(const years& dy) NOEXCEPT; CONSTCD11 bool ok() const NOEXCEPT; }; CONSTCD11 bool operator==(const year_month& x, const year_month& y) NOEXCEPT; CONSTCD11 bool operator!=(const year_month& x, const year_month& y) NOEXCEPT; CONSTCD11 bool operator< (const year_month& x, const year_month& y) NOEXCEPT; CONSTCD11 bool operator> (const year_month& x, const year_month& y) NOEXCEPT; CONSTCD11 bool operator<=(const year_month& x, const year_month& y) NOEXCEPT; CONSTCD11 bool operator>=(const year_month& x, const year_month& y) NOEXCEPT; CONSTCD14 year_month operator+(const year_month& ym, const months& dm) NOEXCEPT; CONSTCD14 year_month operator+(const months& dm, const year_month& ym) NOEXCEPT; CONSTCD14 year_month operator-(const year_month& ym, const months& dm) NOEXCEPT; CONSTCD11 months operator-(const year_month& x, const year_month& y) NOEXCEPT; CONSTCD11 year_month operator+(const year_month& ym, const years& dy) NOEXCEPT; CONSTCD11 year_month operator+(const years& dy, const year_month& ym) NOEXCEPT; CONSTCD11 year_month operator-(const year_month& ym, const years& dy) NOEXCEPT; template std::basic_ostream& operator<<(std::basic_ostream& os, const year_month& ym); // month_day class month_day { solar_hijri::month m_; solar_hijri::day d_; public: month_day() = default; CONSTCD11 month_day(const solar_hijri::month& m, const solar_hijri::day& d) NOEXCEPT; CONSTCD11 solar_hijri::month month() const NOEXCEPT; CONSTCD11 solar_hijri::day day() const NOEXCEPT; CONSTCD14 bool ok() const NOEXCEPT; }; CONSTCD11 bool operator==(const month_day& x, const month_day& y) NOEXCEPT; CONSTCD11 bool operator!=(const month_day& x, const month_day& y) NOEXCEPT; CONSTCD11 bool operator< (const month_day& x, const month_day& y) NOEXCEPT; CONSTCD11 bool operator> (const month_day& x, const month_day& y) NOEXCEPT; CONSTCD11 bool operator<=(const month_day& x, const month_day& y) NOEXCEPT; CONSTCD11 bool operator>=(const month_day& x, const month_day& y) NOEXCEPT; template std::basic_ostream& operator<<(std::basic_ostream& os, const month_day& md); // month_day_last class month_day_last { solar_hijri::month m_; public: month_day_last() = default; CONSTCD11 explicit month_day_last(const solar_hijri::month& m) NOEXCEPT; CONSTCD11 solar_hijri::month month() const NOEXCEPT; CONSTCD11 bool ok() const NOEXCEPT; }; CONSTCD11 bool operator==(const month_day_last& x, const month_day_last& y) NOEXCEPT; CONSTCD11 bool operator!=(const month_day_last& x, const month_day_last& y) NOEXCEPT; CONSTCD11 bool operator< (const month_day_last& x, const month_day_last& y) NOEXCEPT; CONSTCD11 bool operator> (const month_day_last& x, const month_day_last& y) NOEXCEPT; CONSTCD11 bool operator<=(const month_day_last& x, const month_day_last& y) NOEXCEPT; CONSTCD11 bool operator>=(const month_day_last& x, const month_day_last& y) NOEXCEPT; template std::basic_ostream& operator<<(std::basic_ostream& os, const month_day_last& mdl); // month_weekday class month_weekday { solar_hijri::month m_; solar_hijri::weekday_indexed wdi_; public: month_weekday() = default; CONSTCD11 month_weekday(const solar_hijri::month& m, const solar_hijri::weekday_indexed& wdi) NOEXCEPT; CONSTCD11 solar_hijri::month month() const NOEXCEPT; CONSTCD11 solar_hijri::weekday_indexed weekday_indexed() const NOEXCEPT; CONSTCD11 bool ok() const NOEXCEPT; }; CONSTCD11 bool operator==(const month_weekday& x, const month_weekday& y) NOEXCEPT; CONSTCD11 bool operator!=(const month_weekday& x, const month_weekday& y) NOEXCEPT; template std::basic_ostream& operator<<(std::basic_ostream& os, const month_weekday& mwd); // month_weekday_last class month_weekday_last { solar_hijri::month m_; solar_hijri::weekday_last wdl_; public: month_weekday_last() = default; CONSTCD11 month_weekday_last(const solar_hijri::month& m, const solar_hijri::weekday_last& wd) NOEXCEPT; CONSTCD11 solar_hijri::month month() const NOEXCEPT; CONSTCD11 solar_hijri::weekday_last weekday_last() const NOEXCEPT; CONSTCD11 bool ok() const NOEXCEPT; }; CONSTCD11 bool operator==(const month_weekday_last& x, const month_weekday_last& y) NOEXCEPT; CONSTCD11 bool operator!=(const month_weekday_last& x, const month_weekday_last& y) NOEXCEPT; template std::basic_ostream& operator<<(std::basic_ostream& os, const month_weekday_last& mwdl); // class year_month_day class year_month_day { solar_hijri::year y_; solar_hijri::month m_; solar_hijri::day d_; public: year_month_day() = default; CONSTCD11 year_month_day(const solar_hijri::year& y, const solar_hijri::month& m, const solar_hijri::day& d) NOEXCEPT; CONSTCD14 year_month_day(const year_month_day_last& ymdl) NOEXCEPT; CONSTCD14 year_month_day(sys_days dp) NOEXCEPT; CONSTCD14 explicit year_month_day(local_days dp) NOEXCEPT; CONSTCD14 year_month_day& operator+=(const months& m) NOEXCEPT; CONSTCD14 year_month_day& operator-=(const months& m) NOEXCEPT; CONSTCD14 year_month_day& operator+=(const years& y) NOEXCEPT; CONSTCD14 year_month_day& operator-=(const years& y) NOEXCEPT; CONSTCD11 solar_hijri::year year() const NOEXCEPT; CONSTCD11 solar_hijri::month month() const NOEXCEPT; CONSTCD11 solar_hijri::day day() const NOEXCEPT; CONSTCD14 operator sys_days() const NOEXCEPT; CONSTCD14 explicit operator local_days() const NOEXCEPT; CONSTCD14 bool ok() const NOEXCEPT; private: static CONSTCD14 year_month_day from_days(days dp) NOEXCEPT; CONSTCD14 days to_days() const NOEXCEPT; }; CONSTCD11 bool operator==(const year_month_day& x, const year_month_day& y) NOEXCEPT; CONSTCD11 bool operator!=(const year_month_day& x, const year_month_day& y) NOEXCEPT; CONSTCD11 bool operator< (const year_month_day& x, const year_month_day& y) NOEXCEPT; CONSTCD11 bool operator> (const year_month_day& x, const year_month_day& y) NOEXCEPT; CONSTCD11 bool operator<=(const year_month_day& x, const year_month_day& y) NOEXCEPT; CONSTCD11 bool operator>=(const year_month_day& x, const year_month_day& y) NOEXCEPT; CONSTCD14 year_month_day operator+(const year_month_day& ymd, const months& dm) NOEXCEPT; CONSTCD14 year_month_day operator+(const months& dm, const year_month_day& ymd) NOEXCEPT; CONSTCD14 year_month_day operator-(const year_month_day& ymd, const months& dm) NOEXCEPT; CONSTCD11 year_month_day operator+(const year_month_day& ymd, const years& dy) NOEXCEPT; CONSTCD11 year_month_day operator+(const years& dy, const year_month_day& ymd) NOEXCEPT; CONSTCD11 year_month_day operator-(const year_month_day& ymd, const years& dy) NOEXCEPT; template std::basic_ostream& operator<<(std::basic_ostream& os, const year_month_day& ymd); // year_month_day_last class year_month_day_last { solar_hijri::year y_; solar_hijri::month_day_last mdl_; public: year_month_day_last() = default; CONSTCD11 year_month_day_last(const solar_hijri::year& y, const solar_hijri::month_day_last& mdl) NOEXCEPT; CONSTCD14 year_month_day_last& operator+=(const months& m) NOEXCEPT; CONSTCD14 year_month_day_last& operator-=(const months& m) NOEXCEPT; CONSTCD14 year_month_day_last& operator+=(const years& y) NOEXCEPT; CONSTCD14 year_month_day_last& operator-=(const years& y) NOEXCEPT; CONSTCD11 solar_hijri::year year() const NOEXCEPT; CONSTCD11 solar_hijri::month month() const NOEXCEPT; CONSTCD11 solar_hijri::month_day_last month_day_last() const NOEXCEPT; CONSTCD14 solar_hijri::day day() const NOEXCEPT; CONSTCD14 operator sys_days() const NOEXCEPT; CONSTCD14 explicit operator local_days() const NOEXCEPT; CONSTCD11 bool ok() const NOEXCEPT; }; CONSTCD11 bool operator==(const year_month_day_last& x, const year_month_day_last& y) NOEXCEPT; CONSTCD11 bool operator!=(const year_month_day_last& x, const year_month_day_last& y) NOEXCEPT; CONSTCD11 bool operator< (const year_month_day_last& x, const year_month_day_last& y) NOEXCEPT; CONSTCD11 bool operator> (const year_month_day_last& x, const year_month_day_last& y) NOEXCEPT; CONSTCD11 bool operator<=(const year_month_day_last& x, const year_month_day_last& y) NOEXCEPT; CONSTCD11 bool operator>=(const year_month_day_last& x, const year_month_day_last& y) NOEXCEPT; CONSTCD14 year_month_day_last operator+(const year_month_day_last& ymdl, const months& dm) NOEXCEPT; CONSTCD14 year_month_day_last operator+(const months& dm, const year_month_day_last& ymdl) NOEXCEPT; CONSTCD11 year_month_day_last operator+(const year_month_day_last& ymdl, const years& dy) NOEXCEPT; CONSTCD11 year_month_day_last operator+(const years& dy, const year_month_day_last& ymdl) NOEXCEPT; CONSTCD14 year_month_day_last operator-(const year_month_day_last& ymdl, const months& dm) NOEXCEPT; CONSTCD11 year_month_day_last operator-(const year_month_day_last& ymdl, const years& dy) NOEXCEPT; template std::basic_ostream& operator<<(std::basic_ostream& os, const year_month_day_last& ymdl); // year_month_weekday class year_month_weekday { solar_hijri::year y_; solar_hijri::month m_; solar_hijri::weekday_indexed wdi_; public: year_month_weekday() = default; CONSTCD11 year_month_weekday(const solar_hijri::year& y, const solar_hijri::month& m, const solar_hijri::weekday_indexed& wdi) NOEXCEPT; CONSTCD14 year_month_weekday(const sys_days& dp) NOEXCEPT; CONSTCD14 explicit year_month_weekday(const local_days& dp) NOEXCEPT; CONSTCD14 year_month_weekday& operator+=(const months& m) NOEXCEPT; CONSTCD14 year_month_weekday& operator-=(const months& m) NOEXCEPT; CONSTCD14 year_month_weekday& operator+=(const years& y) NOEXCEPT; CONSTCD14 year_month_weekday& operator-=(const years& y) NOEXCEPT; CONSTCD11 solar_hijri::year year() const NOEXCEPT; CONSTCD11 solar_hijri::month month() const NOEXCEPT; CONSTCD11 solar_hijri::weekday weekday() const NOEXCEPT; CONSTCD11 unsigned index() const NOEXCEPT; CONSTCD11 solar_hijri::weekday_indexed weekday_indexed() const NOEXCEPT; CONSTCD14 operator sys_days() const NOEXCEPT; CONSTCD14 explicit operator local_days() const NOEXCEPT; CONSTCD14 bool ok() const NOEXCEPT; private: static CONSTCD14 year_month_weekday from_days(days dp) NOEXCEPT; CONSTCD14 days to_days() const NOEXCEPT; }; CONSTCD11 bool operator==(const year_month_weekday& x, const year_month_weekday& y) NOEXCEPT; CONSTCD11 bool operator!=(const year_month_weekday& x, const year_month_weekday& y) NOEXCEPT; CONSTCD14 year_month_weekday operator+(const year_month_weekday& ymwd, const months& dm) NOEXCEPT; CONSTCD14 year_month_weekday operator+(const months& dm, const year_month_weekday& ymwd) NOEXCEPT; CONSTCD11 year_month_weekday operator+(const year_month_weekday& ymwd, const years& dy) NOEXCEPT; CONSTCD11 year_month_weekday operator+(const years& dy, const year_month_weekday& ymwd) NOEXCEPT; CONSTCD14 year_month_weekday operator-(const year_month_weekday& ymwd, const months& dm) NOEXCEPT; CONSTCD11 year_month_weekday operator-(const year_month_weekday& ymwd, const years& dy) NOEXCEPT; template std::basic_ostream& operator<<(std::basic_ostream& os, const year_month_weekday& ymwdi); // year_month_weekday_last class year_month_weekday_last { solar_hijri::year y_; solar_hijri::month m_; solar_hijri::weekday_last wdl_; public: year_month_weekday_last() = default; CONSTCD11 year_month_weekday_last(const solar_hijri::year& y, const solar_hijri::month& m, const solar_hijri::weekday_last& wdl) NOEXCEPT; CONSTCD14 year_month_weekday_last& operator+=(const months& m) NOEXCEPT; CONSTCD14 year_month_weekday_last& operator-=(const months& m) NOEXCEPT; CONSTCD14 year_month_weekday_last& operator+=(const years& y) NOEXCEPT; CONSTCD14 year_month_weekday_last& operator-=(const years& y) NOEXCEPT; CONSTCD11 solar_hijri::year year() const NOEXCEPT; CONSTCD11 solar_hijri::month month() const NOEXCEPT; CONSTCD11 solar_hijri::weekday weekday() const NOEXCEPT; CONSTCD11 solar_hijri::weekday_last weekday_last() const NOEXCEPT; CONSTCD14 operator sys_days() const NOEXCEPT; CONSTCD14 explicit operator local_days() const NOEXCEPT; CONSTCD11 bool ok() const NOEXCEPT; private: CONSTCD14 days to_days() const NOEXCEPT; }; CONSTCD11 bool operator==(const year_month_weekday_last& x, const year_month_weekday_last& y) NOEXCEPT; CONSTCD11 bool operator!=(const year_month_weekday_last& x, const year_month_weekday_last& y) NOEXCEPT; CONSTCD14 year_month_weekday_last operator+(const year_month_weekday_last& ymwdl, const months& dm) NOEXCEPT; CONSTCD14 year_month_weekday_last operator+(const months& dm, const year_month_weekday_last& ymwdl) NOEXCEPT; CONSTCD11 year_month_weekday_last operator+(const year_month_weekday_last& ymwdl, const years& dy) NOEXCEPT; CONSTCD11 year_month_weekday_last operator+(const years& dy, const year_month_weekday_last& ymwdl) NOEXCEPT; CONSTCD14 year_month_weekday_last operator-(const year_month_weekday_last& ymwdl, const months& dm) NOEXCEPT; CONSTCD11 year_month_weekday_last operator-(const year_month_weekday_last& ymwdl, const years& dy) NOEXCEPT; template std::basic_ostream& operator<<(std::basic_ostream& os, const year_month_weekday_last& ymwdl); #if !defined(_MSC_VER) || (_MSC_VER >= 1900) inline namespace literals { CONSTCD11 solar_hijri::day operator ""_d(unsigned long long d) NOEXCEPT; CONSTCD11 solar_hijri::year operator ""_y(unsigned long long y) NOEXCEPT; } // inline namespace literals #endif // !defined(_MSC_VER) || (_MSC_VER >= 1900) //----------------+ // Implementation | //----------------+ // day CONSTCD11 inline day::day(unsigned d) NOEXCEPT : d_(static_cast(d)) {} CONSTCD14 inline day& day::operator++() NOEXCEPT {++d_; return *this;} CONSTCD14 inline day day::operator++(int) NOEXCEPT {auto tmp(*this); ++(*this); return tmp;} CONSTCD14 inline day& day::operator--() NOEXCEPT {--d_; return *this;} CONSTCD14 inline day day::operator--(int) NOEXCEPT {auto tmp(*this); --(*this); return tmp;} CONSTCD14 inline day& day::operator+=(const days& d) NOEXCEPT {*this = *this + d; return *this;} CONSTCD14 inline day& day::operator-=(const days& d) NOEXCEPT {*this = *this - d; return *this;} CONSTCD11 inline day::operator unsigned() const NOEXCEPT {return d_;} CONSTCD11 inline bool day::ok() const NOEXCEPT {return 1 <= d_ && d_ <= 30;} CONSTCD11 inline bool operator==(const day& x, const day& y) NOEXCEPT { return static_cast(x) == static_cast(y); } CONSTCD11 inline bool operator!=(const day& x, const day& y) NOEXCEPT { return !(x == y); } CONSTCD11 inline bool operator<(const day& x, const day& y) NOEXCEPT { return static_cast(x) < static_cast(y); } CONSTCD11 inline bool operator>(const day& x, const day& y) NOEXCEPT { return y < x; } CONSTCD11 inline bool operator<=(const day& x, const day& y) NOEXCEPT { return !(y < x); } CONSTCD11 inline bool operator>=(const day& x, const day& y) NOEXCEPT { return !(x < y); } CONSTCD11 inline days operator-(const day& x, const day& y) NOEXCEPT { return days{static_cast(static_cast(x) - static_cast(y))}; } CONSTCD11 inline day operator+(const day& x, const days& y) NOEXCEPT { return day{static_cast(x) + static_cast(y.count())}; } CONSTCD11 inline day operator+(const days& x, const day& y) NOEXCEPT { return y + x; } CONSTCD11 inline day operator-(const day& x, const days& y) NOEXCEPT { return x + -y; } template inline std::basic_ostream& operator<<(std::basic_ostream& os, const day& d) { date::detail::save_ostream _(os); os.fill('0'); os.flags(std::ios::dec | std::ios::right); os.width(2); os << static_cast(d); return os; } // month CONSTCD11 inline month::month(unsigned m) NOEXCEPT : m_(static_cast(m)) {} CONSTCD14 inline month& month::operator++() NOEXCEPT {if (++m_ == 13) m_ = 1; return *this;} CONSTCD14 inline month month::operator++(int) NOEXCEPT {auto tmp(*this); ++(*this); return tmp;} CONSTCD14 inline month& month::operator--() NOEXCEPT {if (--m_ == 0) m_ = 12; return *this;} CONSTCD14 inline month month::operator--(int) NOEXCEPT {auto tmp(*this); --(*this); return tmp;} CONSTCD14 inline month& month::operator+=(const months& m) NOEXCEPT { *this = *this + m; return *this; } CONSTCD14 inline month& month::operator-=(const months& m) NOEXCEPT { *this = *this - m; return *this; } CONSTCD11 inline month::operator unsigned() const NOEXCEPT {return m_;} CONSTCD11 inline bool month::ok() const NOEXCEPT {return 1 <= m_ && m_ <= 12;} CONSTCD11 inline bool operator==(const month& x, const month& y) NOEXCEPT { return static_cast(x) == static_cast(y); } CONSTCD11 inline bool operator!=(const month& x, const month& y) NOEXCEPT { return !(x == y); } CONSTCD11 inline bool operator<(const month& x, const month& y) NOEXCEPT { return static_cast(x) < static_cast(y); } CONSTCD11 inline bool operator>(const month& x, const month& y) NOEXCEPT { return y < x; } CONSTCD11 inline bool operator<=(const month& x, const month& y) NOEXCEPT { return !(y < x); } CONSTCD11 inline bool operator>=(const month& x, const month& y) NOEXCEPT { return !(x < y); } CONSTCD14 inline months operator-(const month& x, const month& y) NOEXCEPT { auto const d = static_cast(x) - static_cast(y); return months(d <= 11 ? d : d + 12); } CONSTCD14 inline month operator+(const month& x, const months& y) NOEXCEPT { auto const mu = static_cast(static_cast(x)) - 1 + y.count(); auto const yr = (mu >= 0 ? mu : mu-11) / 12; return month{static_cast(mu - yr * 12 + 1)}; } CONSTCD14 inline month operator+(const months& x, const month& y) NOEXCEPT { return y + x; } CONSTCD14 inline month operator-(const month& x, const months& y) NOEXCEPT { return x + -y; } template inline std::basic_ostream& operator<<(std::basic_ostream& os, const month& m) { switch (static_cast(m)) { case 1: os << "Farvardin"; break; case 2: os << "Ordibehesht"; break; case 3: os << "Khordad"; break; case 4: os << "Tir"; break; case 5: os << "Mordad"; break; case 6: os << "Shahrivar"; break; case 7: os << "Mehr"; break; case 8: os << "Aban"; break; case 9: os << "Azar"; break; case 10: os << "Dey"; break; case 11: os << "Bahman"; break; case 12: os << "Esfand"; break; default: os << static_cast(m) << " is not a valid month"; break; } return os; } // year CONSTCD11 inline year::year(int y) NOEXCEPT : y_(static_cast(y)) {} CONSTCD14 inline year& year::operator++() NOEXCEPT {++y_; return *this;} CONSTCD14 inline year year::operator++(int) NOEXCEPT {auto tmp(*this); ++(*this); return tmp;} CONSTCD14 inline year& year::operator--() NOEXCEPT {--y_; return *this;} CONSTCD14 inline year year::operator--(int) NOEXCEPT {auto tmp(*this); --(*this); return tmp;} CONSTCD14 inline year& year::operator+=(const years& y) NOEXCEPT {*this = *this + y; return *this;} CONSTCD14 inline year& year::operator-=(const years& y) NOEXCEPT {*this = *this - y; return *this;} CONSTCD14 inline bool year::is_leap() const NOEXCEPT { using namespace internal; auto const y = static_cast(y_)-475; auto const era_d = static_cast(y >= 0 ? y : y-years_in_era+1) / static_cast(years_in_era); auto const era = static_cast(era_d); auto const yoe = static_cast(y - era * years_in_era); // Reference: https://www.timeanddate.com/date/iran-leap-year.html // 29 + 33 + 33 + 33 = 128 // 22 * 128 + 4 auto const yoc = (yoe < (22 * 128)) ? ((yoe%128) < 29 ? yoe%128 : (yoe%128 - 29)%33) : yoe - (22 * 128) + 33; return (yoc != 0 && (yoc%4)==0); } CONSTCD11 inline year::operator int() const NOEXCEPT {return y_;} CONSTCD11 inline bool year::ok() const NOEXCEPT {return true;} CONSTCD11 inline year year::min() NOEXCEPT { return year{std::numeric_limits::min()}; } CONSTCD11 inline year year::max() NOEXCEPT { return year{std::numeric_limits::max()}; } CONSTCD11 inline bool operator==(const year& x, const year& y) NOEXCEPT { return static_cast(x) == static_cast(y); } CONSTCD11 inline bool operator!=(const year& x, const year& y) NOEXCEPT { return !(x == y); } CONSTCD11 inline bool operator<(const year& x, const year& y) NOEXCEPT { return static_cast(x) < static_cast(y); } CONSTCD11 inline bool operator>(const year& x, const year& y) NOEXCEPT { return y < x; } CONSTCD11 inline bool operator<=(const year& x, const year& y) NOEXCEPT { return !(y < x); } CONSTCD11 inline bool operator>=(const year& x, const year& y) NOEXCEPT { return !(x < y); } CONSTCD11 inline years operator-(const year& x, const year& y) NOEXCEPT { return years{static_cast(x) - static_cast(y)}; } CONSTCD11 inline year operator+(const year& x, const years& y) NOEXCEPT { return year{static_cast(x) + y.count()}; } CONSTCD11 inline year operator+(const years& x, const year& y) NOEXCEPT { return y + x; } CONSTCD11 inline year operator-(const year& x, const years& y) NOEXCEPT { return year{static_cast(x) - y.count()}; } template inline std::basic_ostream& operator<<(std::basic_ostream& os, const year& y) { date::detail::save_ostream _(os); os.fill('0'); os.flags(std::ios::dec | std::ios::internal); os.width(4 + (y < year{0})); os << static_cast(y); return os; } // weekday CONSTCD11 inline unsigned char weekday::weekday_from_days(int z) NOEXCEPT { auto u = static_cast(z); return static_cast(z >= -4 ? (u+4) % 7 : u % 7); } CONSTCD11 inline weekday::weekday(unsigned wd) NOEXCEPT : wd_(static_cast(wd != 7 ? wd : 0)) {} CONSTCD11 inline weekday::weekday(const sys_days& dp) NOEXCEPT : wd_(weekday_from_days(dp.time_since_epoch().count())) {} CONSTCD11 inline weekday::weekday(const local_days& dp) NOEXCEPT : wd_(weekday_from_days(dp.time_since_epoch().count())) {} CONSTCD14 inline weekday& weekday::operator++() NOEXCEPT {if (++wd_ == 7) wd_ = 0; return *this;} CONSTCD14 inline weekday weekday::operator++(int) NOEXCEPT {auto tmp(*this); ++(*this); return tmp;} CONSTCD14 inline weekday& weekday::operator--() NOEXCEPT {if (wd_-- == 0) wd_ = 6; return *this;} CONSTCD14 inline weekday weekday::operator--(int) NOEXCEPT {auto tmp(*this); --(*this); return tmp;} CONSTCD14 inline weekday& weekday::operator+=(const days& d) NOEXCEPT { *this = *this + d; return *this; } CONSTCD14 inline weekday& weekday::operator-=(const days& d) NOEXCEPT { *this = *this - d; return *this; } CONSTCD11 inline weekday::operator unsigned() const NOEXCEPT { return static_cast(wd_); } CONSTCD11 inline bool weekday::ok() const NOEXCEPT {return wd_ <= 6;} CONSTCD11 inline bool operator==(const weekday& x, const weekday& y) NOEXCEPT { return static_cast(x) == static_cast(y); } CONSTCD11 inline bool operator!=(const weekday& x, const weekday& y) NOEXCEPT { return !(x == y); } CONSTCD14 inline days operator-(const weekday& x, const weekday& y) NOEXCEPT { auto const diff = static_cast(x) - static_cast(y); return days{diff <= 6 ? diff : diff + 7}; } CONSTCD14 inline weekday operator+(const weekday& x, const days& y) NOEXCEPT { auto const wdu = static_cast(static_cast(x)) + y.count(); auto const wk = (wdu >= 0 ? wdu : wdu-6) / 7; return weekday{static_cast(wdu - wk * 7)}; } CONSTCD14 inline weekday operator+(const days& x, const weekday& y) NOEXCEPT { return y + x; } CONSTCD14 inline weekday operator-(const weekday& x, const days& y) NOEXCEPT { return x + -y; } template inline std::basic_ostream& operator<<(std::basic_ostream& os, const weekday& wd) { switch (static_cast(wd)) { case 0: os << "Yekshanbe"; break; case 1: os << "Doshanbe"; break; case 2: os << "Seshanbe"; break; case 3: os << "Chaharshanbe"; break; case 4: os << "Panjshanbe"; break; case 5: os << "Adine"; break; case 6: os << "Shanbe"; break; default: os << static_cast(wd) << " is not a valid weekday"; break; } return os; } #if !defined(_MSC_VER) || (_MSC_VER >= 1900) inline namespace literals { CONSTCD11 inline solar_hijri::day operator ""_d(unsigned long long d) NOEXCEPT { return solar_hijri::day{static_cast(d)}; } CONSTCD11 inline solar_hijri::year operator ""_y(unsigned long long y) NOEXCEPT { return solar_hijri::year(static_cast(y)); } #endif // !defined(_MSC_VER) || (_MSC_VER >= 1900) CONSTDATA solar_hijri::last_spec last{}; CONSTDATA solar_hijri::month far {1}; CONSTDATA solar_hijri::month ord {2}; CONSTDATA solar_hijri::month kho {3}; CONSTDATA solar_hijri::month tir {4}; CONSTDATA solar_hijri::month mor {5}; CONSTDATA solar_hijri::month sha {6}; CONSTDATA solar_hijri::month meh {7}; CONSTDATA solar_hijri::month aba {8}; CONSTDATA solar_hijri::month aza {9}; CONSTDATA solar_hijri::month dey {10}; CONSTDATA solar_hijri::month bah {11}; CONSTDATA solar_hijri::month esf {12}; CONSTDATA solar_hijri::month Farvardin {1}; CONSTDATA solar_hijri::month Ordibehesht {2}; CONSTDATA solar_hijri::month Khordad {3}; CONSTDATA solar_hijri::month Tir {4}; CONSTDATA solar_hijri::month Mordad {5}; CONSTDATA solar_hijri::month Shahrivar {6}; CONSTDATA solar_hijri::month Mehr {7}; CONSTDATA solar_hijri::month Aban {8}; CONSTDATA solar_hijri::month Azar {9}; CONSTDATA solar_hijri::month Dey {10}; CONSTDATA solar_hijri::month Bahman {11}; CONSTDATA solar_hijri::month Esfand {12}; CONSTDATA solar_hijri::weekday yek {0u}; CONSTDATA solar_hijri::weekday dos {1u}; CONSTDATA solar_hijri::weekday ses {2u}; CONSTDATA solar_hijri::weekday cha {3u}; CONSTDATA solar_hijri::weekday pan {4u}; CONSTDATA solar_hijri::weekday adi {5u}; CONSTDATA solar_hijri::weekday shn {6u}; CONSTDATA solar_hijri::weekday Yekshanbe {0u}; CONSTDATA solar_hijri::weekday Doshanbe {1u}; CONSTDATA solar_hijri::weekday Seshanbe {2u}; CONSTDATA solar_hijri::weekday Chaharshanbe {3u}; CONSTDATA solar_hijri::weekday Panjshanbe {4u}; CONSTDATA solar_hijri::weekday Adine {5u}; CONSTDATA solar_hijri::weekday Shanbe {6u}; #if !defined(_MSC_VER) || (_MSC_VER >= 1900) } // inline namespace literals #endif // weekday_indexed CONSTCD11 inline weekday weekday_indexed::weekday() const NOEXCEPT { return solar_hijri::weekday{static_cast(wd_)}; } CONSTCD11 inline unsigned weekday_indexed::index() const NOEXCEPT {return index_;} CONSTCD11 inline bool weekday_indexed::ok() const NOEXCEPT { return weekday().ok() && 1 <= index_ && index_ <= 5; } CONSTCD11 inline weekday_indexed::weekday_indexed(const solar_hijri::weekday& wd, unsigned index) NOEXCEPT : wd_(static_cast(static_cast(wd))) , index_(static_cast(index)) {} template inline std::basic_ostream& operator<<(std::basic_ostream& os, const weekday_indexed& wdi) { return os << wdi.weekday() << '[' << wdi.index() << ']'; } CONSTCD11 inline weekday_indexed weekday::operator[](unsigned index) const NOEXCEPT { return {*this, index}; } CONSTCD11 inline bool operator==(const weekday_indexed& x, const weekday_indexed& y) NOEXCEPT { return x.weekday() == y.weekday() && x.index() == y.index(); } CONSTCD11 inline bool operator!=(const weekday_indexed& x, const weekday_indexed& y) NOEXCEPT { return !(x == y); } // weekday_last CONSTCD11 inline solar_hijri::weekday weekday_last::weekday() const NOEXCEPT {return wd_;} CONSTCD11 inline bool weekday_last::ok() const NOEXCEPT {return wd_.ok();} CONSTCD11 inline weekday_last::weekday_last(const solar_hijri::weekday& wd) NOEXCEPT : wd_(wd) {} CONSTCD11 inline bool operator==(const weekday_last& x, const weekday_last& y) NOEXCEPT { return x.weekday() == y.weekday(); } CONSTCD11 inline bool operator!=(const weekday_last& x, const weekday_last& y) NOEXCEPT { return !(x == y); } template inline std::basic_ostream& operator<<(std::basic_ostream& os, const weekday_last& wdl) { return os << wdl.weekday() << "[last]"; } CONSTCD11 inline weekday_last weekday::operator[](last_spec) const NOEXCEPT { return weekday_last{*this}; } // year_month CONSTCD11 inline year_month::year_month(const solar_hijri::year& y, const solar_hijri::month& m) NOEXCEPT : y_(y) , m_(m) {} CONSTCD11 inline year year_month::year() const NOEXCEPT {return y_;} CONSTCD11 inline month year_month::month() const NOEXCEPT {return m_;} CONSTCD11 inline bool year_month::ok() const NOEXCEPT {return y_.ok() && m_.ok();} CONSTCD14 inline year_month& year_month::operator+=(const months& dm) NOEXCEPT { *this = *this + dm; return *this; } CONSTCD14 inline year_month& year_month::operator-=(const months& dm) NOEXCEPT { *this = *this - dm; return *this; } CONSTCD14 inline year_month& year_month::operator+=(const years& dy) NOEXCEPT { *this = *this + dy; return *this; } CONSTCD14 inline year_month& year_month::operator-=(const years& dy) NOEXCEPT { *this = *this - dy; return *this; } CONSTCD11 inline bool operator==(const year_month& x, const year_month& y) NOEXCEPT { return x.year() == y.year() && x.month() == y.month(); } CONSTCD11 inline bool operator!=(const year_month& x, const year_month& y) NOEXCEPT { return !(x == y); } CONSTCD11 inline bool operator<(const year_month& x, const year_month& y) NOEXCEPT { return x.year() < y.year() ? true : (x.year() > y.year() ? false : (x.month() < y.month())); } CONSTCD11 inline bool operator>(const year_month& x, const year_month& y) NOEXCEPT { return y < x; } CONSTCD11 inline bool operator<=(const year_month& x, const year_month& y) NOEXCEPT { return !(y < x); } CONSTCD11 inline bool operator>=(const year_month& x, const year_month& y) NOEXCEPT { return !(x < y); } CONSTCD14 inline year_month operator+(const year_month& ym, const months& dm) NOEXCEPT { auto dmi = static_cast(static_cast(ym.month())) - 1 + dm.count(); auto dy = (dmi >= 0 ? dmi : dmi-11) / 12; dmi = dmi - dy * 12 + 1; return (ym.year() + years(dy)) / month(static_cast(dmi)); } CONSTCD14 inline year_month operator+(const months& dm, const year_month& ym) NOEXCEPT { return ym + dm; } CONSTCD14 inline year_month operator-(const year_month& ym, const months& dm) NOEXCEPT { return ym + -dm; } CONSTCD11 inline months operator-(const year_month& x, const year_month& y) NOEXCEPT { return (x.year() - y.year()) + months(static_cast(x.month()) - static_cast(y.month())); } CONSTCD11 inline year_month operator+(const year_month& ym, const years& dy) NOEXCEPT { return (ym.year() + dy) / ym.month(); } CONSTCD11 inline year_month operator+(const years& dy, const year_month& ym) NOEXCEPT { return ym + dy; } CONSTCD11 inline year_month operator-(const year_month& ym, const years& dy) NOEXCEPT { return ym + -dy; } template inline std::basic_ostream& operator<<(std::basic_ostream& os, const year_month& ym) { return os << ym.year() << '/' << ym.month(); } // month_day CONSTCD11 inline month_day::month_day(const solar_hijri::month& m, const solar_hijri::day& d) NOEXCEPT : m_(m) , d_(d) {} CONSTCD11 inline solar_hijri::month month_day::month() const NOEXCEPT {return m_;} CONSTCD11 inline solar_hijri::day month_day::day() const NOEXCEPT {return d_;} CONSTCD14 inline bool month_day::ok() const NOEXCEPT { CONSTDATA solar_hijri::day d[] = { solar_hijri::day(31), solar_hijri::day(31), solar_hijri::day(31), solar_hijri::day(31), solar_hijri::day(31), solar_hijri::day(31), solar_hijri::day(30), solar_hijri::day(30), solar_hijri::day(30), solar_hijri::day(30), solar_hijri::day(30), solar_hijri::day(30) }; return m_.ok() && solar_hijri::day(1) <= d_ && d_ <= d[static_cast(m_)-1]; } CONSTCD11 inline bool operator==(const month_day& x, const month_day& y) NOEXCEPT { return x.month() == y.month() && x.day() == y.day(); } CONSTCD11 inline bool operator!=(const month_day& x, const month_day& y) NOEXCEPT { return !(x == y); } CONSTCD11 inline bool operator<(const month_day& x, const month_day& y) NOEXCEPT { return x.month() < y.month() ? true : (x.month() > y.month() ? false : (x.day() < y.day())); } CONSTCD11 inline bool operator>(const month_day& x, const month_day& y) NOEXCEPT { return y < x; } CONSTCD11 inline bool operator<=(const month_day& x, const month_day& y) NOEXCEPT { return !(y < x); } CONSTCD11 inline bool operator>=(const month_day& x, const month_day& y) NOEXCEPT { return !(x < y); } template inline std::basic_ostream& operator<<(std::basic_ostream& os, const month_day& md) { return os << md.month() << '/' << md.day(); } // month_day_last CONSTCD11 inline month month_day_last::month() const NOEXCEPT {return m_;} CONSTCD11 inline bool month_day_last::ok() const NOEXCEPT {return m_.ok();} CONSTCD11 inline month_day_last::month_day_last(const solar_hijri::month& m) NOEXCEPT : m_(m) {} CONSTCD11 inline bool operator==(const month_day_last& x, const month_day_last& y) NOEXCEPT { return x.month() == y.month(); } CONSTCD11 inline bool operator!=(const month_day_last& x, const month_day_last& y) NOEXCEPT { return !(x == y); } CONSTCD11 inline bool operator<(const month_day_last& x, const month_day_last& y) NOEXCEPT { return x.month() < y.month(); } CONSTCD11 inline bool operator>(const month_day_last& x, const month_day_last& y) NOEXCEPT { return y < x; } CONSTCD11 inline bool operator<=(const month_day_last& x, const month_day_last& y) NOEXCEPT { return !(y < x); } CONSTCD11 inline bool operator>=(const month_day_last& x, const month_day_last& y) NOEXCEPT { return !(x < y); } template inline std::basic_ostream& operator<<(std::basic_ostream& os, const month_day_last& mdl) { return os << mdl.month() << "/last"; } // month_weekday CONSTCD11 inline month_weekday::month_weekday(const solar_hijri::month& m, const solar_hijri::weekday_indexed& wdi) NOEXCEPT : m_(m) , wdi_(wdi) {} CONSTCD11 inline month month_weekday::month() const NOEXCEPT {return m_;} CONSTCD11 inline weekday_indexed month_weekday::weekday_indexed() const NOEXCEPT { return wdi_; } CONSTCD11 inline bool month_weekday::ok() const NOEXCEPT { return m_.ok() && wdi_.ok(); } CONSTCD11 inline bool operator==(const month_weekday& x, const month_weekday& y) NOEXCEPT { return x.month() == y.month() && x.weekday_indexed() == y.weekday_indexed(); } CONSTCD11 inline bool operator!=(const month_weekday& x, const month_weekday& y) NOEXCEPT { return !(x == y); } template inline std::basic_ostream& operator<<(std::basic_ostream& os, const month_weekday& mwd) { return os << mwd.month() << '/' << mwd.weekday_indexed(); } // month_weekday_last CONSTCD11 inline month_weekday_last::month_weekday_last(const solar_hijri::month& m, const solar_hijri::weekday_last& wdl) NOEXCEPT : m_(m) , wdl_(wdl) {} CONSTCD11 inline month month_weekday_last::month() const NOEXCEPT {return m_;} CONSTCD11 inline weekday_last month_weekday_last::weekday_last() const NOEXCEPT { return wdl_; } CONSTCD11 inline bool month_weekday_last::ok() const NOEXCEPT { return m_.ok() && wdl_.ok(); } CONSTCD11 inline bool operator==(const month_weekday_last& x, const month_weekday_last& y) NOEXCEPT { return x.month() == y.month() && x.weekday_last() == y.weekday_last(); } CONSTCD11 inline bool operator!=(const month_weekday_last& x, const month_weekday_last& y) NOEXCEPT { return !(x == y); } template inline std::basic_ostream& operator<<(std::basic_ostream& os, const month_weekday_last& mwdl) { return os << mwdl.month() << '/' << mwdl.weekday_last(); } // year_month_day_last CONSTCD11 inline year_month_day_last::year_month_day_last(const solar_hijri::year& y, const solar_hijri::month_day_last& mdl) NOEXCEPT : y_(y) , mdl_(mdl) {} CONSTCD14 inline year_month_day_last& year_month_day_last::operator+=(const months& m) NOEXCEPT { *this = *this + m; return *this; } CONSTCD14 inline year_month_day_last& year_month_day_last::operator-=(const months& m) NOEXCEPT { *this = *this - m; return *this; } CONSTCD14 inline year_month_day_last& year_month_day_last::operator+=(const years& y) NOEXCEPT { *this = *this + y; return *this; } CONSTCD14 inline year_month_day_last& year_month_day_last::operator-=(const years& y) NOEXCEPT { *this = *this - y; return *this; } CONSTCD11 inline year year_month_day_last::year() const NOEXCEPT {return y_;} CONSTCD11 inline month year_month_day_last::month() const NOEXCEPT {return mdl_.month();} CONSTCD11 inline month_day_last year_month_day_last::month_day_last() const NOEXCEPT { return mdl_; } CONSTCD14 inline day year_month_day_last::day() const NOEXCEPT { CONSTDATA solar_hijri::day d[] = { solar_hijri::day(31), solar_hijri::day(31), solar_hijri::day(31), solar_hijri::day(31), solar_hijri::day(31), solar_hijri::day(31), solar_hijri::day(30), solar_hijri::day(30), solar_hijri::day(30), solar_hijri::day(30), solar_hijri::day(30), solar_hijri::day(29) }; return month() != esf || !y_.is_leap() ? d[static_cast(month()) - 1] : solar_hijri::day(30); } CONSTCD14 inline year_month_day_last::operator sys_days() const NOEXCEPT { return sys_days(year()/month()/day()); } CONSTCD14 inline year_month_day_last::operator local_days() const NOEXCEPT { return local_days(year()/month()/day()); } CONSTCD11 inline bool year_month_day_last::ok() const NOEXCEPT { return y_.ok() && mdl_.ok(); } CONSTCD11 inline bool operator==(const year_month_day_last& x, const year_month_day_last& y) NOEXCEPT { return x.year() == y.year() && x.month_day_last() == y.month_day_last(); } CONSTCD11 inline bool operator!=(const year_month_day_last& x, const year_month_day_last& y) NOEXCEPT { return !(x == y); } CONSTCD11 inline bool operator<(const year_month_day_last& x, const year_month_day_last& y) NOEXCEPT { return x.year() < y.year() ? true : (x.year() > y.year() ? false : (x.month_day_last() < y.month_day_last())); } CONSTCD11 inline bool operator>(const year_month_day_last& x, const year_month_day_last& y) NOEXCEPT { return y < x; } CONSTCD11 inline bool operator<=(const year_month_day_last& x, const year_month_day_last& y) NOEXCEPT { return !(y < x); } CONSTCD11 inline bool operator>=(const year_month_day_last& x, const year_month_day_last& y) NOEXCEPT { return !(x < y); } template inline std::basic_ostream& operator<<(std::basic_ostream& os, const year_month_day_last& ymdl) { return os << ymdl.year() << '/' << ymdl.month_day_last(); } CONSTCD14 inline year_month_day_last operator+(const year_month_day_last& ymdl, const months& dm) NOEXCEPT { return (ymdl.year() / ymdl.month() + dm) / last; } CONSTCD14 inline year_month_day_last operator+(const months& dm, const year_month_day_last& ymdl) NOEXCEPT { return ymdl + dm; } CONSTCD14 inline year_month_day_last operator-(const year_month_day_last& ymdl, const months& dm) NOEXCEPT { return ymdl + (-dm); } CONSTCD11 inline year_month_day_last operator+(const year_month_day_last& ymdl, const years& dy) NOEXCEPT { return {ymdl.year()+dy, ymdl.month_day_last()}; } CONSTCD11 inline year_month_day_last operator+(const years& dy, const year_month_day_last& ymdl) NOEXCEPT { return ymdl + dy; } CONSTCD11 inline year_month_day_last operator-(const year_month_day_last& ymdl, const years& dy) NOEXCEPT { return ymdl + (-dy); } // year_month_day CONSTCD11 inline year_month_day::year_month_day(const solar_hijri::year& y, const solar_hijri::month& m, const solar_hijri::day& d) NOEXCEPT : y_(y) , m_(m) , d_(d) {} CONSTCD14 inline year_month_day::year_month_day(const year_month_day_last& ymdl) NOEXCEPT : y_(ymdl.year()) , m_(ymdl.month()) , d_(ymdl.day()) {} CONSTCD14 inline year_month_day::year_month_day(sys_days dp) NOEXCEPT : year_month_day(from_days(dp.time_since_epoch())) {} CONSTCD14 inline year_month_day::year_month_day(local_days dp) NOEXCEPT : year_month_day(from_days(dp.time_since_epoch())) {} CONSTCD11 inline year year_month_day::year() const NOEXCEPT {return y_;} CONSTCD11 inline month year_month_day::month() const NOEXCEPT {return m_;} CONSTCD11 inline day year_month_day::day() const NOEXCEPT {return d_;} CONSTCD14 inline year_month_day& year_month_day::operator+=(const months& m) NOEXCEPT { *this = *this + m; return *this; } CONSTCD14 inline year_month_day& year_month_day::operator-=(const months& m) NOEXCEPT { *this = *this - m; return *this; } CONSTCD14 inline year_month_day& year_month_day::operator+=(const years& y) NOEXCEPT { *this = *this + y; return *this; } CONSTCD14 inline year_month_day& year_month_day::operator-=(const years& y) NOEXCEPT { *this = *this - y; return *this; } CONSTCD14 inline days year_month_day::to_days() const NOEXCEPT { static_assert(std::numeric_limits::digits >= 18, "This algorithm has not been ported to a 16 bit unsigned integer"); static_assert(std::numeric_limits::digits >= 20, "This algorithm has not been ported to a 16 bit signed integer"); using namespace internal; auto const y = static_cast(y_) - 475; auto const m = static_cast(m_); auto const d = static_cast(d_); auto const era_d = static_cast(y >= 0 ? y : y-years_in_era+1) / static_cast(years_in_era); auto const era = static_cast(era_d); auto const fdoe = static_cast(epoch + era * days_in_era); auto const yoe = static_cast(y - era * years_in_era); auto const period_d = static_cast(yoe/years_in_period); auto const period = static_cast(period_d); auto const yop = yoe%years_in_period; auto const fdop = period*days_in_period; auto const cycle = yop < 29 ? 0 : static_cast((yop-29)/years_in_other_cycles + 1); auto const yoc = yop < 29 ? yop : (yop-29)%years_in_other_cycles; auto const fdoc = cycle > 0 ? days_in_first_cycle + (cycle-1)*days_in_other_cycles : 0; auto const group = yoc < 1 ? 0 : static_cast((yoc-1) / 4); auto const yog = static_cast(yoc < 1 ? -1 : (yoc-1) % 4); auto const fdoyog = group*1461 + (yog+1)*365; auto const fdoyoe = fdop + fdoc + fdoyog; auto const doy = 30*(m-1) + ((m > 6) ? 6 : m-1) + d-1; // [0, 365] auto const doe = fdoe + fdoyoe + doy; return days{doe - unix_time_shift}; } CONSTCD14 inline year_month_day::operator sys_days() const NOEXCEPT { return sys_days{to_days()}; } CONSTCD14 inline year_month_day::operator local_days() const NOEXCEPT { return local_days{to_days()}; } CONSTCD14 inline bool year_month_day::ok() const NOEXCEPT { if (!(y_.ok() && m_.ok())) return false; return solar_hijri::day(1) <= d_ && d_ <= (y_/m_/last).day(); } CONSTCD11 inline bool operator==(const year_month_day& x, const year_month_day& y) NOEXCEPT { return x.year() == y.year() && x.month() == y.month() && x.day() == y.day(); } CONSTCD11 inline bool operator!=(const year_month_day& x, const year_month_day& y) NOEXCEPT { return !(x == y); } CONSTCD11 inline bool operator<(const year_month_day& x, const year_month_day& y) NOEXCEPT { return x.year() < y.year() ? true : (x.year() > y.year() ? false : (x.month() < y.month() ? true : (x.month() > y.month() ? false : (x.day() < y.day())))); } CONSTCD11 inline bool operator>(const year_month_day& x, const year_month_day& y) NOEXCEPT { return y < x; } CONSTCD11 inline bool operator<=(const year_month_day& x, const year_month_day& y) NOEXCEPT { return !(y < x); } CONSTCD11 inline bool operator>=(const year_month_day& x, const year_month_day& y) NOEXCEPT { return !(x < y); } template inline std::basic_ostream& operator<<(std::basic_ostream& os, const year_month_day& ymd) { date::detail::save_ostream _(os); os.fill('0'); os.flags(std::ios::dec | std::ios::right); os << ymd.year() << '-'; os.width(2); os << static_cast(ymd.month()) << '-'; os << ymd.day(); return os; } CONSTCD14 inline year_month_day year_month_day::from_days(days dp) NOEXCEPT { static_assert(std::numeric_limits::digits >= 18, "This algorithm has not been ported to a 16 bit unsigned integer"); static_assert(std::numeric_limits::digits >= 20, "This algorithm has not been ported to a 16 bit signed integer"); using namespace internal; auto const z = dp.count() + unix_time_shift; auto const delta = static_cast(z - epoch); auto const era = static_cast(delta >= 0 ? delta : delta-days_in_era+1) / static_cast(days_in_era); auto const era_i = static_cast(era); auto const fdoe = static_cast(epoch + static_cast(era_i * days_in_era)); auto const doe_fdoe = z - fdoe; auto const period = static_cast(doe_fdoe < 22*days_in_period ? doe_fdoe / days_in_period : 22); auto const dop = doe_fdoe % days_in_period; auto const cycle = dop < days_in_first_cycle ? 0 : (dop-days_in_first_cycle) / days_in_other_cycles + 1; auto const doc = dop < days_in_first_cycle ? dop : (dop-days_in_first_cycle) % days_in_other_cycles; auto const group = doc < 365 && period != 22 ? -1 : static_cast(((doc < 365 ? 365 : doc)-365)/1461); auto const yog = doc < 365 && period != 22 ? -1 : static_cast( (period != 22 ? ((doc-365 )%1461) : doc)/365); auto const yoc = group == -1 ? 0 : (period != 22 ? 1 : 0) + group*4 + (yog == 4 ? 3 : yog); auto const doy = group == -1 ? doc : (period != 22 ? ((yoc-1)%4 == 0 ? (group >= 0 ? (doe_fdoe - (period*days_in_period) - (cycle > 0 ? days_in_first_cycle + (cycle-1)*days_in_other_cycles : 0) - (group*1461 + ((yog == 4 ? 3 : yog)+1)*365)) : 365) : doe_fdoe - (period*days_in_period) - (cycle > 0 ? days_in_first_cycle + (cycle-1)*days_in_other_cycles : 0) - (group*1461 + ((yog == 4 ? 3 : yog)+1)*365)) : (yog == 4 ? 365 : doe_fdoe - (period*days_in_period) - yog*365)); auto const yoe = period != 22 ? period*years_in_period + (cycle > 0 ? years_in_first_cycle + (cycle-1)*years_in_other_cycles : 0) + yoc : 22*years_in_period + ((yog == 4) ? 3 : yog); auto const y = static_cast(static_cast(yoe) + 475 + era_i * years_in_era); auto const m = doy < 186 ? doy/31 + 1 : (doy-186)/30 + 7; // [1, 12] auto const d = doy - (30*(m-1) + ((m > 6) ? 6 : m-1) - 1); // [1, 31] return year_month_day{solar_hijri::year(y), solar_hijri::month(m), solar_hijri::day(d)}; } CONSTCD14 inline year_month_day operator+(const year_month_day& ymd, const months& dm) NOEXCEPT { return (ymd.year() / ymd.month() + dm) / ymd.day(); } CONSTCD14 inline year_month_day operator+(const months& dm, const year_month_day& ymd) NOEXCEPT { return ymd + dm; } CONSTCD14 inline year_month_day operator-(const year_month_day& ymd, const months& dm) NOEXCEPT { return ymd + (-dm); } CONSTCD11 inline year_month_day operator+(const year_month_day& ymd, const years& dy) NOEXCEPT { return (ymd.year() + dy) / ymd.month() / ymd.day(); } CONSTCD11 inline year_month_day operator+(const years& dy, const year_month_day& ymd) NOEXCEPT { return ymd + dy; } CONSTCD11 inline year_month_day operator-(const year_month_day& ymd, const years& dy) NOEXCEPT { return ymd + (-dy); } // year_month_weekday CONSTCD11 inline year_month_weekday::year_month_weekday(const solar_hijri::year& y, const solar_hijri::month& m, const solar_hijri::weekday_indexed& wdi) NOEXCEPT : y_(y) , m_(m) , wdi_(wdi) {} CONSTCD14 inline year_month_weekday::year_month_weekday(const sys_days& dp) NOEXCEPT : year_month_weekday(from_days(dp.time_since_epoch())) {} CONSTCD14 inline year_month_weekday::year_month_weekday(const local_days& dp) NOEXCEPT : year_month_weekday(from_days(dp.time_since_epoch())) {} CONSTCD14 inline year_month_weekday& year_month_weekday::operator+=(const months& m) NOEXCEPT { *this = *this + m; return *this; } CONSTCD14 inline year_month_weekday& year_month_weekday::operator-=(const months& m) NOEXCEPT { *this = *this - m; return *this; } CONSTCD14 inline year_month_weekday& year_month_weekday::operator+=(const years& y) NOEXCEPT { *this = *this + y; return *this; } CONSTCD14 inline year_month_weekday& year_month_weekday::operator-=(const years& y) NOEXCEPT { *this = *this - y; return *this; } CONSTCD11 inline year year_month_weekday::year() const NOEXCEPT {return y_;} CONSTCD11 inline month year_month_weekday::month() const NOEXCEPT {return m_;} CONSTCD11 inline weekday year_month_weekday::weekday() const NOEXCEPT { return wdi_.weekday(); } CONSTCD11 inline unsigned year_month_weekday::index() const NOEXCEPT { return wdi_.index(); } CONSTCD11 inline weekday_indexed year_month_weekday::weekday_indexed() const NOEXCEPT { return wdi_; } CONSTCD14 inline year_month_weekday::operator sys_days() const NOEXCEPT { return sys_days{to_days()}; } CONSTCD14 inline year_month_weekday::operator local_days() const NOEXCEPT { return local_days{to_days()}; } CONSTCD14 inline bool year_month_weekday::ok() const NOEXCEPT { if (!y_.ok() || !m_.ok() || !wdi_.weekday().ok() || wdi_.index() < 1) return false; if (wdi_.index() <= 4) return true; auto d2 = wdi_.weekday() - solar_hijri::weekday(y_/m_/1) + days((wdi_.index()-1)*7 + 1); return static_cast(d2.count()) <= static_cast((y_/m_/last).day()); } CONSTCD14 inline year_month_weekday year_month_weekday::from_days(days d) NOEXCEPT { sys_days dp{d}; auto const wd = solar_hijri::weekday(dp); auto const ymd = year_month_day(dp); return {ymd.year(), ymd.month(), wd[(static_cast(ymd.day())-1)/7+1]}; } CONSTCD14 inline days year_month_weekday::to_days() const NOEXCEPT { auto d = sys_days(y_/m_/1); return (d + (wdi_.weekday() - solar_hijri::weekday(d) + days{(wdi_.index()-1)*7}) ).time_since_epoch(); } CONSTCD11 inline bool operator==(const year_month_weekday& x, const year_month_weekday& y) NOEXCEPT { return x.year() == y.year() && x.month() == y.month() && x.weekday_indexed() == y.weekday_indexed(); } CONSTCD11 inline bool operator!=(const year_month_weekday& x, const year_month_weekday& y) NOEXCEPT { return !(x == y); } template inline std::basic_ostream& operator<<(std::basic_ostream& os, const year_month_weekday& ymwdi) { return os << ymwdi.year() << '/' << ymwdi.month() << '/' << ymwdi.weekday_indexed(); } CONSTCD14 inline year_month_weekday operator+(const year_month_weekday& ymwd, const months& dm) NOEXCEPT { return (ymwd.year() / ymwd.month() + dm) / ymwd.weekday_indexed(); } CONSTCD14 inline year_month_weekday operator+(const months& dm, const year_month_weekday& ymwd) NOEXCEPT { return ymwd + dm; } CONSTCD14 inline year_month_weekday operator-(const year_month_weekday& ymwd, const months& dm) NOEXCEPT { return ymwd + (-dm); } CONSTCD11 inline year_month_weekday operator+(const year_month_weekday& ymwd, const years& dy) NOEXCEPT { return {ymwd.year()+dy, ymwd.month(), ymwd.weekday_indexed()}; } CONSTCD11 inline year_month_weekday operator+(const years& dy, const year_month_weekday& ymwd) NOEXCEPT { return ymwd + dy; } CONSTCD11 inline year_month_weekday operator-(const year_month_weekday& ymwd, const years& dy) NOEXCEPT { return ymwd + (-dy); } // year_month_weekday_last CONSTCD11 inline year_month_weekday_last::year_month_weekday_last(const solar_hijri::year& y, const solar_hijri::month& m, const solar_hijri::weekday_last& wdl) NOEXCEPT : y_(y) , m_(m) , wdl_(wdl) {} CONSTCD14 inline year_month_weekday_last& year_month_weekday_last::operator+=(const months& m) NOEXCEPT { *this = *this + m; return *this; } CONSTCD14 inline year_month_weekday_last& year_month_weekday_last::operator-=(const months& m) NOEXCEPT { *this = *this - m; return *this; } CONSTCD14 inline year_month_weekday_last& year_month_weekday_last::operator+=(const years& y) NOEXCEPT { *this = *this + y; return *this; } CONSTCD14 inline year_month_weekday_last& year_month_weekday_last::operator-=(const years& y) NOEXCEPT { *this = *this - y; return *this; } CONSTCD11 inline year year_month_weekday_last::year() const NOEXCEPT {return y_;} CONSTCD11 inline month year_month_weekday_last::month() const NOEXCEPT {return m_;} CONSTCD11 inline weekday year_month_weekday_last::weekday() const NOEXCEPT { return wdl_.weekday(); } CONSTCD11 inline weekday_last year_month_weekday_last::weekday_last() const NOEXCEPT { return wdl_; } CONSTCD14 inline year_month_weekday_last::operator sys_days() const NOEXCEPT { return sys_days{to_days()}; } CONSTCD14 inline year_month_weekday_last::operator local_days() const NOEXCEPT { return local_days{to_days()}; } CONSTCD11 inline bool year_month_weekday_last::ok() const NOEXCEPT { return y_.ok() && m_.ok() && wdl_.ok(); } CONSTCD14 inline days year_month_weekday_last::to_days() const NOEXCEPT { auto const d = sys_days(y_/m_/last); return (d - (solar_hijri::weekday{d} - wdl_.weekday())).time_since_epoch(); } CONSTCD11 inline bool operator==(const year_month_weekday_last& x, const year_month_weekday_last& y) NOEXCEPT { return x.year() == y.year() && x.month() == y.month() && x.weekday_last() == y.weekday_last(); } CONSTCD11 inline bool operator!=(const year_month_weekday_last& x, const year_month_weekday_last& y) NOEXCEPT { return !(x == y); } template inline std::basic_ostream& operator<<(std::basic_ostream& os, const year_month_weekday_last& ymwdl) { return os << ymwdl.year() << '/' << ymwdl.month() << '/' << ymwdl.weekday_last(); } CONSTCD14 inline year_month_weekday_last operator+(const year_month_weekday_last& ymwdl, const months& dm) NOEXCEPT { return (ymwdl.year() / ymwdl.month() + dm) / ymwdl.weekday_last(); } CONSTCD14 inline year_month_weekday_last operator+(const months& dm, const year_month_weekday_last& ymwdl) NOEXCEPT { return ymwdl + dm; } CONSTCD14 inline year_month_weekday_last operator-(const year_month_weekday_last& ymwdl, const months& dm) NOEXCEPT { return ymwdl + (-dm); } CONSTCD11 inline year_month_weekday_last operator+(const year_month_weekday_last& ymwdl, const years& dy) NOEXCEPT { return {ymwdl.year()+dy, ymwdl.month(), ymwdl.weekday_last()}; } CONSTCD11 inline year_month_weekday_last operator+(const years& dy, const year_month_weekday_last& ymwdl) NOEXCEPT { return ymwdl + dy; } CONSTCD11 inline year_month_weekday_last operator-(const year_month_weekday_last& ymwdl, const years& dy) NOEXCEPT { return ymwdl + (-dy); } // year_month from operator/() CONSTCD11 inline year_month operator/(const year& y, const month& m) NOEXCEPT { return {y, m}; } CONSTCD11 inline year_month operator/(const year& y, int m) NOEXCEPT { return y / month(static_cast(m)); } // month_day from operator/() CONSTCD11 inline month_day operator/(const month& m, const day& d) NOEXCEPT { return {m, d}; } CONSTCD11 inline month_day operator/(const day& d, const month& m) NOEXCEPT { return m / d; } CONSTCD11 inline month_day operator/(const month& m, int d) NOEXCEPT { return m / day(static_cast(d)); } CONSTCD11 inline month_day operator/(int m, const day& d) NOEXCEPT { return month(static_cast(m)) / d; } CONSTCD11 inline month_day operator/(const day& d, int m) NOEXCEPT {return m / d;} // month_day_last from operator/() CONSTCD11 inline month_day_last operator/(const month& m, last_spec) NOEXCEPT { return month_day_last{m}; } CONSTCD11 inline month_day_last operator/(last_spec, const month& m) NOEXCEPT { return m/last; } CONSTCD11 inline month_day_last operator/(int m, last_spec) NOEXCEPT { return month(static_cast(m))/last; } CONSTCD11 inline month_day_last operator/(last_spec, int m) NOEXCEPT { return m/last; } // month_weekday from operator/() CONSTCD11 inline month_weekday operator/(const month& m, const weekday_indexed& wdi) NOEXCEPT { return {m, wdi}; } CONSTCD11 inline month_weekday operator/(const weekday_indexed& wdi, const month& m) NOEXCEPT { return m / wdi; } CONSTCD11 inline month_weekday operator/(int m, const weekday_indexed& wdi) NOEXCEPT { return month(static_cast(m)) / wdi; } CONSTCD11 inline month_weekday operator/(const weekday_indexed& wdi, int m) NOEXCEPT { return m / wdi; } // month_weekday_last from operator/() CONSTCD11 inline month_weekday_last operator/(const month& m, const weekday_last& wdl) NOEXCEPT { return {m, wdl}; } CONSTCD11 inline month_weekday_last operator/(const weekday_last& wdl, const month& m) NOEXCEPT { return m / wdl; } CONSTCD11 inline month_weekday_last operator/(int m, const weekday_last& wdl) NOEXCEPT { return month(static_cast(m)) / wdl; } CONSTCD11 inline month_weekday_last operator/(const weekday_last& wdl, int m) NOEXCEPT { return m / wdl; } // year_month_day from operator/() CONSTCD11 inline year_month_day operator/(const year_month& ym, const day& d) NOEXCEPT { return {ym.year(), ym.month(), d}; } CONSTCD11 inline year_month_day operator/(const year_month& ym, int d) NOEXCEPT { return ym / day(static_cast(d)); } CONSTCD11 inline year_month_day operator/(const year& y, const month_day& md) NOEXCEPT { return y / md.month() / md.day(); } CONSTCD11 inline year_month_day operator/(int y, const month_day& md) NOEXCEPT { return year(y) / md; } CONSTCD11 inline year_month_day operator/(const month_day& md, const year& y) NOEXCEPT { return y / md; } CONSTCD11 inline year_month_day operator/(const month_day& md, int y) NOEXCEPT { return year(y) / md; } // year_month_day_last from operator/() CONSTCD11 inline year_month_day_last operator/(const year_month& ym, last_spec) NOEXCEPT { return {ym.year(), month_day_last{ym.month()}}; } CONSTCD11 inline year_month_day_last operator/(const year& y, const month_day_last& mdl) NOEXCEPT { return {y, mdl}; } CONSTCD11 inline year_month_day_last operator/(int y, const month_day_last& mdl) NOEXCEPT { return year(y) / mdl; } CONSTCD11 inline year_month_day_last operator/(const month_day_last& mdl, const year& y) NOEXCEPT { return y / mdl; } CONSTCD11 inline year_month_day_last operator/(const month_day_last& mdl, int y) NOEXCEPT { return year(y) / mdl; } // year_month_weekday from operator/() CONSTCD11 inline year_month_weekday operator/(const year_month& ym, const weekday_indexed& wdi) NOEXCEPT { return {ym.year(), ym.month(), wdi}; } CONSTCD11 inline year_month_weekday operator/(const year& y, const month_weekday& mwd) NOEXCEPT { return {y, mwd.month(), mwd.weekday_indexed()}; } CONSTCD11 inline year_month_weekday operator/(int y, const month_weekday& mwd) NOEXCEPT { return year(y) / mwd; } CONSTCD11 inline year_month_weekday operator/(const month_weekday& mwd, const year& y) NOEXCEPT { return y / mwd; } CONSTCD11 inline year_month_weekday operator/(const month_weekday& mwd, int y) NOEXCEPT { return year(y) / mwd; } // year_month_weekday_last from operator/() CONSTCD11 inline year_month_weekday_last operator/(const year_month& ym, const weekday_last& wdl) NOEXCEPT { return {ym.year(), ym.month(), wdl}; } CONSTCD11 inline year_month_weekday_last operator/(const year& y, const month_weekday_last& mwdl) NOEXCEPT { return {y, mwdl.month(), mwdl.weekday_last()}; } CONSTCD11 inline year_month_weekday_last operator/(int y, const month_weekday_last& mwdl) NOEXCEPT { return year(y) / mwdl; } CONSTCD11 inline year_month_weekday_last operator/(const month_weekday_last& mwdl, const year& y) NOEXCEPT { return y / mwdl; } CONSTCD11 inline year_month_weekday_last operator/(const month_weekday_last& mwdl, int y) NOEXCEPT { return year(y) / mwdl; } } // namespace solar_hijri #endif // SOLAR_HIJRI_H pr0m1th3as-datatypes-9c9a8d3/src/date/tz.h000066400000000000000000002464611522766574100204040ustar00rootroot00000000000000#ifndef TZ_H #define TZ_H // The MIT License (MIT) // // Copyright (c) 2015, 2016, 2017 Howard Hinnant // Copyright (c) 2017 Jiangang Zhuang // Copyright (c) 2017 Aaron Bishop // Copyright (c) 2017 Tomasz Kamiński // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. // // Our apologies. When the previous paragraph was written, lowercase had not yet // been invented (that would involve another several millennia of evolution). // We did not mean to shout. // Get more recent database at http://www.iana.org/time-zones // The notion of "current timezone" is something the operating system is expected to "just // know". How it knows this is system specific. It's often a value set by the user at OS // installation time and recorded by the OS somewhere. On Linux and Mac systems the current // timezone name is obtained by looking at the name or contents of a particular file on // disk. On Windows the current timezone name comes from the registry. In either method, // there is no guarantee that the "native" current timezone name obtained will match any // of the "Standard" names in this library's "database". On Linux, the names usually do // seem to match so mapping functions to map from native to "Standard" are typically not // required. On Windows, the names are never "Standard" so mapping is always required. // Technically any OS may use the mapping process but currently only Windows does use it. #ifndef USE_OS_TZDB # define USE_OS_TZDB 0 #endif #ifndef HAS_REMOTE_API # if USE_OS_TZDB == 0 # if defined __ANDROID__ # define HAS_REMOTE_API 0 # else # define HAS_REMOTE_API 1 # endif # else // HAS_REMOTE_API makes no sense when using the OS timezone database # define HAS_REMOTE_API 0 # endif #endif #ifdef __clang__ # pragma clang diagnostic push # pragma clang diagnostic ignored "-Wconstant-logical-operand" #endif static_assert(!(USE_OS_TZDB && HAS_REMOTE_API), "USE_OS_TZDB and HAS_REMOTE_API can not be used together"); #ifdef __clang__ # pragma clang diagnostic pop #endif #ifndef AUTO_DOWNLOAD # define AUTO_DOWNLOAD HAS_REMOTE_API #endif static_assert(HAS_REMOTE_API == 0 ? AUTO_DOWNLOAD == 0 : true, "AUTO_DOWNLOAD can not be turned on without HAS_REMOTE_API"); #ifndef USE_SHELL_API # define USE_SHELL_API 1 #endif #if USE_OS_TZDB # ifdef _WIN32 # error "USE_OS_TZDB can not be used on Windows" # endif #endif #ifndef HAS_DEDUCTION_GUIDES # if __cplusplus >= 201703 # define HAS_DEDUCTION_GUIDES 1 # else # define HAS_DEDUCTION_GUIDES 0 # endif #endif // HAS_DEDUCTION_GUIDES #include "date.h" #if defined(_MSC_VER) && (_MSC_VER < 1900) #include "tz_private.h" #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef _WIN32 # ifdef DATE_BUILD_DLL # define DATE_API __declspec(dllexport) # elif defined(DATE_USE_DLL) # define DATE_API __declspec(dllimport) # else # define DATE_API # endif #else # ifdef DATE_BUILD_DLL # define DATE_API __attribute__ ((visibility ("default"))) # else # define DATE_API # endif #endif namespace date { enum class choose {earliest, latest}; #if defined(BUILD_TZ_LIB) # if defined(ANDROID) || defined(__ANDROID__) struct tzdb; static std::unique_ptr init_tzdb(); # endif // defined(ANDROID) || defined(__ANDROID__) #endif // defined(BUILD_TZ_LIB) namespace detail { struct undocumented; template struct nodeduct { using type = T; }; template using nodeduct_t = typename nodeduct::type; } struct sys_info { sys_seconds begin; sys_seconds end; std::chrono::seconds offset; std::chrono::minutes save; std::string abbrev; }; template std::basic_ostream& operator<<(std::basic_ostream& os, const sys_info& r) { os << r.begin << '\n'; os << r.end << '\n'; os << make_time(r.offset) << "\n"; os << make_time(r.save) << "\n"; os << r.abbrev << '\n'; return os; } struct local_info { enum {unique, nonexistent, ambiguous} result; sys_info first; sys_info second; }; template std::basic_ostream& operator<<(std::basic_ostream& os, const local_info& r) { if (r.result == local_info::nonexistent) os << "nonexistent between\n"; else if (r.result == local_info::ambiguous) os << "ambiguous between\n"; os << r.first; if (r.result != local_info::unique) { os << "and\n"; os << r.second; } return os; } class nonexistent_local_time : public std::runtime_error { public: template nonexistent_local_time(local_time tp, const local_info& i); private: template static std::string make_msg(local_time tp, const local_info& i); }; template inline nonexistent_local_time::nonexistent_local_time(local_time tp, const local_info& i) : std::runtime_error(make_msg(tp, i)) { } template std::string nonexistent_local_time::make_msg(local_time tp, const local_info& i) { assert(i.result == local_info::nonexistent); std::ostringstream os; os << tp << " is in a gap between\n" << local_seconds{i.first.end.time_since_epoch()} + i.first.offset << ' ' << i.first.abbrev << " and\n" << local_seconds{i.second.begin.time_since_epoch()} + i.second.offset << ' ' << i.second.abbrev << " which are both equivalent to\n"; date::operator<<(os, i.first.end) << " UTC"; return os.str(); } class ambiguous_local_time : public std::runtime_error { public: template ambiguous_local_time(local_time tp, const local_info& i); private: template static std::string make_msg(local_time tp, const local_info& i); }; template inline ambiguous_local_time::ambiguous_local_time(local_time tp, const local_info& i) : std::runtime_error(make_msg(tp, i)) { } template std::string ambiguous_local_time::make_msg(local_time tp, const local_info& i) { assert(i.result == local_info::ambiguous); std::ostringstream os; os << tp << " is ambiguous. It could be\n" << tp << ' ' << i.first.abbrev << " == " << tp - i.first.offset << " UTC or\n" << tp << ' ' << i.second.abbrev << " == " << tp - i.second.offset << " UTC"; return os.str(); } class time_zone; #if HAS_STRING_VIEW DATE_API const time_zone* locate_zone(std::string_view tz_name); #else DATE_API const time_zone* locate_zone(const std::string& tz_name); #endif DATE_API const time_zone* current_zone(); template struct zoned_traits { }; template <> struct zoned_traits { static const time_zone* default_zone() { return date::locate_zone("Etc/UTC"); } #if HAS_STRING_VIEW static const time_zone* locate_zone(std::string_view name) { return date::locate_zone(name); } #else // !HAS_STRING_VIEW static const time_zone* locate_zone(const std::string& name) { return date::locate_zone(name); } static const time_zone* locate_zone(const char* name) { return date::locate_zone(name); } #endif // !HAS_STRING_VIEW }; template class zoned_time; template bool operator==(const zoned_time& x, const zoned_time& y); template class zoned_time { public: using duration = typename std::common_type::type; private: TimeZonePtr zone_; sys_time tp_; public: #if !defined(_MSC_VER) || (_MSC_VER > 1916) template ::default_zone())> #endif zoned_time(); #if !defined(_MSC_VER) || (_MSC_VER > 1916) template ::default_zone())> #endif zoned_time(const sys_time& st); explicit zoned_time(TimeZonePtr z); #if HAS_STRING_VIEW template ::locate_zone(std::string_view())) >::value >::type> explicit zoned_time(std::string_view name); #else # if !defined(_MSC_VER) || (_MSC_VER > 1916) template ::locate_zone(std::string())) >::value >::type> # endif explicit zoned_time(const std::string& name); #endif template , sys_time>::value >::type> zoned_time(const zoned_time& zt) NOEXCEPT; zoned_time(TimeZonePtr z, const sys_time& st); #if !defined(_MSC_VER) || (_MSC_VER > 1916) template ()->to_sys(local_time{})), sys_time >::value >::type> #endif zoned_time(TimeZonePtr z, const local_time& tp); #if !defined(_MSC_VER) || (_MSC_VER > 1916) template ()->to_sys(local_time{}, choose::earliest)), sys_time >::value >::type> #endif zoned_time(TimeZonePtr z, const local_time& tp, choose c); template , sys_time>::value >::type> zoned_time(TimeZonePtr z, const zoned_time& zt); template , sys_time>::value >::type> zoned_time(TimeZonePtr z, const zoned_time& zt, choose); #if HAS_STRING_VIEW template ::locate_zone(std::string_view())), sys_time >::value >::type> zoned_time(std::string_view name, detail::nodeduct_t&> st); template ::locate_zone(std::string_view())), local_time >::value >::type> zoned_time(std::string_view name, detail::nodeduct_t&> tp); template ::locate_zone(std::string_view())), local_time, choose >::value >::type> zoned_time(std::string_view name, detail::nodeduct_t&> tp, choose c); template , sys_time>::value && std::is_constructible < zoned_time, decltype(zoned_traits::locate_zone(std::string_view())), zoned_time >::value >::type> zoned_time(std::string_view name, const zoned_time& zt); template , sys_time>::value && std::is_constructible < zoned_time, decltype(zoned_traits::locate_zone(std::string_view())), zoned_time, choose >::value >::type> zoned_time(std::string_view name, const zoned_time& zt, choose); #else // !HAS_STRING_VIEW #if !defined(_MSC_VER) || (_MSC_VER > 1916) template ::locate_zone(std::string())), sys_time >::value >::type> #endif zoned_time(const std::string& name, const sys_time& st); #if !defined(_MSC_VER) || (_MSC_VER > 1916) template ::locate_zone(std::string())), sys_time >::value >::type> #endif zoned_time(const char* name, const sys_time& st); #if !defined(_MSC_VER) || (_MSC_VER > 1916) template ::locate_zone(std::string())), local_time >::value >::type> #endif zoned_time(const std::string& name, const local_time& tp); #if !defined(_MSC_VER) || (_MSC_VER > 1916) template ::locate_zone(std::string())), local_time >::value >::type> #endif zoned_time(const char* name, const local_time& tp); #if !defined(_MSC_VER) || (_MSC_VER > 1916) template ::locate_zone(std::string())), local_time, choose >::value >::type> #endif zoned_time(const std::string& name, const local_time& tp, choose c); #if !defined(_MSC_VER) || (_MSC_VER > 1916) template ::locate_zone(std::string())), local_time, choose >::value >::type> #endif zoned_time(const char* name, const local_time& tp, choose c); #if !defined(_MSC_VER) || (_MSC_VER > 1916) template , sys_time>::value && std::is_constructible < zoned_time, decltype(zoned_traits::locate_zone(std::string())), zoned_time >::value >::type> #else template #endif zoned_time(const std::string& name, const zoned_time& zt); #if !defined(_MSC_VER) || (_MSC_VER > 1916) template , sys_time>::value && std::is_constructible < zoned_time, decltype(zoned_traits::locate_zone(std::string())), zoned_time >::value >::type> #else template #endif zoned_time(const char* name, const zoned_time& zt); #if !defined(_MSC_VER) || (_MSC_VER > 1916) template , sys_time>::value && std::is_constructible < zoned_time, decltype(zoned_traits::locate_zone(std::string())), zoned_time, choose >::value >::type> #else template #endif zoned_time(const std::string& name, const zoned_time& zt, choose); #if !defined(_MSC_VER) || (_MSC_VER > 1916) template , sys_time>::value && std::is_constructible < zoned_time, decltype(zoned_traits::locate_zone(std::string())), zoned_time, choose >::value >::type> #else template #endif zoned_time(const char* name, const zoned_time& zt, choose); #endif // !HAS_STRING_VIEW zoned_time& operator=(const sys_time& st); zoned_time& operator=(const local_time& ut); explicit operator sys_time() const; explicit operator local_time() const; TimeZonePtr get_time_zone() const; local_time get_local_time() const; sys_time get_sys_time() const; sys_info get_info() const; template friend bool operator==(const zoned_time& x, const zoned_time& y); template friend std::basic_ostream& operator<<(std::basic_ostream& os, const zoned_time& t); private: template friend class zoned_time; template static TimeZonePtr2&& check(TimeZonePtr2&& p); }; using zoned_seconds = zoned_time; #if HAS_DEDUCTION_GUIDES namespace detail { template using time_zone_representation = std::conditional_t < std::is_convertible::value, time_zone const*, std::remove_cv_t> >; } zoned_time() -> zoned_time; template zoned_time(sys_time) -> zoned_time>; template zoned_time(TimeZonePtrOrName&&) -> zoned_time>; template zoned_time(TimeZonePtrOrName&&, sys_time) -> zoned_time, detail::time_zone_representation>; template zoned_time(TimeZonePtrOrName&&, local_time, choose = choose::earliest) -> zoned_time, detail::time_zone_representation>; template zoned_time(TimeZonePtrOrName&&, zoned_time, choose = choose::earliest) -> zoned_time, detail::time_zone_representation>; #endif // HAS_DEDUCTION_GUIDES template inline bool operator==(const zoned_time& x, const zoned_time& y) { return x.zone_ == y.zone_ && x.tp_ == y.tp_; } template inline bool operator!=(const zoned_time& x, const zoned_time& y) { return !(x == y); } #if !defined(_MSC_VER) || (_MSC_VER >= 1900) namespace detail { # if USE_OS_TZDB struct transition; struct expanded_ttinfo; # else // !USE_OS_TZDB struct zonelet; class Rule; # endif // !USE_OS_TZDB } #endif // !defined(_MSC_VER) || (_MSC_VER >= 1900) class time_zone { private: std::string name_; #if USE_OS_TZDB std::vector transitions_; std::vector ttinfos_; #else // !USE_OS_TZDB std::vector zonelets_; #endif // !USE_OS_TZDB std::unique_ptr adjusted_; public: #if !defined(_MSC_VER) || (_MSC_VER >= 1900) time_zone(time_zone&&) = default; time_zone& operator=(time_zone&&) = default; #else // defined(_MSC_VER) && (_MSC_VER < 1900) time_zone(time_zone&& src); time_zone& operator=(time_zone&& src); #endif // defined(_MSC_VER) && (_MSC_VER < 1900) DATE_API explicit time_zone(const std::string& s, detail::undocumented); const std::string& name() const NOEXCEPT; template sys_info get_info(sys_time st) const; template local_info get_info(local_time tp) const; template sys_time::type> to_sys(local_time tp) const; template sys_time::type> to_sys(local_time tp, choose z) const; template local_time::type> to_local(sys_time tp) const; friend bool operator==(const time_zone& x, const time_zone& y) NOEXCEPT; friend bool operator< (const time_zone& x, const time_zone& y) NOEXCEPT; friend DATE_API std::ostream& operator<<(std::ostream& os, const time_zone& z); #if !USE_OS_TZDB DATE_API void add(const std::string& s); #else # if defined(BUILD_TZ_LIB) # if defined(ANDROID) || defined(__ANDROID__) friend std::unique_ptr init_tzdb(); # endif // defined(ANDROID) || defined(__ANDROID__) # endif // defined(BUILD_TZ_LIB) #endif // !USE_OS_TZDB private: DATE_API sys_info get_info_impl(sys_seconds tp) const; DATE_API local_info get_info_impl(local_seconds tp) const; template sys_time::type> to_sys_impl(local_time tp, choose z, std::false_type) const; template sys_time::type> to_sys_impl(local_time tp, choose, std::true_type) const; #if USE_OS_TZDB DATE_API void init() const; DATE_API void init_impl(); DATE_API sys_info load_sys_info(std::vector::const_iterator i) const; template DATE_API void load_data(std::istream& inf, std::int32_t tzh_leapcnt, std::int32_t tzh_timecnt, std::int32_t tzh_typecnt, std::int32_t tzh_charcnt); # if defined(ANDROID) || defined(__ANDROID__) void parse_from_android_tzdata(std::ifstream& inf, const std::size_t off); # endif // defined(ANDROID) || defined(__ANDROID__) #else // !USE_OS_TZDB DATE_API sys_info get_info_impl(sys_seconds tp, int tz_int) const; DATE_API void adjust_infos(const std::vector& rules); DATE_API void parse_info(std::istream& in); #endif // !USE_OS_TZDB }; #if defined(_MSC_VER) && (_MSC_VER < 1900) inline time_zone::time_zone(time_zone&& src) : name_(std::move(src.name_)) , zonelets_(std::move(src.zonelets_)) , adjusted_(std::move(src.adjusted_)) {} inline time_zone& time_zone::operator=(time_zone&& src) { name_ = std::move(src.name_); zonelets_ = std::move(src.zonelets_); adjusted_ = std::move(src.adjusted_); return *this; } #endif // defined(_MSC_VER) && (_MSC_VER < 1900) inline const std::string& time_zone::name() const NOEXCEPT { return name_; } template inline sys_info time_zone::get_info(sys_time st) const { return get_info_impl(date::floor(st)); } template inline local_info time_zone::get_info(local_time tp) const { return get_info_impl(date::floor(tp)); } template inline sys_time::type> time_zone::to_sys(local_time tp) const { return to_sys_impl(tp, choose{}, std::true_type{}); } template inline sys_time::type> time_zone::to_sys(local_time tp, choose z) const { return to_sys_impl(tp, z, std::false_type{}); } template inline local_time::type> time_zone::to_local(sys_time tp) const { using LT = local_time::type>; auto i = get_info(tp); return LT{(tp + i.offset).time_since_epoch()}; } inline bool operator==(const time_zone& x, const time_zone& y) NOEXCEPT {return x.name_ == y.name_;} inline bool operator< (const time_zone& x, const time_zone& y) NOEXCEPT {return x.name_ < y.name_;} inline bool operator!=(const time_zone& x, const time_zone& y) NOEXCEPT {return !(x == y);} inline bool operator> (const time_zone& x, const time_zone& y) NOEXCEPT {return y < x;} inline bool operator<=(const time_zone& x, const time_zone& y) NOEXCEPT {return !(y < x);} inline bool operator>=(const time_zone& x, const time_zone& y) NOEXCEPT {return !(x < y);} template sys_time::type> time_zone::to_sys_impl(local_time tp, choose z, std::false_type) const { auto i = get_info(tp); if (i.result == local_info::nonexistent) { return i.first.end; } else if (i.result == local_info::ambiguous) { if (z == choose::latest) return sys_time{tp.time_since_epoch()} - i.second.offset; } return sys_time{tp.time_since_epoch()} - i.first.offset; } template sys_time::type> time_zone::to_sys_impl(local_time tp, choose, std::true_type) const { auto i = get_info(tp); if (i.result == local_info::nonexistent) throw nonexistent_local_time(tp, i); else if (i.result == local_info::ambiguous) throw ambiguous_local_time(tp, i); return sys_time{tp.time_since_epoch()} - i.first.offset; } #if !USE_OS_TZDB class time_zone_link { private: std::string name_; std::string target_; public: DATE_API explicit time_zone_link(const std::string& s); const std::string& name() const {return name_;} const std::string& target() const {return target_;} friend bool operator==(const time_zone_link& x, const time_zone_link& y) {return x.name_ == y.name_;} friend bool operator< (const time_zone_link& x, const time_zone_link& y) {return x.name_ < y.name_;} friend DATE_API std::ostream& operator<<(std::ostream& os, const time_zone_link& x); }; using link = time_zone_link; inline bool operator!=(const time_zone_link& x, const time_zone_link& y) {return !(x == y);} inline bool operator> (const time_zone_link& x, const time_zone_link& y) {return y < x;} inline bool operator<=(const time_zone_link& x, const time_zone_link& y) {return !(y < x);} inline bool operator>=(const time_zone_link& x, const time_zone_link& y) {return !(x < y);} #endif // !USE_OS_TZDB class leap_second { private: sys_seconds date_; public: #if USE_OS_TZDB DATE_API explicit leap_second(const sys_seconds& s, detail::undocumented); #else DATE_API explicit leap_second(const std::string& s, detail::undocumented); #endif sys_seconds date() const {return date_;} friend bool operator==(const leap_second& x, const leap_second& y) {return x.date_ == y.date_;} friend bool operator< (const leap_second& x, const leap_second& y) {return x.date_ < y.date_;} template friend bool operator==(const leap_second& x, const sys_time& y) { return x.date_ == y; } template friend bool operator< (const leap_second& x, const sys_time& y) { return x.date_ < y; } template friend bool operator< (const sys_time& x, const leap_second& y) { return x < y.date_; } friend DATE_API std::ostream& operator<<(std::ostream& os, const leap_second& x); }; inline bool operator!=(const leap_second& x, const leap_second& y) {return !(x == y);} inline bool operator> (const leap_second& x, const leap_second& y) {return y < x;} inline bool operator<=(const leap_second& x, const leap_second& y) {return !(y < x);} inline bool operator>=(const leap_second& x, const leap_second& y) {return !(x < y);} template inline bool operator==(const sys_time& x, const leap_second& y) { return y == x; } template inline bool operator!=(const leap_second& x, const sys_time& y) { return !(x == y); } template inline bool operator!=(const sys_time& x, const leap_second& y) { return !(x == y); } template inline bool operator> (const leap_second& x, const sys_time& y) { return y < x; } template inline bool operator> (const sys_time& x, const leap_second& y) { return y < x; } template inline bool operator<=(const leap_second& x, const sys_time& y) { return !(y < x); } template inline bool operator<=(const sys_time& x, const leap_second& y) { return !(y < x); } template inline bool operator>=(const leap_second& x, const sys_time& y) { return !(x < y); } template inline bool operator>=(const sys_time& x, const leap_second& y) { return !(x < y); } using leap = leap_second; #ifdef _WIN32 namespace detail { // The time zone mapping is modelled after this data file: // http://unicode.org/repos/cldr/trunk/common/supplemental/windowsZones.xml // and the field names match the element names from the mapZone element // of windowsZones.xml. // The website displays this file here: // http://www.unicode.org/cldr/charts/latest/supplemental/zone_tzid.html // The html view is sorted before being displayed but is otherwise the same // There is a mapping between the os centric view (in this case windows) // the html displays uses and the generic view the xml file. // That mapping is this: // display column "windows" -> xml field "other". // display column "region" -> xml field "territory". // display column "tzid" -> xml field "type". // This structure uses the generic terminology because it could be // used to to support other os/native name conversions, not just windows, // and using the same generic names helps retain the connection to the // origin of the data that we are using. struct timezone_mapping { timezone_mapping(const char* other, const char* territory, const char* type) : other(other), territory(territory), type(type) { } timezone_mapping() = default; std::string other; std::string territory; std::string type; }; } // detail #endif // _WIN32 struct tzdb { std::string version = "unknown"; std::vector zones; #if !USE_OS_TZDB std::vector links; #endif std::vector leap_seconds; #if !USE_OS_TZDB std::vector rules; #endif #ifdef _WIN32 std::vector mappings; #endif tzdb* next = nullptr; tzdb() = default; #if !defined(_MSC_VER) || (_MSC_VER >= 1900) tzdb(tzdb&&) = default; tzdb& operator=(tzdb&&) = default; #else // defined(_MSC_VER) && (_MSC_VER < 1900) tzdb(tzdb&& src) : version(std::move(src.version)) , zones(std::move(src.zones)) , links(std::move(src.links)) , leap_seconds(std::move(src.leap_seconds)) , rules(std::move(src.rules)) , mappings(std::move(src.mappings)) {} tzdb& operator=(tzdb&& src) { version = std::move(src.version); zones = std::move(src.zones); links = std::move(src.links); leap_seconds = std::move(src.leap_seconds); rules = std::move(src.rules); mappings = std::move(src.mappings); return *this; } #endif // defined(_MSC_VER) && (_MSC_VER < 1900) #if HAS_STRING_VIEW DATE_API const time_zone* locate_zone(std::string_view tz_name) const; #else DATE_API const time_zone* locate_zone(const std::string& tz_name) const; #endif DATE_API const time_zone* current_zone() const; }; using TZ_DB = tzdb; DATE_API std::ostream& operator<<(std::ostream& os, const tzdb& db); DATE_API const tzdb& get_tzdb(); class tzdb_list { std::atomic head_{nullptr}; public: DATE_API ~tzdb_list(); tzdb_list() = default; DATE_API tzdb_list(tzdb_list&& x) NOEXCEPT; const tzdb& front() const NOEXCEPT {return *head_;} tzdb& front() NOEXCEPT {return *head_;} class const_iterator; const_iterator begin() const NOEXCEPT; const_iterator end() const NOEXCEPT; const_iterator cbegin() const NOEXCEPT; const_iterator cend() const NOEXCEPT; DATE_API const_iterator erase_after(const_iterator p) NOEXCEPT; struct undocumented_helper; private: void push_front(tzdb* tzdb) NOEXCEPT; }; class tzdb_list::const_iterator { tzdb* p_ = nullptr; explicit const_iterator(tzdb* p) NOEXCEPT : p_{p} {} public: const_iterator() = default; using iterator_category = std::forward_iterator_tag; using value_type = tzdb; using reference = const value_type&; using pointer = const value_type*; using difference_type = std::ptrdiff_t; reference operator*() const NOEXCEPT {return *p_;} pointer operator->() const NOEXCEPT {return p_;} const_iterator& operator++() NOEXCEPT {p_ = p_->next; return *this;} const_iterator operator++(int) NOEXCEPT {auto t = *this; ++(*this); return t;} friend bool operator==(const const_iterator& x, const const_iterator& y) NOEXCEPT {return x.p_ == y.p_;} friend bool operator!=(const const_iterator& x, const const_iterator& y) NOEXCEPT {return !(x == y);} friend class tzdb_list; }; inline tzdb_list::const_iterator tzdb_list::begin() const NOEXCEPT { return const_iterator{head_}; } inline tzdb_list::const_iterator tzdb_list::end() const NOEXCEPT { return const_iterator{nullptr}; } inline tzdb_list::const_iterator tzdb_list::cbegin() const NOEXCEPT { return begin(); } inline tzdb_list::const_iterator tzdb_list::cend() const NOEXCEPT { return end(); } DATE_API tzdb_list& get_tzdb_list(); #if !USE_OS_TZDB DATE_API const tzdb& reload_tzdb(); DATE_API void set_install(const std::string& install); #endif // !USE_OS_TZDB #if HAS_REMOTE_API DATE_API std::string remote_version(); // if provided error_buffer size should be at least CURL_ERROR_SIZE DATE_API bool remote_download(const std::string& version, char* error_buffer = nullptr); DATE_API bool remote_install(const std::string& version); #endif // zoned_time namespace detail { template inline T* to_raw_pointer(T* p) NOEXCEPT { return p; } template inline auto to_raw_pointer(Pointer p) NOEXCEPT -> decltype(detail::to_raw_pointer(p.operator->())) { return detail::to_raw_pointer(p.operator->()); } } // namespace detail template template inline TimeZonePtr2&& zoned_time::check(TimeZonePtr2&& p) { if (detail::to_raw_pointer(p) == nullptr) throw std::runtime_error( "zoned_time constructed with a time zone pointer == nullptr"); return std::forward(p); } template #if !defined(_MSC_VER) || (_MSC_VER > 1916) template #endif inline zoned_time::zoned_time() : zone_(check(zoned_traits::default_zone())) {} template #if !defined(_MSC_VER) || (_MSC_VER > 1916) template #endif inline zoned_time::zoned_time(const sys_time& st) : zone_(check(zoned_traits::default_zone())) , tp_(st) {} template inline zoned_time::zoned_time(TimeZonePtr z) : zone_(check(std::move(z))) {} #if HAS_STRING_VIEW template template inline zoned_time::zoned_time(std::string_view name) : zoned_time(zoned_traits::locate_zone(name)) {} #else // !HAS_STRING_VIEW template #if !defined(_MSC_VER) || (_MSC_VER > 1916) template #endif inline zoned_time::zoned_time(const std::string& name) : zoned_time(zoned_traits::locate_zone(name)) {} #endif // !HAS_STRING_VIEW template template inline zoned_time::zoned_time(const zoned_time& zt) NOEXCEPT : zone_(zt.zone_) , tp_(zt.tp_) {} template inline zoned_time::zoned_time(TimeZonePtr z, const sys_time& st) : zone_(check(std::move(z))) , tp_(st) {} template #if !defined(_MSC_VER) || (_MSC_VER > 1916) template #endif inline zoned_time::zoned_time(TimeZonePtr z, const local_time& t) : zone_(check(std::move(z))) , tp_(zone_->to_sys(t)) {} template #if !defined(_MSC_VER) || (_MSC_VER > 1916) template #endif inline zoned_time::zoned_time(TimeZonePtr z, const local_time& t, choose c) : zone_(check(std::move(z))) , tp_(zone_->to_sys(t, c)) {} template template inline zoned_time::zoned_time(TimeZonePtr z, const zoned_time& zt) : zone_(check(std::move(z))) , tp_(zt.tp_) {} template template inline zoned_time::zoned_time(TimeZonePtr z, const zoned_time& zt, choose) : zoned_time(std::move(z), zt) {} #if HAS_STRING_VIEW template template inline zoned_time::zoned_time(std::string_view name, detail::nodeduct_t&> st) : zoned_time(zoned_traits::locate_zone(name), st) {} template template inline zoned_time::zoned_time(std::string_view name, detail::nodeduct_t&> t) : zoned_time(zoned_traits::locate_zone(name), t) {} template template inline zoned_time::zoned_time(std::string_view name, detail::nodeduct_t&> t, choose c) : zoned_time(zoned_traits::locate_zone(name), t, c) {} template template inline zoned_time::zoned_time(std::string_view name, const zoned_time& zt) : zoned_time(zoned_traits::locate_zone(name), zt) {} template template inline zoned_time::zoned_time(std::string_view name, const zoned_time& zt, choose c) : zoned_time(zoned_traits::locate_zone(name), zt, c) {} #else // !HAS_STRING_VIEW template #if !defined(_MSC_VER) || (_MSC_VER > 1916) template #endif inline zoned_time::zoned_time(const std::string& name, const sys_time& st) : zoned_time(zoned_traits::locate_zone(name), st) {} template #if !defined(_MSC_VER) || (_MSC_VER > 1916) template #endif inline zoned_time::zoned_time(const char* name, const sys_time& st) : zoned_time(zoned_traits::locate_zone(name), st) {} template #if !defined(_MSC_VER) || (_MSC_VER > 1916) template #endif inline zoned_time::zoned_time(const std::string& name, const local_time& t) : zoned_time(zoned_traits::locate_zone(name), t) {} template #if !defined(_MSC_VER) || (_MSC_VER > 1916) template #endif inline zoned_time::zoned_time(const char* name, const local_time& t) : zoned_time(zoned_traits::locate_zone(name), t) {} template #if !defined(_MSC_VER) || (_MSC_VER > 1916) template #endif inline zoned_time::zoned_time(const std::string& name, const local_time& t, choose c) : zoned_time(zoned_traits::locate_zone(name), t, c) {} template #if !defined(_MSC_VER) || (_MSC_VER > 1916) template #endif inline zoned_time::zoned_time(const char* name, const local_time& t, choose c) : zoned_time(zoned_traits::locate_zone(name), t, c) {} template #if !defined(_MSC_VER) || (_MSC_VER > 1916) template #else template #endif inline zoned_time::zoned_time(const std::string& name, const zoned_time& zt) : zoned_time(zoned_traits::locate_zone(name), zt) {} template #if !defined(_MSC_VER) || (_MSC_VER > 1916) template #else template #endif inline zoned_time::zoned_time(const char* name, const zoned_time& zt) : zoned_time(zoned_traits::locate_zone(name), zt) {} template #if !defined(_MSC_VER) || (_MSC_VER > 1916) template #else template #endif inline zoned_time::zoned_time(const std::string& name, const zoned_time& zt, choose c) : zoned_time(zoned_traits::locate_zone(name), zt, c) {} template #if !defined(_MSC_VER) || (_MSC_VER > 1916) template #else template #endif inline zoned_time::zoned_time(const char* name, const zoned_time& zt, choose c) : zoned_time(zoned_traits::locate_zone(name), zt, c) {} #endif // HAS_STRING_VIEW template inline zoned_time& zoned_time::operator=(const sys_time& st) { tp_ = st; return *this; } template inline zoned_time& zoned_time::operator=(const local_time& ut) { tp_ = zone_->to_sys(ut); return *this; } template inline zoned_time::operator local_time::duration>() const { return get_local_time(); } template inline zoned_time::operator sys_time::duration>() const { return get_sys_time(); } template inline TimeZonePtr zoned_time::get_time_zone() const { return zone_; } template inline local_time::duration> zoned_time::get_local_time() const { return zone_->to_local(tp_); } template inline sys_time::duration> zoned_time::get_sys_time() const { return tp_; } template inline sys_info zoned_time::get_info() const { return zone_->get_info(tp_); } // make_zoned_time inline zoned_time make_zoned() { return zoned_time(); } template inline zoned_time::type> make_zoned(const sys_time& tp) { return zoned_time::type>(tp); } template 1916) #if !defined(__INTEL_COMPILER) || (__INTEL_COMPILER > 1600) , class = typename std::enable_if < std::is_class < typename std::decay < decltype(*detail::to_raw_pointer(std::declval())) >::type >{} >::type #endif #endif > inline zoned_time make_zoned(TimeZonePtr z) { return zoned_time(std::move(z)); } inline zoned_seconds make_zoned(const std::string& name) { return zoned_seconds(name); } template 1916) #if !defined(__INTEL_COMPILER) || (__INTEL_COMPILER > 1600) , class = typename std::enable_if < std::is_class())>::type>{} >::type #endif #endif > inline zoned_time::type, TimeZonePtr> make_zoned(TimeZonePtr zone, const local_time& tp) { return zoned_time::type, TimeZonePtr>(std::move(zone), tp); } template 1916) #if !defined(__INTEL_COMPILER) || (__INTEL_COMPILER > 1600) , class = typename std::enable_if < std::is_class())>::type>{} >::type #endif #endif > inline zoned_time::type, TimeZonePtr> make_zoned(TimeZonePtr zone, const local_time& tp, choose c) { return zoned_time::type, TimeZonePtr>(std::move(zone), tp, c); } template inline zoned_time::type> make_zoned(const std::string& name, const local_time& tp) { return zoned_time::type>(name, tp); } template inline zoned_time::type> make_zoned(const std::string& name, const local_time& tp, choose c) { return zoned_time::type>(name, tp, c); } template inline zoned_time make_zoned(TimeZonePtr zone, const zoned_time& zt) { return zoned_time(std::move(zone), zt); } template inline zoned_time make_zoned(const std::string& name, const zoned_time& zt) { return zoned_time(name, zt); } template inline zoned_time make_zoned(TimeZonePtr zone, const zoned_time& zt, choose c) { return zoned_time(std::move(zone), zt, c); } template inline zoned_time make_zoned(const std::string& name, const zoned_time& zt, choose c) { return zoned_time(name, zt, c); } template 1916) #if !defined(__INTEL_COMPILER) || (__INTEL_COMPILER > 1600) , class = typename std::enable_if < std::is_class())>::type>{} >::type #endif #endif > inline zoned_time::type, TimeZonePtr> make_zoned(TimeZonePtr zone, const sys_time& st) { return zoned_time::type, TimeZonePtr>(std::move(zone), st); } template inline zoned_time::type> make_zoned(const std::string& name, const sys_time& st) { return zoned_time::type>(name, st); } template std::basic_ostream& to_stream(std::basic_ostream& os, const CharT* fmt, const zoned_time& tp) { using duration = typename zoned_time::duration; using LT = local_time; auto const st = tp.get_sys_time(); auto const info = tp.get_time_zone()->get_info(st); return to_stream(os, fmt, LT{(st+info.offset).time_since_epoch()}, &info.abbrev, &info.offset); } template inline std::basic_ostream& operator<<(std::basic_ostream& os, const zoned_time& t) { const CharT fmt[] = {'%', 'F', ' ', '%', 'T', ' ', '%', 'Z', CharT{}}; return to_stream(os, fmt, t); } class utc_clock { public: using duration = std::chrono::system_clock::duration; using rep = duration::rep; using period = duration::period; using time_point = std::chrono::time_point; static CONSTDATA bool is_steady = false; static time_point now(); template static std::chrono::time_point::type> to_sys(const std::chrono::time_point&); template static std::chrono::time_point::type> from_sys(const std::chrono::time_point&); template static std::chrono::time_point::type> to_local(const std::chrono::time_point&); template static std::chrono::time_point::type> from_local(const std::chrono::time_point&); }; template using utc_time = std::chrono::time_point; using utc_seconds = utc_time; template utc_time::type> utc_clock::from_sys(const sys_time& st) { using std::chrono::seconds; using CD = typename std::common_type::type; auto const& leaps = get_tzdb().leap_seconds; auto const lt = std::upper_bound(leaps.begin(), leaps.end(), st); return utc_time{st.time_since_epoch() + seconds{lt-leaps.begin()}}; } // Return pair // first is true if ut is during a leap second insertion, otherwise false. // If ut is during a leap second insertion, that leap second is included in the count template std::pair is_leap_second(date::utc_time const& ut) { using std::chrono::seconds; using duration = typename std::common_type::type; auto const& leaps = get_tzdb().leap_seconds; auto tp = sys_time{ut.time_since_epoch()}; auto const lt = std::upper_bound(leaps.begin(), leaps.end(), tp); auto ds = seconds{lt-leaps.begin()}; tp -= ds; auto ls = false; if (lt > leaps.begin()) { if (tp < lt[-1]) { if (tp >= lt[-1].date() - seconds{1}) ls = true; else --ds; } } return {ls, ds}; } struct leap_second_info { bool is_leap_second; std::chrono::seconds elapsed; }; template leap_second_info get_leap_second_info(date::utc_time const& ut) { auto p = is_leap_second(ut); return {p.first, p.second}; } template sys_time::type> utc_clock::to_sys(const utc_time& ut) { using std::chrono::seconds; using CD = typename std::common_type::type; auto ls = is_leap_second(ut); auto tp = sys_time{ut.time_since_epoch() - ls.second}; if (ls.first) tp = floor(tp) + seconds{1} - CD{1}; return tp; } inline utc_clock::time_point utc_clock::now() { return from_sys(std::chrono::system_clock::now()); } template utc_time::type> utc_clock::from_local(const local_time& st) { return from_sys(sys_time{st.time_since_epoch()}); } template local_time::type> utc_clock::to_local(const utc_time& ut) { using CD = typename std::common_type::type; return local_time{to_sys(ut).time_since_epoch()}; } template std::basic_ostream& to_stream(std::basic_ostream& os, const CharT* fmt, const utc_time& t) { using std::chrono::seconds; using CT = typename std::common_type::type; const std::string abbrev("UTC"); CONSTDATA seconds offset{0}; auto ls = is_leap_second(t); auto tp = sys_time{t.time_since_epoch() - ls.second}; auto const sd = floor(tp); year_month_day ymd = sd; auto time = make_time(tp - sys_seconds{sd}); time.seconds(detail::undocumented{}) += seconds{ls.first}; fields fds{ymd, time}; return to_stream(os, fmt, fds, &abbrev, &offset); } template std::basic_ostream& operator<<(std::basic_ostream& os, const utc_time& t) { const CharT fmt[] = {'%', 'F', ' ', '%', 'T', CharT{}}; return to_stream(os, fmt, t); } template > std::basic_istream& from_stream(std::basic_istream& is, const CharT* fmt, utc_time& tp, std::basic_string* abbrev = nullptr, std::chrono::minutes* offset = nullptr) { using std::chrono::seconds; using std::chrono::minutes; using CT = typename std::common_type::type; minutes offset_local{}; auto offptr = offset ? offset : &offset_local; fields fds{}; fds.has_tod = true; from_stream(is, fmt, fds, abbrev, offptr); if (!fds.ymd.ok()) is.setstate(std::ios::failbit); if (!is.fail()) { bool is_60_sec = fds.tod.seconds() == seconds{60}; if (is_60_sec) fds.tod.seconds(detail::undocumented{}) -= seconds{1}; auto tmp = utc_clock::from_sys(sys_days(fds.ymd) - *offptr + fds.tod.to_duration()); if (is_60_sec) tmp += seconds{1}; if (is_60_sec != is_leap_second(tmp).first || !fds.tod.in_conventional_range()) { is.setstate(std::ios::failbit); return is; } tp = std::chrono::time_point_cast(tmp); } return is; } // tai_clock class tai_clock { public: using duration = std::chrono::system_clock::duration; using rep = duration::rep; using period = duration::period; using time_point = std::chrono::time_point; static const bool is_steady = false; static time_point now(); template static std::chrono::time_point::type> to_utc(const std::chrono::time_point&) NOEXCEPT; template static std::chrono::time_point::type> from_utc(const std::chrono::time_point&) NOEXCEPT; template static std::chrono::time_point::type> to_local(const std::chrono::time_point&) NOEXCEPT; template static std::chrono::time_point::type> from_local(const std::chrono::time_point&) NOEXCEPT; }; template using tai_time = std::chrono::time_point; using tai_seconds = tai_time; template inline utc_time::type> tai_clock::to_utc(const tai_time& t) NOEXCEPT { using std::chrono::seconds; using CD = typename std::common_type::type; return utc_time{t.time_since_epoch()} - (sys_days(year{1970}/January/1) - sys_days(year{1958}/January/1) + seconds{10}); } template inline tai_time::type> tai_clock::from_utc(const utc_time& t) NOEXCEPT { using std::chrono::seconds; using CD = typename std::common_type::type; return tai_time{t.time_since_epoch()} + (sys_days(year{1970}/January/1) - sys_days(year{1958}/January/1) + seconds{10}); } inline tai_clock::time_point tai_clock::now() { return from_utc(utc_clock::now()); } template inline local_time::type> tai_clock::to_local(const tai_time& t) NOEXCEPT { using CD = typename std::common_type::type; return local_time{t.time_since_epoch()} - (local_days(year{1970}/January/1) - local_days(year{1958}/January/1)); } template inline tai_time::type> tai_clock::from_local(const local_time& t) NOEXCEPT { using CD = typename std::common_type::type; return tai_time{t.time_since_epoch()} + (local_days(year{1970}/January/1) - local_days(year{1958}/January/1)); } template std::basic_ostream& to_stream(std::basic_ostream& os, const CharT* fmt, const tai_time& t) { const std::string abbrev("TAI"); CONSTDATA std::chrono::seconds offset{0}; return to_stream(os, fmt, tai_clock::to_local(t), &abbrev, &offset); } template std::basic_ostream& operator<<(std::basic_ostream& os, const tai_time& t) { const CharT fmt[] = {'%', 'F', ' ', '%', 'T', CharT{}}; return to_stream(os, fmt, t); } template > std::basic_istream& from_stream(std::basic_istream& is, const CharT* fmt, tai_time& tp, std::basic_string* abbrev = nullptr, std::chrono::minutes* offset = nullptr) { local_time lp; from_stream(is, fmt, lp, abbrev, offset); if (!is.fail()) tp = tai_clock::from_local(lp); return is; } // gps_clock class gps_clock { public: using duration = std::chrono::system_clock::duration; using rep = duration::rep; using period = duration::period; using time_point = std::chrono::time_point; static const bool is_steady = false; static time_point now(); template static std::chrono::time_point::type> to_utc(const std::chrono::time_point&) NOEXCEPT; template static std::chrono::time_point::type> from_utc(const std::chrono::time_point&) NOEXCEPT; template static std::chrono::time_point::type> to_local(const std::chrono::time_point&) NOEXCEPT; template static std::chrono::time_point::type> from_local(const std::chrono::time_point&) NOEXCEPT; }; template using gps_time = std::chrono::time_point; using gps_seconds = gps_time; template inline utc_time::type> gps_clock::to_utc(const gps_time& t) NOEXCEPT { using std::chrono::seconds; using CD = typename std::common_type::type; return utc_time{t.time_since_epoch()} + (sys_days(year{1980}/January/Sunday[1]) - sys_days(year{1970}/January/1) + seconds{9}); } template inline gps_time::type> gps_clock::from_utc(const utc_time& t) NOEXCEPT { using std::chrono::seconds; using CD = typename std::common_type::type; return gps_time{t.time_since_epoch()} - (sys_days(year{1980}/January/Sunday[1]) - sys_days(year{1970}/January/1) + seconds{9}); } inline gps_clock::time_point gps_clock::now() { return from_utc(utc_clock::now()); } template inline local_time::type> gps_clock::to_local(const gps_time& t) NOEXCEPT { using CD = typename std::common_type::type; return local_time{t.time_since_epoch()} + (local_days(year{1980}/January/Sunday[1]) - local_days(year{1970}/January/1)); } template inline gps_time::type> gps_clock::from_local(const local_time& t) NOEXCEPT { using CD = typename std::common_type::type; return gps_time{t.time_since_epoch()} - (local_days(year{1980}/January/Sunday[1]) - local_days(year{1970}/January/1)); } template std::basic_ostream& to_stream(std::basic_ostream& os, const CharT* fmt, const gps_time& t) { const std::string abbrev("GPS"); CONSTDATA std::chrono::seconds offset{0}; return to_stream(os, fmt, gps_clock::to_local(t), &abbrev, &offset); } template std::basic_ostream& operator<<(std::basic_ostream& os, const gps_time& t) { const CharT fmt[] = {'%', 'F', ' ', '%', 'T', CharT{}}; return to_stream(os, fmt, t); } template > std::basic_istream& from_stream(std::basic_istream& is, const CharT* fmt, gps_time& tp, std::basic_string* abbrev = nullptr, std::chrono::minutes* offset = nullptr) { local_time lp; from_stream(is, fmt, lp, abbrev, offset); if (!is.fail()) tp = gps_clock::from_local(lp); return is; } // clock_time_conversion template struct clock_time_conversion {}; template <> struct clock_time_conversion { template CONSTCD14 sys_time operator()(const sys_time& st) const { return st; } }; template <> struct clock_time_conversion { template CONSTCD14 utc_time operator()(const utc_time& ut) const { return ut; } }; template<> struct clock_time_conversion { template CONSTCD14 local_time operator()(const local_time& lt) const { return lt; } }; template <> struct clock_time_conversion { template utc_time::type> operator()(const sys_time& st) const { return utc_clock::from_sys(st); } }; template <> struct clock_time_conversion { template sys_time::type> operator()(const utc_time& ut) const { return utc_clock::to_sys(ut); } }; template<> struct clock_time_conversion { template CONSTCD14 local_time operator()(const sys_time& st) const { return local_time{st.time_since_epoch()}; } }; template<> struct clock_time_conversion { template CONSTCD14 sys_time operator()(const local_time& lt) const { return sys_time{lt.time_since_epoch()}; } }; template<> struct clock_time_conversion { template utc_time::type> operator()(const local_time& lt) const { return utc_clock::from_local(lt); } }; template<> struct clock_time_conversion { template local_time::type> operator()(const utc_time& ut) const { return utc_clock::to_local(ut); } }; template struct clock_time_conversion { template CONSTCD14 std::chrono::time_point operator()(const std::chrono::time_point& tp) const { return tp; } }; namespace ctc_detail { template using time_point = std::chrono::time_point; using std::declval; using std::chrono::system_clock; //Check if TimePoint is time for given clock, //if not emits hard error template struct return_clock_time { using clock_time_point = time_point; using type = TimePoint; static_assert(std::is_same::value, "time point with appropariate clock shall be returned"); }; // Check if Clock has to_sys method accepting TimePoint with given duration const& and // returning sys_time. If so has nested type member equal to return type to_sys. template struct return_to_sys {}; template struct return_to_sys < Clock, Duration, decltype(Clock::to_sys(declval const&>()), void()) > : return_clock_time < system_clock, decltype(Clock::to_sys(declval const&>())) > {}; // Similiar to above template struct return_from_sys {}; template struct return_from_sys < Clock, Duration, decltype(Clock::from_sys(declval const&>()), void()) > : return_clock_time < Clock, decltype(Clock::from_sys(declval const&>())) > {}; // Similiar to above template struct return_to_utc {}; template struct return_to_utc < Clock, Duration, decltype(Clock::to_utc(declval const&>()), void()) > : return_clock_time < utc_clock, decltype(Clock::to_utc(declval const&>()))> {}; // Similiar to above template struct return_from_utc {}; template struct return_from_utc < Clock, Duration, decltype(Clock::from_utc(declval const&>()), void()) > : return_clock_time < Clock, decltype(Clock::from_utc(declval const&>())) > {}; // Similiar to above template struct return_to_local {}; template struct return_to_local < Clock, Duration, decltype(Clock::to_local(declval const&>()), void()) > : return_clock_time < local_t, decltype(Clock::to_local(declval const&>())) > {}; // Similiar to above template struct return_from_local {}; template struct return_from_local < Clock, Duration, decltype(Clock::from_local(declval const&>()), void()) > : return_clock_time < Clock, decltype(Clock::from_local(declval const&>())) > {}; } // namespace ctc_detail template struct clock_time_conversion { template CONSTCD14 typename ctc_detail::return_to_sys::type operator()(const std::chrono::time_point& tp) const { return SrcClock::to_sys(tp); } }; template struct clock_time_conversion { template CONSTCD14 typename ctc_detail::return_from_sys::type operator()(const sys_time& st) const { return DstClock::from_sys(st); } }; template struct clock_time_conversion { template CONSTCD14 typename ctc_detail::return_to_utc::type operator()(const std::chrono::time_point& tp) const { return SrcClock::to_utc(tp); } }; template struct clock_time_conversion { template CONSTCD14 typename ctc_detail::return_from_utc::type operator()(const utc_time& ut) const { return DstClock::from_utc(ut); } }; template struct clock_time_conversion { template CONSTCD14 typename ctc_detail::return_to_local::type operator()(const std::chrono::time_point& tp) const { return SrcClock::to_local(tp); } }; template struct clock_time_conversion { template CONSTCD14 typename ctc_detail::return_from_local::type operator()(const local_time& lt) const { return DstClock::from_local(lt); } }; namespace clock_cast_detail { template using time_point = std::chrono::time_point; using std::chrono::system_clock; template CONSTCD14 auto conv_clock(const time_point& t) -> decltype(std::declval>()(t)) { return clock_time_conversion{}(t); } //direct trait conversion, 1st candidate template CONSTCD14 auto cc_impl(const time_point& t, const time_point*) -> decltype(conv_clock(t)) { return conv_clock(t); } //conversion through sys, 2nd candidate template CONSTCD14 auto cc_impl(const time_point& t, const void*) -> decltype(conv_clock(conv_clock(t))) { return conv_clock(conv_clock(t)); } //conversion through utc, 2nd candidate template CONSTCD14 auto cc_impl(const time_point& t, const void*) -> decltype(0, // MSVC_WORKAROUND conv_clock(conv_clock(t))) { return conv_clock(conv_clock(t)); } //conversion through sys and utc, 3rd candidate template CONSTCD14 auto cc_impl(const time_point& t, ...) -> decltype(conv_clock(conv_clock(conv_clock(t)))) { return conv_clock(conv_clock(conv_clock(t))); } //conversion through utc and sys, 3rd candidate template CONSTCD14 auto cc_impl(const time_point& t, ...) -> decltype(0, // MSVC_WORKAROUND conv_clock(conv_clock(conv_clock(t)))) { return conv_clock(conv_clock(conv_clock(t))); } } // namespace clock_cast_detail template CONSTCD14 auto clock_cast(const std::chrono::time_point& tp) -> decltype(clock_cast_detail::cc_impl(tp, &tp)) { return clock_cast_detail::cc_impl(tp, &tp); } // Deprecated API template inline sys_time::type> to_sys_time(const utc_time& t) { return utc_clock::to_sys(t); } template inline sys_time::type> to_sys_time(const tai_time& t) { return utc_clock::to_sys(tai_clock::to_utc(t)); } template inline sys_time::type> to_sys_time(const gps_time& t) { return utc_clock::to_sys(gps_clock::to_utc(t)); } template inline utc_time::type> to_utc_time(const sys_time& t) { return utc_clock::from_sys(t); } template inline utc_time::type> to_utc_time(const tai_time& t) { return tai_clock::to_utc(t); } template inline utc_time::type> to_utc_time(const gps_time& t) { return gps_clock::to_utc(t); } template inline tai_time::type> to_tai_time(const sys_time& t) { return tai_clock::from_utc(utc_clock::from_sys(t)); } template inline tai_time::type> to_tai_time(const utc_time& t) { return tai_clock::from_utc(t); } template inline tai_time::type> to_tai_time(const gps_time& t) { return tai_clock::from_utc(gps_clock::to_utc(t)); } template inline gps_time::type> to_gps_time(const sys_time& t) { return gps_clock::from_utc(utc_clock::from_sys(t)); } template inline gps_time::type> to_gps_time(const utc_time& t) { return gps_clock::from_utc(t); } template inline gps_time::type> to_gps_time(const tai_time& t) { return gps_clock::from_utc(tai_clock::to_utc(t)); } } // namespace date #endif // TZ_H pr0m1th3as-datatypes-9c9a8d3/src/date/tz_private.h000066400000000000000000000246621522766574100221330ustar00rootroot00000000000000#ifndef TZ_PRIVATE_H #define TZ_PRIVATE_H // The MIT License (MIT) // // Copyright (c) 2015, 2016 Howard Hinnant // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. // // Our apologies. When the previous paragraph was written, lowercase had not yet // been invented (that would involve another several millennia of evolution). // We did not mean to shout. #if !defined(_MSC_VER) || (_MSC_VER >= 1900) #include "tz.h" #else #include "date.h" #include #endif namespace date { namespace detail { #if !USE_OS_TZDB enum class tz {utc, local, standard}; //forward declare to avoid warnings in gcc 6.2 class MonthDayTime; std::istream& operator>>(std::istream& is, MonthDayTime& x); std::ostream& operator<<(std::ostream& os, const MonthDayTime& x); class MonthDayTime { private: struct pair { #if defined(_MSC_VER) && (_MSC_VER < 1900) pair() : month_day_(date::jan / 1), weekday_(0U) {} pair(const date::month_day& month_day, const date::weekday& weekday) : month_day_(month_day), weekday_(weekday) {} #endif date::month_day month_day_; date::weekday weekday_; }; enum Type {month_day, month_last_dow, lteq, gteq}; Type type_{month_day}; #if !defined(_MSC_VER) || (_MSC_VER >= 1900) union U #else struct U #endif { date::month_day month_day_; date::month_weekday_last month_weekday_last_; pair month_day_weekday_; #if !defined(_MSC_VER) || (_MSC_VER >= 1900) U() : month_day_{date::jan/1} {} #else U() : month_day_(date::jan/1), month_weekday_last_(date::month(0U), date::weekday_last(date::weekday(0U))) {} #endif // !defined(_MSC_VER) || (_MSC_VER >= 1900) U& operator=(const date::month_day& x); U& operator=(const date::month_weekday_last& x); U& operator=(const pair& x); } u; std::chrono::hours h_{0}; std::chrono::minutes m_{0}; std::chrono::seconds s_{0}; tz zone_{tz::local}; public: MonthDayTime() = default; MonthDayTime(local_seconds tp, tz timezone); MonthDayTime(const date::month_day& md, tz timezone); date::day day() const; date::month month() const; tz zone() const {return zone_;} void canonicalize(date::year y); sys_seconds to_sys(date::year y, std::chrono::seconds offset, std::chrono::seconds save) const; sys_days to_sys_days(date::year y) const; sys_seconds to_time_point(date::year y) const; int compare(date::year y, const MonthDayTime& x, date::year yx, std::chrono::seconds offset, std::chrono::minutes prev_save) const; friend std::istream& operator>>(std::istream& is, MonthDayTime& x); friend std::ostream& operator<<(std::ostream& os, const MonthDayTime& x); }; // A Rule specifies one or more set of datetimes without using an offset. // Multiple dates are specified with multiple years. The years in effect // go from starting_year_ to ending_year_, inclusive. starting_year_ <= // ending_year_. save_ is in effect for times from the specified time // onward, including the specified time. When the specified time is // local, it uses the save_ from the chronologically previous Rule, or if // there is none, 0. //forward declare to avoid warnings in gcc 6.2 class Rule; bool operator==(const Rule& x, const Rule& y); bool operator<(const Rule& x, const Rule& y); bool operator==(const Rule& x, const date::year& y); bool operator<(const Rule& x, const date::year& y); bool operator==(const date::year& x, const Rule& y); bool operator<(const date::year& x, const Rule& y); bool operator==(const Rule& x, const std::string& y); bool operator<(const Rule& x, const std::string& y); bool operator==(const std::string& x, const Rule& y); bool operator<(const std::string& x, const Rule& y); std::ostream& operator<<(std::ostream& os, const Rule& r); class Rule { private: std::string name_; date::year starting_year_{0}; date::year ending_year_{0}; MonthDayTime starting_at_; std::chrono::minutes save_{0}; std::string abbrev_; public: Rule() = default; explicit Rule(const std::string& s); Rule(const Rule& r, date::year starting_year, date::year ending_year); const std::string& name() const {return name_;} const std::string& abbrev() const {return abbrev_;} const MonthDayTime& mdt() const {return starting_at_;} const date::year& starting_year() const {return starting_year_;} const date::year& ending_year() const {return ending_year_;} const std::chrono::minutes& save() const {return save_;} static void split_overlaps(std::vector& rules); friend bool operator==(const Rule& x, const Rule& y); friend bool operator<(const Rule& x, const Rule& y); friend bool operator==(const Rule& x, const date::year& y); friend bool operator<(const Rule& x, const date::year& y); friend bool operator==(const date::year& x, const Rule& y); friend bool operator<(const date::year& x, const Rule& y); friend bool operator==(const Rule& x, const std::string& y); friend bool operator<(const Rule& x, const std::string& y); friend bool operator==(const std::string& x, const Rule& y); friend bool operator<(const std::string& x, const Rule& y); friend std::ostream& operator<<(std::ostream& os, const Rule& r); private: date::day day() const; date::month month() const; static void split_overlaps(std::vector& rules, std::size_t i, std::size_t& e); static bool overlaps(const Rule& x, const Rule& y); static void split(std::vector& rules, std::size_t i, std::size_t k, std::size_t& e); }; inline bool operator!=(const Rule& x, const Rule& y) {return !(x == y);} inline bool operator> (const Rule& x, const Rule& y) {return y < x;} inline bool operator<=(const Rule& x, const Rule& y) {return !(y < x);} inline bool operator>=(const Rule& x, const Rule& y) {return !(x < y);} inline bool operator!=(const Rule& x, const date::year& y) {return !(x == y);} inline bool operator> (const Rule& x, const date::year& y) {return y < x;} inline bool operator<=(const Rule& x, const date::year& y) {return !(y < x);} inline bool operator>=(const Rule& x, const date::year& y) {return !(x < y);} inline bool operator!=(const date::year& x, const Rule& y) {return !(x == y);} inline bool operator> (const date::year& x, const Rule& y) {return y < x;} inline bool operator<=(const date::year& x, const Rule& y) {return !(y < x);} inline bool operator>=(const date::year& x, const Rule& y) {return !(x < y);} inline bool operator!=(const Rule& x, const std::string& y) {return !(x == y);} inline bool operator> (const Rule& x, const std::string& y) {return y < x;} inline bool operator<=(const Rule& x, const std::string& y) {return !(y < x);} inline bool operator>=(const Rule& x, const std::string& y) {return !(x < y);} inline bool operator!=(const std::string& x, const Rule& y) {return !(x == y);} inline bool operator> (const std::string& x, const Rule& y) {return y < x;} inline bool operator<=(const std::string& x, const Rule& y) {return !(y < x);} inline bool operator>=(const std::string& x, const Rule& y) {return !(x < y);} struct zonelet { enum tag {has_rule, has_save, is_empty}; std::chrono::seconds gmtoff_; tag tag_ = has_rule; #if !defined(_MSC_VER) || (_MSC_VER >= 1900) union U #else struct U #endif { std::string rule_; std::chrono::minutes save_; ~U() {} U() {} U(const U&) {} U& operator=(const U&) = delete; } u; std::string format_; date::year until_year_{0}; MonthDayTime until_date_; sys_seconds until_utc_; local_seconds until_std_; local_seconds until_loc_; std::chrono::minutes initial_save_{0}; std::string initial_abbrev_; std::pair first_rule_{nullptr, date::year::min()}; std::pair last_rule_{nullptr, date::year::max()}; ~zonelet(); zonelet(); zonelet(const zonelet& i); zonelet& operator=(const zonelet&) = delete; }; #else // USE_OS_TZDB struct ttinfo { std::int32_t tt_gmtoff; unsigned char tt_isdst; unsigned char tt_abbrind; unsigned char pad[2]; }; static_assert(sizeof(ttinfo) == 8, ""); struct expanded_ttinfo { std::chrono::seconds offset; std::string abbrev; bool is_dst; }; struct transition { sys_seconds timepoint; const expanded_ttinfo* info; transition(sys_seconds tp, const expanded_ttinfo* i = nullptr) : timepoint(tp) , info(i) {} friend std::ostream& operator<<(std::ostream& os, const transition& t) { date::operator<<(os, t.timepoint) << "Z "; if (t.info->offset >= std::chrono::seconds{0}) os << '+'; os << make_time(t.info->offset); if (t.info->is_dst > 0) os << " daylight "; else os << " standard "; os << t.info->abbrev; return os; } }; #endif // USE_OS_TZDB } // namespace detail } // namespace date #if defined(_MSC_VER) && (_MSC_VER < 1900) #include "tz.h" #endif #endif // TZ_PRIVATE_H pr0m1th3as-datatypes-9c9a8d3/src/ods/000077500000000000000000000000001522766574100174315ustar00rootroot00000000000000pr0m1th3as-datatypes-9c9a8d3/src/ods/LICENSE.md000066400000000000000000000020671522766574100210420ustar00rootroot00000000000000MIT License Copyright (c) 2006-2023 Arseny Kapoulkine Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. pr0m1th3as-datatypes-9c9a8d3/src/ods/miniz.c000066400000000000000000011657651522766574100207500ustar00rootroot00000000000000#include "miniz.h" /************************************************************************** * * Copyright 2013-2014 RAD Game Tools and Valve Software * Copyright 2010-2014 Rich Geldreich and Tenacious Software LLC * All Rights Reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. * **************************************************************************/ typedef unsigned char mz_validate_uint16[sizeof(mz_uint16) == 2 ? 1 : -1]; typedef unsigned char mz_validate_uint32[sizeof(mz_uint32) == 4 ? 1 : -1]; typedef unsigned char mz_validate_uint64[sizeof(mz_uint64) == 8 ? 1 : -1]; #ifdef __cplusplus extern "C" { #endif /* ------------------- zlib-style API's */ mz_ulong mz_adler32(mz_ulong adler, const unsigned char *ptr, size_t buf_len) { mz_uint32 i, s1 = (mz_uint32)(adler & 0xffff), s2 = (mz_uint32)(adler >> 16); size_t block_len = buf_len % 5552; if (!ptr) return MZ_ADLER32_INIT; while (buf_len) { for (i = 0; i + 7 < block_len; i += 8, ptr += 8) { s1 += ptr[0], s2 += s1; s1 += ptr[1], s2 += s1; s1 += ptr[2], s2 += s1; s1 += ptr[3], s2 += s1; s1 += ptr[4], s2 += s1; s1 += ptr[5], s2 += s1; s1 += ptr[6], s2 += s1; s1 += ptr[7], s2 += s1; } for (; i < block_len; ++i) s1 += *ptr++, s2 += s1; s1 %= 65521U, s2 %= 65521U; buf_len -= block_len; block_len = 5552; } return (s2 << 16) + s1; } /* Karl Malbrain's compact CRC-32. See "A compact CCITT crc16 and crc32 C implementation that balances processor cache usage against speed": http://www.geocities.com/malbrain/ */ #if 0 mz_ulong mz_crc32(mz_ulong crc, const mz_uint8 *ptr, size_t buf_len) { static const mz_uint32 s_crc32[16] = { 0, 0x1db71064, 0x3b6e20c8, 0x26d930ac, 0x76dc4190, 0x6b6b51f4, 0x4db26158, 0x5005713c, 0xedb88320, 0xf00f9344, 0xd6d6a3e8, 0xcb61b38c, 0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c }; mz_uint32 crcu32 = (mz_uint32)crc; if (!ptr) return MZ_CRC32_INIT; crcu32 = ~crcu32; while (buf_len--) { mz_uint8 b = *ptr++; crcu32 = (crcu32 >> 4) ^ s_crc32[(crcu32 & 0xF) ^ (b & 0xF)]; crcu32 = (crcu32 >> 4) ^ s_crc32[(crcu32 & 0xF) ^ (b >> 4)]; } return ~crcu32; } #elif defined(USE_EXTERNAL_MZCRC) /* If USE_EXTERNAL_CRC is defined, an external module will export the * mz_crc32() symbol for us to use, e.g. an SSE-accelerated version. * Depending on the impl, it may be necessary to ~ the input/output crc values. */ mz_ulong mz_crc32(mz_ulong crc, const mz_uint8 *ptr, size_t buf_len); #else /* Faster, but larger CPU cache footprint. */ mz_ulong mz_crc32(mz_ulong crc, const mz_uint8 *ptr, size_t buf_len) { static const mz_uint32 s_crc_table[256] = { 0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3, 0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91, 0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, 0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5, 0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172, 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, 0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59, 0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F, 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D, 0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433, 0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01, 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457, 0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65, 0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB, 0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0, 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9, 0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F, 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD, 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, 0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683, 0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1, 0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7, 0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC, 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, 0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B, 0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79, 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F, 0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D, 0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713, 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, 0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777, 0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45, 0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, 0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB, 0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9, 0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF, 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D }; mz_uint32 crc32 = (mz_uint32)crc ^ 0xFFFFFFFF; const mz_uint8 *pByte_buf = (const mz_uint8 *)ptr; while (buf_len >= 4) { crc32 = (crc32 >> 8) ^ s_crc_table[(crc32 ^ pByte_buf[0]) & 0xFF]; crc32 = (crc32 >> 8) ^ s_crc_table[(crc32 ^ pByte_buf[1]) & 0xFF]; crc32 = (crc32 >> 8) ^ s_crc_table[(crc32 ^ pByte_buf[2]) & 0xFF]; crc32 = (crc32 >> 8) ^ s_crc_table[(crc32 ^ pByte_buf[3]) & 0xFF]; pByte_buf += 4; buf_len -= 4; } while (buf_len) { crc32 = (crc32 >> 8) ^ s_crc_table[(crc32 ^ pByte_buf[0]) & 0xFF]; ++pByte_buf; --buf_len; } return ~crc32; } #endif void mz_free(void *p) { MZ_FREE(p); } MINIZ_EXPORT void *miniz_def_alloc_func(void *opaque, size_t items, size_t size) { (void)opaque, (void)items, (void)size; return MZ_MALLOC(items * size); } MINIZ_EXPORT void miniz_def_free_func(void *opaque, void *address) { (void)opaque, (void)address; MZ_FREE(address); } MINIZ_EXPORT void *miniz_def_realloc_func(void *opaque, void *address, size_t items, size_t size) { (void)opaque, (void)address, (void)items, (void)size; return MZ_REALLOC(address, items * size); } const char *mz_version(void) { return MZ_VERSION; } #ifndef MINIZ_NO_ZLIB_APIS #ifndef MINIZ_NO_DEFLATE_APIS int mz_deflateInit(mz_streamp pStream, int level) { return mz_deflateInit2(pStream, level, MZ_DEFLATED, MZ_DEFAULT_WINDOW_BITS, 9, MZ_DEFAULT_STRATEGY); } int mz_deflateInit2(mz_streamp pStream, int level, int method, int window_bits, int mem_level, int strategy) { tdefl_compressor *pComp; mz_uint comp_flags = TDEFL_COMPUTE_ADLER32 | tdefl_create_comp_flags_from_zip_params(level, window_bits, strategy); if (!pStream) return MZ_STREAM_ERROR; if ((method != MZ_DEFLATED) || ((mem_level < 1) || (mem_level > 9)) || ((window_bits != MZ_DEFAULT_WINDOW_BITS) && (-window_bits != MZ_DEFAULT_WINDOW_BITS))) return MZ_PARAM_ERROR; pStream->data_type = 0; pStream->adler = MZ_ADLER32_INIT; pStream->msg = NULL; pStream->reserved = 0; pStream->total_in = 0; pStream->total_out = 0; if (!pStream->zalloc) pStream->zalloc = miniz_def_alloc_func; if (!pStream->zfree) pStream->zfree = miniz_def_free_func; pComp = (tdefl_compressor *)pStream->zalloc(pStream->opaque, 1, sizeof(tdefl_compressor)); if (!pComp) return MZ_MEM_ERROR; pStream->state = (struct mz_internal_state *)pComp; if (tdefl_init(pComp, NULL, NULL, comp_flags) != TDEFL_STATUS_OKAY) { mz_deflateEnd(pStream); return MZ_PARAM_ERROR; } return MZ_OK; } int mz_deflateReset(mz_streamp pStream) { if ((!pStream) || (!pStream->state) || (!pStream->zalloc) || (!pStream->zfree)) return MZ_STREAM_ERROR; pStream->total_in = pStream->total_out = 0; tdefl_init((tdefl_compressor *)pStream->state, NULL, NULL, ((tdefl_compressor *)pStream->state)->m_flags); return MZ_OK; } int mz_deflate(mz_streamp pStream, int flush) { size_t in_bytes, out_bytes; mz_ulong orig_total_in, orig_total_out; int mz_status = MZ_OK; if ((!pStream) || (!pStream->state) || (flush < 0) || (flush > MZ_FINISH) || (!pStream->next_out)) return MZ_STREAM_ERROR; if (!pStream->avail_out) return MZ_BUF_ERROR; if (flush == MZ_PARTIAL_FLUSH) flush = MZ_SYNC_FLUSH; if (((tdefl_compressor *)pStream->state)->m_prev_return_status == TDEFL_STATUS_DONE) return (flush == MZ_FINISH) ? MZ_STREAM_END : MZ_BUF_ERROR; orig_total_in = pStream->total_in; orig_total_out = pStream->total_out; for (;;) { tdefl_status defl_status; in_bytes = pStream->avail_in; out_bytes = pStream->avail_out; defl_status = tdefl_compress((tdefl_compressor *)pStream->state, pStream->next_in, &in_bytes, pStream->next_out, &out_bytes, (tdefl_flush)flush); pStream->next_in += (mz_uint)in_bytes; pStream->avail_in -= (mz_uint)in_bytes; pStream->total_in += (mz_uint)in_bytes; pStream->adler = tdefl_get_adler32((tdefl_compressor *)pStream->state); pStream->next_out += (mz_uint)out_bytes; pStream->avail_out -= (mz_uint)out_bytes; pStream->total_out += (mz_uint)out_bytes; if (defl_status < 0) { mz_status = MZ_STREAM_ERROR; break; } else if (defl_status == TDEFL_STATUS_DONE) { mz_status = MZ_STREAM_END; break; } else if (!pStream->avail_out) break; else if ((!pStream->avail_in) && (flush != MZ_FINISH)) { if ((flush) || (pStream->total_in != orig_total_in) || (pStream->total_out != orig_total_out)) break; return MZ_BUF_ERROR; /* Can't make forward progress without some input. */ } } return mz_status; } int mz_deflateEnd(mz_streamp pStream) { if (!pStream) return MZ_STREAM_ERROR; if (pStream->state) { pStream->zfree(pStream->opaque, pStream->state); pStream->state = NULL; } return MZ_OK; } mz_ulong mz_deflateBound(mz_streamp pStream, mz_ulong source_len) { (void)pStream; /* This is really over conservative. (And lame, but it's actually pretty tricky to compute a true upper bound given the way tdefl's blocking works.) */ return MZ_MAX(128 + (source_len * 110) / 100, 128 + source_len + ((source_len / (31 * 1024)) + 1) * 5); } int mz_compress2(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len, int level) { int status; mz_stream stream; memset(&stream, 0, sizeof(stream)); /* In case mz_ulong is 64-bits (argh I hate longs). */ if ((mz_uint64)(source_len | *pDest_len) > 0xFFFFFFFFU) return MZ_PARAM_ERROR; stream.next_in = pSource; stream.avail_in = (mz_uint32)source_len; stream.next_out = pDest; stream.avail_out = (mz_uint32)*pDest_len; status = mz_deflateInit(&stream, level); if (status != MZ_OK) return status; status = mz_deflate(&stream, MZ_FINISH); if (status != MZ_STREAM_END) { mz_deflateEnd(&stream); return (status == MZ_OK) ? MZ_BUF_ERROR : status; } *pDest_len = stream.total_out; return mz_deflateEnd(&stream); } int mz_compress(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len) { return mz_compress2(pDest, pDest_len, pSource, source_len, MZ_DEFAULT_COMPRESSION); } mz_ulong mz_compressBound(mz_ulong source_len) { return mz_deflateBound(NULL, source_len); } #endif /*#ifndef MINIZ_NO_DEFLATE_APIS*/ #ifndef MINIZ_NO_INFLATE_APIS typedef struct { tinfl_decompressor m_decomp; mz_uint m_dict_ofs, m_dict_avail, m_first_call, m_has_flushed; int m_window_bits; mz_uint8 m_dict[TINFL_LZ_DICT_SIZE]; tinfl_status m_last_status; } inflate_state; int mz_inflateInit2(mz_streamp pStream, int window_bits) { inflate_state *pDecomp; if (!pStream) return MZ_STREAM_ERROR; if ((window_bits != MZ_DEFAULT_WINDOW_BITS) && (-window_bits != MZ_DEFAULT_WINDOW_BITS)) return MZ_PARAM_ERROR; pStream->data_type = 0; pStream->adler = 0; pStream->msg = NULL; pStream->total_in = 0; pStream->total_out = 0; pStream->reserved = 0; if (!pStream->zalloc) pStream->zalloc = miniz_def_alloc_func; if (!pStream->zfree) pStream->zfree = miniz_def_free_func; pDecomp = (inflate_state *)pStream->zalloc(pStream->opaque, 1, sizeof(inflate_state)); if (!pDecomp) return MZ_MEM_ERROR; pStream->state = (struct mz_internal_state *)pDecomp; tinfl_init(&pDecomp->m_decomp); pDecomp->m_dict_ofs = 0; pDecomp->m_dict_avail = 0; pDecomp->m_last_status = TINFL_STATUS_NEEDS_MORE_INPUT; pDecomp->m_first_call = 1; pDecomp->m_has_flushed = 0; pDecomp->m_window_bits = window_bits; return MZ_OK; } int mz_inflateInit(mz_streamp pStream) { return mz_inflateInit2(pStream, MZ_DEFAULT_WINDOW_BITS); } int mz_inflateReset(mz_streamp pStream) { inflate_state *pDecomp; if (!pStream) return MZ_STREAM_ERROR; pStream->data_type = 0; pStream->adler = 0; pStream->msg = NULL; pStream->total_in = 0; pStream->total_out = 0; pStream->reserved = 0; pDecomp = (inflate_state *)pStream->state; tinfl_init(&pDecomp->m_decomp); pDecomp->m_dict_ofs = 0; pDecomp->m_dict_avail = 0; pDecomp->m_last_status = TINFL_STATUS_NEEDS_MORE_INPUT; pDecomp->m_first_call = 1; pDecomp->m_has_flushed = 0; /* pDecomp->m_window_bits = window_bits */; return MZ_OK; } int mz_inflate(mz_streamp pStream, int flush) { inflate_state *pState; mz_uint n, first_call, decomp_flags = TINFL_FLAG_COMPUTE_ADLER32; size_t in_bytes, out_bytes, orig_avail_in; tinfl_status status; if ((!pStream) || (!pStream->state)) return MZ_STREAM_ERROR; if (flush == MZ_PARTIAL_FLUSH) flush = MZ_SYNC_FLUSH; if ((flush) && (flush != MZ_SYNC_FLUSH) && (flush != MZ_FINISH)) return MZ_STREAM_ERROR; pState = (inflate_state *)pStream->state; if (pState->m_window_bits > 0) decomp_flags |= TINFL_FLAG_PARSE_ZLIB_HEADER; orig_avail_in = pStream->avail_in; first_call = pState->m_first_call; pState->m_first_call = 0; if (pState->m_last_status < 0) return MZ_DATA_ERROR; if (pState->m_has_flushed && (flush != MZ_FINISH)) return MZ_STREAM_ERROR; pState->m_has_flushed |= (flush == MZ_FINISH); if ((flush == MZ_FINISH) && (first_call)) { /* MZ_FINISH on the first call implies that the input and output buffers are large enough to hold the entire compressed/decompressed file. */ decomp_flags |= TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF; in_bytes = pStream->avail_in; out_bytes = pStream->avail_out; status = tinfl_decompress(&pState->m_decomp, pStream->next_in, &in_bytes, pStream->next_out, pStream->next_out, &out_bytes, decomp_flags); pState->m_last_status = status; pStream->next_in += (mz_uint)in_bytes; pStream->avail_in -= (mz_uint)in_bytes; pStream->total_in += (mz_uint)in_bytes; pStream->adler = tinfl_get_adler32(&pState->m_decomp); pStream->next_out += (mz_uint)out_bytes; pStream->avail_out -= (mz_uint)out_bytes; pStream->total_out += (mz_uint)out_bytes; if (status < 0) return MZ_DATA_ERROR; else if (status != TINFL_STATUS_DONE) { pState->m_last_status = TINFL_STATUS_FAILED; return MZ_BUF_ERROR; } return MZ_STREAM_END; } /* flush != MZ_FINISH then we must assume there's more input. */ if (flush != MZ_FINISH) decomp_flags |= TINFL_FLAG_HAS_MORE_INPUT; if (pState->m_dict_avail) { n = MZ_MIN(pState->m_dict_avail, pStream->avail_out); memcpy(pStream->next_out, pState->m_dict + pState->m_dict_ofs, n); pStream->next_out += n; pStream->avail_out -= n; pStream->total_out += n; pState->m_dict_avail -= n; pState->m_dict_ofs = (pState->m_dict_ofs + n) & (TINFL_LZ_DICT_SIZE - 1); return ((pState->m_last_status == TINFL_STATUS_DONE) && (!pState->m_dict_avail)) ? MZ_STREAM_END : MZ_OK; } for (;;) { in_bytes = pStream->avail_in; out_bytes = TINFL_LZ_DICT_SIZE - pState->m_dict_ofs; status = tinfl_decompress(&pState->m_decomp, pStream->next_in, &in_bytes, pState->m_dict, pState->m_dict + pState->m_dict_ofs, &out_bytes, decomp_flags); pState->m_last_status = status; pStream->next_in += (mz_uint)in_bytes; pStream->avail_in -= (mz_uint)in_bytes; pStream->total_in += (mz_uint)in_bytes; pStream->adler = tinfl_get_adler32(&pState->m_decomp); pState->m_dict_avail = (mz_uint)out_bytes; n = MZ_MIN(pState->m_dict_avail, pStream->avail_out); memcpy(pStream->next_out, pState->m_dict + pState->m_dict_ofs, n); pStream->next_out += n; pStream->avail_out -= n; pStream->total_out += n; pState->m_dict_avail -= n; pState->m_dict_ofs = (pState->m_dict_ofs + n) & (TINFL_LZ_DICT_SIZE - 1); if (status < 0) return MZ_DATA_ERROR; /* Stream is corrupted (there could be some uncompressed data left in the output dictionary - oh well). */ else if ((status == TINFL_STATUS_NEEDS_MORE_INPUT) && (!orig_avail_in)) return MZ_BUF_ERROR; /* Signal caller that we can't make forward progress without supplying more input or by setting flush to MZ_FINISH. */ else if (flush == MZ_FINISH) { /* The output buffer MUST be large to hold the remaining uncompressed data when flush==MZ_FINISH. */ if (status == TINFL_STATUS_DONE) return pState->m_dict_avail ? MZ_BUF_ERROR : MZ_STREAM_END; /* status here must be TINFL_STATUS_HAS_MORE_OUTPUT, which means there's at least 1 more byte on the way. If there's no more room left in the output buffer then something is wrong. */ else if (!pStream->avail_out) return MZ_BUF_ERROR; } else if ((status == TINFL_STATUS_DONE) || (!pStream->avail_in) || (!pStream->avail_out) || (pState->m_dict_avail)) break; } return ((status == TINFL_STATUS_DONE) && (!pState->m_dict_avail)) ? MZ_STREAM_END : MZ_OK; } int mz_inflateEnd(mz_streamp pStream) { if (!pStream) return MZ_STREAM_ERROR; if (pStream->state) { pStream->zfree(pStream->opaque, pStream->state); pStream->state = NULL; } return MZ_OK; } int mz_uncompress2(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong *pSource_len) { mz_stream stream; int status; memset(&stream, 0, sizeof(stream)); /* In case mz_ulong is 64-bits (argh I hate longs). */ if ((mz_uint64)(*pSource_len | *pDest_len) > 0xFFFFFFFFU) return MZ_PARAM_ERROR; stream.next_in = pSource; stream.avail_in = (mz_uint32)*pSource_len; stream.next_out = pDest; stream.avail_out = (mz_uint32)*pDest_len; status = mz_inflateInit(&stream); if (status != MZ_OK) return status; status = mz_inflate(&stream, MZ_FINISH); *pSource_len = *pSource_len - stream.avail_in; if (status != MZ_STREAM_END) { mz_inflateEnd(&stream); return ((status == MZ_BUF_ERROR) && (!stream.avail_in)) ? MZ_DATA_ERROR : status; } *pDest_len = stream.total_out; return mz_inflateEnd(&stream); } int mz_uncompress(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len) { return mz_uncompress2(pDest, pDest_len, pSource, &source_len); } #endif /*#ifndef MINIZ_NO_INFLATE_APIS*/ const char *mz_error(int err) { static struct { int m_err; const char *m_pDesc; } s_error_descs[] = { { MZ_OK, "" }, { MZ_STREAM_END, "stream end" }, { MZ_NEED_DICT, "need dictionary" }, { MZ_ERRNO, "file error" }, { MZ_STREAM_ERROR, "stream error" }, { MZ_DATA_ERROR, "data error" }, { MZ_MEM_ERROR, "out of memory" }, { MZ_BUF_ERROR, "buf error" }, { MZ_VERSION_ERROR, "version error" }, { MZ_PARAM_ERROR, "parameter error" } }; mz_uint i; for (i = 0; i < sizeof(s_error_descs) / sizeof(s_error_descs[0]); ++i) if (s_error_descs[i].m_err == err) return s_error_descs[i].m_pDesc; return NULL; } #endif /*MINIZ_NO_ZLIB_APIS */ #ifdef __cplusplus } #endif /* This is free and unencumbered software released into the public domain. Anyone is free to copy, modify, publish, use, compile, sell, or distribute this software, either in source code form or as a compiled binary, for any purpose, commercial or non-commercial, and by any means. In jurisdictions that recognize copyright laws, the author or authors of this software dedicate any and all copyright interest in the software to the public domain. We make this dedication for the benefit of the public at large and to the detriment of our heirs and successors. We intend this dedication to be an overt act of relinquishment in perpetuity of all present and future rights to this software under copyright law. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. For more information, please refer to */ /************************************************************************** * * Copyright 2013-2014 RAD Game Tools and Valve Software * Copyright 2010-2014 Rich Geldreich and Tenacious Software LLC * All Rights Reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. * **************************************************************************/ #ifndef MINIZ_NO_DEFLATE_APIS #ifdef __cplusplus extern "C" { #endif /* ------------------- Low-level Compression (independent from all decompression API's) */ /* Purposely making these tables static for faster init and thread safety. */ static const mz_uint16 s_tdefl_len_sym[256] = { 257, 258, 259, 260, 261, 262, 263, 264, 265, 265, 266, 266, 267, 267, 268, 268, 269, 269, 269, 269, 270, 270, 270, 270, 271, 271, 271, 271, 272, 272, 272, 272, 273, 273, 273, 273, 273, 273, 273, 273, 274, 274, 274, 274, 274, 274, 274, 274, 275, 275, 275, 275, 275, 275, 275, 275, 276, 276, 276, 276, 276, 276, 276, 276, 277, 277, 277, 277, 277, 277, 277, 277, 277, 277, 277, 277, 277, 277, 277, 277, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 279, 279, 279, 279, 279, 279, 279, 279, 279, 279, 279, 279, 279, 279, 279, 279, 280, 280, 280, 280, 280, 280, 280, 280, 280, 280, 280, 280, 280, 280, 280, 280, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 285 }; static const mz_uint8 s_tdefl_len_extra[256] = { 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 0 }; static const mz_uint8 s_tdefl_small_dist_sym[512] = { 0, 1, 2, 3, 4, 4, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17 }; static const mz_uint8 s_tdefl_small_dist_extra[512] = { 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7 }; static const mz_uint8 s_tdefl_large_dist_sym[128] = { 0, 0, 18, 19, 20, 20, 21, 21, 22, 22, 22, 22, 23, 23, 23, 23, 24, 24, 24, 24, 24, 24, 24, 24, 25, 25, 25, 25, 25, 25, 25, 25, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29 }; static const mz_uint8 s_tdefl_large_dist_extra[128] = { 0, 0, 8, 8, 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13 }; /* Radix sorts tdefl_sym_freq[] array by 16-bit key m_key. Returns ptr to sorted values. */ typedef struct { mz_uint16 m_key, m_sym_index; } tdefl_sym_freq; static tdefl_sym_freq *tdefl_radix_sort_syms(mz_uint num_syms, tdefl_sym_freq *pSyms0, tdefl_sym_freq *pSyms1) { mz_uint32 total_passes = 2, pass_shift, pass, i, hist[256 * 2]; tdefl_sym_freq *pCur_syms = pSyms0, *pNew_syms = pSyms1; MZ_CLEAR_ARR(hist); for (i = 0; i < num_syms; i++) { mz_uint freq = pSyms0[i].m_key; hist[freq & 0xFF]++; hist[256 + ((freq >> 8) & 0xFF)]++; } while ((total_passes > 1) && (num_syms == hist[(total_passes - 1) * 256])) total_passes--; for (pass_shift = 0, pass = 0; pass < total_passes; pass++, pass_shift += 8) { const mz_uint32 *pHist = &hist[pass << 8]; mz_uint offsets[256], cur_ofs = 0; for (i = 0; i < 256; i++) { offsets[i] = cur_ofs; cur_ofs += pHist[i]; } for (i = 0; i < num_syms; i++) pNew_syms[offsets[(pCur_syms[i].m_key >> pass_shift) & 0xFF]++] = pCur_syms[i]; { tdefl_sym_freq *t = pCur_syms; pCur_syms = pNew_syms; pNew_syms = t; } } return pCur_syms; } /* tdefl_calculate_minimum_redundancy() originally written by: Alistair Moffat, alistair@cs.mu.oz.au, Jyrki Katajainen, jyrki@diku.dk, November 1996. */ static void tdefl_calculate_minimum_redundancy(tdefl_sym_freq *A, int n) { int root, leaf, next, avbl, used, dpth; if (n == 0) return; else if (n == 1) { A[0].m_key = 1; return; } A[0].m_key += A[1].m_key; root = 0; leaf = 2; for (next = 1; next < n - 1; next++) { if (leaf >= n || A[root].m_key < A[leaf].m_key) { A[next].m_key = A[root].m_key; A[root++].m_key = (mz_uint16)next; } else A[next].m_key = A[leaf++].m_key; if (leaf >= n || (root < next && A[root].m_key < A[leaf].m_key)) { A[next].m_key = (mz_uint16)(A[next].m_key + A[root].m_key); A[root++].m_key = (mz_uint16)next; } else A[next].m_key = (mz_uint16)(A[next].m_key + A[leaf++].m_key); } A[n - 2].m_key = 0; for (next = n - 3; next >= 0; next--) A[next].m_key = A[A[next].m_key].m_key + 1; avbl = 1; used = dpth = 0; root = n - 2; next = n - 1; while (avbl > 0) { while (root >= 0 && (int)A[root].m_key == dpth) { used++; root--; } while (avbl > used) { A[next--].m_key = (mz_uint16)(dpth); avbl--; } avbl = 2 * used; dpth++; used = 0; } } /* Limits canonical Huffman code table's max code size. */ enum { TDEFL_MAX_SUPPORTED_HUFF_CODESIZE = 32 }; static void tdefl_huffman_enforce_max_code_size(int *pNum_codes, int code_list_len, int max_code_size) { int i; mz_uint32 total = 0; if (code_list_len <= 1) return; for (i = max_code_size + 1; i <= TDEFL_MAX_SUPPORTED_HUFF_CODESIZE; i++) pNum_codes[max_code_size] += pNum_codes[i]; for (i = max_code_size; i > 0; i--) total += (((mz_uint32)pNum_codes[i]) << (max_code_size - i)); while (total != (1UL << max_code_size)) { pNum_codes[max_code_size]--; for (i = max_code_size - 1; i > 0; i--) if (pNum_codes[i]) { pNum_codes[i]--; pNum_codes[i + 1] += 2; break; } total--; } } static void tdefl_optimize_huffman_table(tdefl_compressor *d, int table_num, int table_len, int code_size_limit, int static_table) { int i, j, l, num_codes[1 + TDEFL_MAX_SUPPORTED_HUFF_CODESIZE]; mz_uint next_code[TDEFL_MAX_SUPPORTED_HUFF_CODESIZE + 1]; MZ_CLEAR_ARR(num_codes); if (static_table) { for (i = 0; i < table_len; i++) num_codes[d->m_huff_code_sizes[table_num][i]]++; } else { tdefl_sym_freq syms0[TDEFL_MAX_HUFF_SYMBOLS], syms1[TDEFL_MAX_HUFF_SYMBOLS], *pSyms; int num_used_syms = 0; const mz_uint16 *pSym_count = &d->m_huff_count[table_num][0]; for (i = 0; i < table_len; i++) if (pSym_count[i]) { syms0[num_used_syms].m_key = (mz_uint16)pSym_count[i]; syms0[num_used_syms++].m_sym_index = (mz_uint16)i; } pSyms = tdefl_radix_sort_syms(num_used_syms, syms0, syms1); tdefl_calculate_minimum_redundancy(pSyms, num_used_syms); for (i = 0; i < num_used_syms; i++) num_codes[pSyms[i].m_key]++; tdefl_huffman_enforce_max_code_size(num_codes, num_used_syms, code_size_limit); MZ_CLEAR_ARR(d->m_huff_code_sizes[table_num]); MZ_CLEAR_ARR(d->m_huff_codes[table_num]); for (i = 1, j = num_used_syms; i <= code_size_limit; i++) for (l = num_codes[i]; l > 0; l--) d->m_huff_code_sizes[table_num][pSyms[--j].m_sym_index] = (mz_uint8)(i); } next_code[1] = 0; for (j = 0, i = 2; i <= code_size_limit; i++) next_code[i] = j = ((j + num_codes[i - 1]) << 1); for (i = 0; i < table_len; i++) { mz_uint rev_code = 0, code, code_size; if ((code_size = d->m_huff_code_sizes[table_num][i]) == 0) continue; code = next_code[code_size]++; for (l = code_size; l > 0; l--, code >>= 1) rev_code = (rev_code << 1) | (code & 1); d->m_huff_codes[table_num][i] = (mz_uint16)rev_code; } } #define TDEFL_PUT_BITS(b, l) \ do \ { \ mz_uint bits = b; \ mz_uint len = l; \ MZ_ASSERT(bits <= ((1U << len) - 1U)); \ d->m_bit_buffer |= (bits << d->m_bits_in); \ d->m_bits_in += len; \ while (d->m_bits_in >= 8) \ { \ if (d->m_pOutput_buf < d->m_pOutput_buf_end) \ *d->m_pOutput_buf++ = (mz_uint8)(d->m_bit_buffer); \ d->m_bit_buffer >>= 8; \ d->m_bits_in -= 8; \ } \ } \ MZ_MACRO_END #define TDEFL_RLE_PREV_CODE_SIZE() \ { \ if (rle_repeat_count) \ { \ if (rle_repeat_count < 3) \ { \ d->m_huff_count[2][prev_code_size] = (mz_uint16)(d->m_huff_count[2][prev_code_size] + rle_repeat_count); \ while (rle_repeat_count--) \ packed_code_sizes[num_packed_code_sizes++] = prev_code_size; \ } \ else \ { \ d->m_huff_count[2][16] = (mz_uint16)(d->m_huff_count[2][16] + 1); \ packed_code_sizes[num_packed_code_sizes++] = 16; \ packed_code_sizes[num_packed_code_sizes++] = (mz_uint8)(rle_repeat_count - 3); \ } \ rle_repeat_count = 0; \ } \ } #define TDEFL_RLE_ZERO_CODE_SIZE() \ { \ if (rle_z_count) \ { \ if (rle_z_count < 3) \ { \ d->m_huff_count[2][0] = (mz_uint16)(d->m_huff_count[2][0] + rle_z_count); \ while (rle_z_count--) \ packed_code_sizes[num_packed_code_sizes++] = 0; \ } \ else if (rle_z_count <= 10) \ { \ d->m_huff_count[2][17] = (mz_uint16)(d->m_huff_count[2][17] + 1); \ packed_code_sizes[num_packed_code_sizes++] = 17; \ packed_code_sizes[num_packed_code_sizes++] = (mz_uint8)(rle_z_count - 3); \ } \ else \ { \ d->m_huff_count[2][18] = (mz_uint16)(d->m_huff_count[2][18] + 1); \ packed_code_sizes[num_packed_code_sizes++] = 18; \ packed_code_sizes[num_packed_code_sizes++] = (mz_uint8)(rle_z_count - 11); \ } \ rle_z_count = 0; \ } \ } static const mz_uint8 s_tdefl_packed_code_size_syms_swizzle[] = { 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 }; static void tdefl_start_dynamic_block(tdefl_compressor *d) { int num_lit_codes, num_dist_codes, num_bit_lengths; mz_uint i, total_code_sizes_to_pack, num_packed_code_sizes, rle_z_count, rle_repeat_count, packed_code_sizes_index; mz_uint8 code_sizes_to_pack[TDEFL_MAX_HUFF_SYMBOLS_0 + TDEFL_MAX_HUFF_SYMBOLS_1], packed_code_sizes[TDEFL_MAX_HUFF_SYMBOLS_0 + TDEFL_MAX_HUFF_SYMBOLS_1], prev_code_size = 0xFF; d->m_huff_count[0][256] = 1; tdefl_optimize_huffman_table(d, 0, TDEFL_MAX_HUFF_SYMBOLS_0, 15, MZ_FALSE); tdefl_optimize_huffman_table(d, 1, TDEFL_MAX_HUFF_SYMBOLS_1, 15, MZ_FALSE); for (num_lit_codes = 286; num_lit_codes > 257; num_lit_codes--) if (d->m_huff_code_sizes[0][num_lit_codes - 1]) break; for (num_dist_codes = 30; num_dist_codes > 1; num_dist_codes--) if (d->m_huff_code_sizes[1][num_dist_codes - 1]) break; memcpy(code_sizes_to_pack, &d->m_huff_code_sizes[0][0], num_lit_codes); memcpy(code_sizes_to_pack + num_lit_codes, &d->m_huff_code_sizes[1][0], num_dist_codes); total_code_sizes_to_pack = num_lit_codes + num_dist_codes; num_packed_code_sizes = 0; rle_z_count = 0; rle_repeat_count = 0; memset(&d->m_huff_count[2][0], 0, sizeof(d->m_huff_count[2][0]) * TDEFL_MAX_HUFF_SYMBOLS_2); for (i = 0; i < total_code_sizes_to_pack; i++) { mz_uint8 code_size = code_sizes_to_pack[i]; if (!code_size) { TDEFL_RLE_PREV_CODE_SIZE(); if (++rle_z_count == 138) { TDEFL_RLE_ZERO_CODE_SIZE(); } } else { TDEFL_RLE_ZERO_CODE_SIZE(); if (code_size != prev_code_size) { TDEFL_RLE_PREV_CODE_SIZE(); d->m_huff_count[2][code_size] = (mz_uint16)(d->m_huff_count[2][code_size] + 1); packed_code_sizes[num_packed_code_sizes++] = code_size; } else if (++rle_repeat_count == 6) { TDEFL_RLE_PREV_CODE_SIZE(); } } prev_code_size = code_size; } if (rle_repeat_count) { TDEFL_RLE_PREV_CODE_SIZE(); } else { TDEFL_RLE_ZERO_CODE_SIZE(); } tdefl_optimize_huffman_table(d, 2, TDEFL_MAX_HUFF_SYMBOLS_2, 7, MZ_FALSE); TDEFL_PUT_BITS(2, 2); TDEFL_PUT_BITS(num_lit_codes - 257, 5); TDEFL_PUT_BITS(num_dist_codes - 1, 5); for (num_bit_lengths = 18; num_bit_lengths >= 0; num_bit_lengths--) if (d->m_huff_code_sizes[2][s_tdefl_packed_code_size_syms_swizzle[num_bit_lengths]]) break; num_bit_lengths = MZ_MAX(4, (num_bit_lengths + 1)); TDEFL_PUT_BITS(num_bit_lengths - 4, 4); for (i = 0; (int)i < num_bit_lengths; i++) TDEFL_PUT_BITS(d->m_huff_code_sizes[2][s_tdefl_packed_code_size_syms_swizzle[i]], 3); for (packed_code_sizes_index = 0; packed_code_sizes_index < num_packed_code_sizes;) { mz_uint code = packed_code_sizes[packed_code_sizes_index++]; MZ_ASSERT(code < TDEFL_MAX_HUFF_SYMBOLS_2); TDEFL_PUT_BITS(d->m_huff_codes[2][code], d->m_huff_code_sizes[2][code]); if (code >= 16) TDEFL_PUT_BITS(packed_code_sizes[packed_code_sizes_index++], "\02\03\07"[code - 16]); } } static void tdefl_start_static_block(tdefl_compressor *d) { mz_uint i; mz_uint8 *p = &d->m_huff_code_sizes[0][0]; for (i = 0; i <= 143; ++i) *p++ = 8; for (; i <= 255; ++i) *p++ = 9; for (; i <= 279; ++i) *p++ = 7; for (; i <= 287; ++i) *p++ = 8; memset(d->m_huff_code_sizes[1], 5, 32); tdefl_optimize_huffman_table(d, 0, 288, 15, MZ_TRUE); tdefl_optimize_huffman_table(d, 1, 32, 15, MZ_TRUE); TDEFL_PUT_BITS(1, 2); } static const mz_uint mz_bitmasks[17] = { 0x0000, 0x0001, 0x0003, 0x0007, 0x000F, 0x001F, 0x003F, 0x007F, 0x00FF, 0x01FF, 0x03FF, 0x07FF, 0x0FFF, 0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF }; #if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN && MINIZ_HAS_64BIT_REGISTERS static mz_bool tdefl_compress_lz_codes(tdefl_compressor *d) { mz_uint flags; mz_uint8 *pLZ_codes; mz_uint8 *pOutput_buf = d->m_pOutput_buf; mz_uint8 *pLZ_code_buf_end = d->m_pLZ_code_buf; mz_uint64 bit_buffer = d->m_bit_buffer; mz_uint bits_in = d->m_bits_in; #define TDEFL_PUT_BITS_FAST(b, l) \ { \ bit_buffer |= (((mz_uint64)(b)) << bits_in); \ bits_in += (l); \ } flags = 1; for (pLZ_codes = d->m_lz_code_buf; pLZ_codes < pLZ_code_buf_end; flags >>= 1) { if (flags == 1) flags = *pLZ_codes++ | 0x100; if (flags & 1) { mz_uint s0, s1, n0, n1, sym, num_extra_bits; mz_uint match_len = pLZ_codes[0]; mz_uint match_dist = (pLZ_codes[1] | (pLZ_codes[2] << 8)); pLZ_codes += 3; MZ_ASSERT(d->m_huff_code_sizes[0][s_tdefl_len_sym[match_len]]); TDEFL_PUT_BITS_FAST(d->m_huff_codes[0][s_tdefl_len_sym[match_len]], d->m_huff_code_sizes[0][s_tdefl_len_sym[match_len]]); TDEFL_PUT_BITS_FAST(match_len & mz_bitmasks[s_tdefl_len_extra[match_len]], s_tdefl_len_extra[match_len]); /* This sequence coaxes MSVC into using cmov's vs. jmp's. */ s0 = s_tdefl_small_dist_sym[match_dist & 511]; n0 = s_tdefl_small_dist_extra[match_dist & 511]; s1 = s_tdefl_large_dist_sym[match_dist >> 8]; n1 = s_tdefl_large_dist_extra[match_dist >> 8]; sym = (match_dist < 512) ? s0 : s1; num_extra_bits = (match_dist < 512) ? n0 : n1; MZ_ASSERT(d->m_huff_code_sizes[1][sym]); TDEFL_PUT_BITS_FAST(d->m_huff_codes[1][sym], d->m_huff_code_sizes[1][sym]); TDEFL_PUT_BITS_FAST(match_dist & mz_bitmasks[num_extra_bits], num_extra_bits); } else { mz_uint lit = *pLZ_codes++; MZ_ASSERT(d->m_huff_code_sizes[0][lit]); TDEFL_PUT_BITS_FAST(d->m_huff_codes[0][lit], d->m_huff_code_sizes[0][lit]); if (((flags & 2) == 0) && (pLZ_codes < pLZ_code_buf_end)) { flags >>= 1; lit = *pLZ_codes++; MZ_ASSERT(d->m_huff_code_sizes[0][lit]); TDEFL_PUT_BITS_FAST(d->m_huff_codes[0][lit], d->m_huff_code_sizes[0][lit]); if (((flags & 2) == 0) && (pLZ_codes < pLZ_code_buf_end)) { flags >>= 1; lit = *pLZ_codes++; MZ_ASSERT(d->m_huff_code_sizes[0][lit]); TDEFL_PUT_BITS_FAST(d->m_huff_codes[0][lit], d->m_huff_code_sizes[0][lit]); } } } if (pOutput_buf >= d->m_pOutput_buf_end) return MZ_FALSE; memcpy(pOutput_buf, &bit_buffer, sizeof(mz_uint64)); pOutput_buf += (bits_in >> 3); bit_buffer >>= (bits_in & ~7); bits_in &= 7; } #undef TDEFL_PUT_BITS_FAST d->m_pOutput_buf = pOutput_buf; d->m_bits_in = 0; d->m_bit_buffer = 0; while (bits_in) { mz_uint32 n = MZ_MIN(bits_in, 16); TDEFL_PUT_BITS((mz_uint)bit_buffer & mz_bitmasks[n], n); bit_buffer >>= n; bits_in -= n; } TDEFL_PUT_BITS(d->m_huff_codes[0][256], d->m_huff_code_sizes[0][256]); return (d->m_pOutput_buf < d->m_pOutput_buf_end); } #else static mz_bool tdefl_compress_lz_codes(tdefl_compressor *d) { mz_uint flags; mz_uint8 *pLZ_codes; flags = 1; for (pLZ_codes = d->m_lz_code_buf; pLZ_codes < d->m_pLZ_code_buf; flags >>= 1) { if (flags == 1) flags = *pLZ_codes++ | 0x100; if (flags & 1) { mz_uint sym, num_extra_bits; mz_uint match_len = pLZ_codes[0], match_dist = (pLZ_codes[1] | (pLZ_codes[2] << 8)); pLZ_codes += 3; MZ_ASSERT(d->m_huff_code_sizes[0][s_tdefl_len_sym[match_len]]); TDEFL_PUT_BITS(d->m_huff_codes[0][s_tdefl_len_sym[match_len]], d->m_huff_code_sizes[0][s_tdefl_len_sym[match_len]]); TDEFL_PUT_BITS(match_len & mz_bitmasks[s_tdefl_len_extra[match_len]], s_tdefl_len_extra[match_len]); if (match_dist < 512) { sym = s_tdefl_small_dist_sym[match_dist]; num_extra_bits = s_tdefl_small_dist_extra[match_dist]; } else { sym = s_tdefl_large_dist_sym[match_dist >> 8]; num_extra_bits = s_tdefl_large_dist_extra[match_dist >> 8]; } MZ_ASSERT(d->m_huff_code_sizes[1][sym]); TDEFL_PUT_BITS(d->m_huff_codes[1][sym], d->m_huff_code_sizes[1][sym]); TDEFL_PUT_BITS(match_dist & mz_bitmasks[num_extra_bits], num_extra_bits); } else { mz_uint lit = *pLZ_codes++; MZ_ASSERT(d->m_huff_code_sizes[0][lit]); TDEFL_PUT_BITS(d->m_huff_codes[0][lit], d->m_huff_code_sizes[0][lit]); } } TDEFL_PUT_BITS(d->m_huff_codes[0][256], d->m_huff_code_sizes[0][256]); return (d->m_pOutput_buf < d->m_pOutput_buf_end); } #endif /* MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN && MINIZ_HAS_64BIT_REGISTERS */ static mz_bool tdefl_compress_block(tdefl_compressor *d, mz_bool static_block) { if (static_block) tdefl_start_static_block(d); else tdefl_start_dynamic_block(d); return tdefl_compress_lz_codes(d); } static const mz_uint s_tdefl_num_probes[11]; static int tdefl_flush_block(tdefl_compressor *d, int flush) { mz_uint saved_bit_buf, saved_bits_in; mz_uint8 *pSaved_output_buf; mz_bool comp_block_succeeded = MZ_FALSE; int n, use_raw_block = ((d->m_flags & TDEFL_FORCE_ALL_RAW_BLOCKS) != 0) && (d->m_lookahead_pos - d->m_lz_code_buf_dict_pos) <= d->m_dict_size; mz_uint8 *pOutput_buf_start = ((d->m_pPut_buf_func == NULL) && ((*d->m_pOut_buf_size - d->m_out_buf_ofs) >= TDEFL_OUT_BUF_SIZE)) ? ((mz_uint8 *)d->m_pOut_buf + d->m_out_buf_ofs) : d->m_output_buf; d->m_pOutput_buf = pOutput_buf_start; d->m_pOutput_buf_end = d->m_pOutput_buf + TDEFL_OUT_BUF_SIZE - 16; MZ_ASSERT(!d->m_output_flush_remaining); d->m_output_flush_ofs = 0; d->m_output_flush_remaining = 0; *d->m_pLZ_flags = (mz_uint8)(*d->m_pLZ_flags >> d->m_num_flags_left); d->m_pLZ_code_buf -= (d->m_num_flags_left == 8); if ((d->m_flags & TDEFL_WRITE_ZLIB_HEADER) && (!d->m_block_index)) { const mz_uint8 cmf = 0x78; mz_uint8 flg, flevel = 3; mz_uint header, i, mz_un = sizeof(s_tdefl_num_probes) / sizeof(mz_uint); /* Determine compression level by reversing the process in tdefl_create_comp_flags_from_zip_params() */ for (i = 0; i < mz_un; i++) if (s_tdefl_num_probes[i] == (d->m_flags & 0xFFF)) break; if (i < 2) flevel = 0; else if (i < 6) flevel = 1; else if (i == 6) flevel = 2; header = cmf << 8 | (flevel << 6); header += 31 - (header % 31); flg = header & 0xFF; TDEFL_PUT_BITS(cmf, 8); TDEFL_PUT_BITS(flg, 8); } TDEFL_PUT_BITS(flush == TDEFL_FINISH, 1); pSaved_output_buf = d->m_pOutput_buf; saved_bit_buf = d->m_bit_buffer; saved_bits_in = d->m_bits_in; if (!use_raw_block) comp_block_succeeded = tdefl_compress_block(d, (d->m_flags & TDEFL_FORCE_ALL_STATIC_BLOCKS) || (d->m_total_lz_bytes < 48)); /* If the block gets expanded, forget the current contents of the output buffer and send a raw block instead. */ if (((use_raw_block) || ((d->m_total_lz_bytes) && ((d->m_pOutput_buf - pSaved_output_buf + 1U) >= d->m_total_lz_bytes))) && ((d->m_lookahead_pos - d->m_lz_code_buf_dict_pos) <= d->m_dict_size)) { mz_uint i; d->m_pOutput_buf = pSaved_output_buf; d->m_bit_buffer = saved_bit_buf, d->m_bits_in = saved_bits_in; TDEFL_PUT_BITS(0, 2); if (d->m_bits_in) { TDEFL_PUT_BITS(0, 8 - d->m_bits_in); } for (i = 2; i; --i, d->m_total_lz_bytes ^= 0xFFFF) { TDEFL_PUT_BITS(d->m_total_lz_bytes & 0xFFFF, 16); } for (i = 0; i < d->m_total_lz_bytes; ++i) { TDEFL_PUT_BITS(d->m_dict[(d->m_lz_code_buf_dict_pos + i) & TDEFL_LZ_DICT_SIZE_MASK], 8); } } /* Check for the extremely unlikely (if not impossible) case of the compressed block not fitting into the output buffer when using dynamic codes. */ else if (!comp_block_succeeded) { d->m_pOutput_buf = pSaved_output_buf; d->m_bit_buffer = saved_bit_buf, d->m_bits_in = saved_bits_in; tdefl_compress_block(d, MZ_TRUE); } if (flush) { if (flush == TDEFL_FINISH) { if (d->m_bits_in) { TDEFL_PUT_BITS(0, 8 - d->m_bits_in); } if (d->m_flags & TDEFL_WRITE_ZLIB_HEADER) { mz_uint i, a = d->m_adler32; for (i = 0; i < 4; i++) { TDEFL_PUT_BITS((a >> 24) & 0xFF, 8); a <<= 8; } } } else { mz_uint i, z = 0; TDEFL_PUT_BITS(0, 3); if (d->m_bits_in) { TDEFL_PUT_BITS(0, 8 - d->m_bits_in); } for (i = 2; i; --i, z ^= 0xFFFF) { TDEFL_PUT_BITS(z & 0xFFFF, 16); } } } MZ_ASSERT(d->m_pOutput_buf < d->m_pOutput_buf_end); memset(&d->m_huff_count[0][0], 0, sizeof(d->m_huff_count[0][0]) * TDEFL_MAX_HUFF_SYMBOLS_0); memset(&d->m_huff_count[1][0], 0, sizeof(d->m_huff_count[1][0]) * TDEFL_MAX_HUFF_SYMBOLS_1); d->m_pLZ_code_buf = d->m_lz_code_buf + 1; d->m_pLZ_flags = d->m_lz_code_buf; d->m_num_flags_left = 8; d->m_lz_code_buf_dict_pos += d->m_total_lz_bytes; d->m_total_lz_bytes = 0; d->m_block_index++; if ((n = (int)(d->m_pOutput_buf - pOutput_buf_start)) != 0) { if (d->m_pPut_buf_func) { *d->m_pIn_buf_size = d->m_pSrc - (const mz_uint8 *)d->m_pIn_buf; if (!(*d->m_pPut_buf_func)(d->m_output_buf, n, d->m_pPut_buf_user)) return (d->m_prev_return_status = TDEFL_STATUS_PUT_BUF_FAILED); } else if (pOutput_buf_start == d->m_output_buf) { int bytes_to_copy = (int)MZ_MIN((size_t)n, (size_t)(*d->m_pOut_buf_size - d->m_out_buf_ofs)); memcpy((mz_uint8 *)d->m_pOut_buf + d->m_out_buf_ofs, d->m_output_buf, bytes_to_copy); d->m_out_buf_ofs += bytes_to_copy; if ((n -= bytes_to_copy) != 0) { d->m_output_flush_ofs = bytes_to_copy; d->m_output_flush_remaining = n; } } else { d->m_out_buf_ofs += n; } } return d->m_output_flush_remaining; } #if MINIZ_USE_UNALIGNED_LOADS_AND_STORES #ifdef MINIZ_UNALIGNED_USE_MEMCPY static mz_uint16 TDEFL_READ_UNALIGNED_WORD(const mz_uint8* p) { mz_uint16 ret; memcpy(&ret, p, sizeof(mz_uint16)); return ret; } static mz_uint16 TDEFL_READ_UNALIGNED_WORD2(const mz_uint16* p) { mz_uint16 ret; memcpy(&ret, p, sizeof(mz_uint16)); return ret; } #else #define TDEFL_READ_UNALIGNED_WORD(p) *(const mz_uint16 *)(p) #define TDEFL_READ_UNALIGNED_WORD2(p) *(const mz_uint16 *)(p) #endif static MZ_FORCEINLINE void tdefl_find_match(tdefl_compressor *d, mz_uint lookahead_pos, mz_uint max_dist, mz_uint max_match_len, mz_uint *pMatch_dist, mz_uint *pMatch_len) { mz_uint dist, pos = lookahead_pos & TDEFL_LZ_DICT_SIZE_MASK, match_len = *pMatch_len, probe_pos = pos, next_probe_pos, probe_len; mz_uint num_probes_left = d->m_max_probes[match_len >= 32]; const mz_uint16 *s = (const mz_uint16 *)(d->m_dict + pos), *p, *q; mz_uint16 c01 = TDEFL_READ_UNALIGNED_WORD(&d->m_dict[pos + match_len - 1]), s01 = TDEFL_READ_UNALIGNED_WORD2(s); MZ_ASSERT(max_match_len <= TDEFL_MAX_MATCH_LEN); if (max_match_len <= match_len) return; for (;;) { for (;;) { if (--num_probes_left == 0) return; #define TDEFL_PROBE \ next_probe_pos = d->m_next[probe_pos]; \ if ((!next_probe_pos) || ((dist = (mz_uint16)(lookahead_pos - next_probe_pos)) > max_dist)) \ return; \ probe_pos = next_probe_pos & TDEFL_LZ_DICT_SIZE_MASK; \ if (TDEFL_READ_UNALIGNED_WORD(&d->m_dict[probe_pos + match_len - 1]) == c01) \ break; TDEFL_PROBE; TDEFL_PROBE; TDEFL_PROBE; } if (!dist) break; q = (const mz_uint16 *)(d->m_dict + probe_pos); if (TDEFL_READ_UNALIGNED_WORD2(q) != s01) continue; p = s; probe_len = 32; do { } while ((TDEFL_READ_UNALIGNED_WORD2(++p) == TDEFL_READ_UNALIGNED_WORD2(++q)) && (TDEFL_READ_UNALIGNED_WORD2(++p) == TDEFL_READ_UNALIGNED_WORD2(++q)) && (TDEFL_READ_UNALIGNED_WORD2(++p) == TDEFL_READ_UNALIGNED_WORD2(++q)) && (TDEFL_READ_UNALIGNED_WORD2(++p) == TDEFL_READ_UNALIGNED_WORD2(++q)) && (--probe_len > 0)); if (!probe_len) { *pMatch_dist = dist; *pMatch_len = MZ_MIN(max_match_len, (mz_uint)TDEFL_MAX_MATCH_LEN); break; } else if ((probe_len = ((mz_uint)(p - s) * 2) + (mz_uint)(*(const mz_uint8 *)p == *(const mz_uint8 *)q)) > match_len) { *pMatch_dist = dist; if ((*pMatch_len = match_len = MZ_MIN(max_match_len, probe_len)) == max_match_len) break; c01 = TDEFL_READ_UNALIGNED_WORD(&d->m_dict[pos + match_len - 1]); } } } #else static MZ_FORCEINLINE void tdefl_find_match(tdefl_compressor *d, mz_uint lookahead_pos, mz_uint max_dist, mz_uint max_match_len, mz_uint *pMatch_dist, mz_uint *pMatch_len) { mz_uint dist, pos = lookahead_pos & TDEFL_LZ_DICT_SIZE_MASK, match_len = *pMatch_len, probe_pos = pos, next_probe_pos, probe_len; mz_uint num_probes_left = d->m_max_probes[match_len >= 32]; const mz_uint8 *s = d->m_dict + pos, *p, *q; mz_uint8 c0 = d->m_dict[pos + match_len], c1 = d->m_dict[pos + match_len - 1]; MZ_ASSERT(max_match_len <= TDEFL_MAX_MATCH_LEN); if (max_match_len <= match_len) return; for (;;) { for (;;) { if (--num_probes_left == 0) return; #define TDEFL_PROBE \ next_probe_pos = d->m_next[probe_pos]; \ if ((!next_probe_pos) || ((dist = (mz_uint16)(lookahead_pos - next_probe_pos)) > max_dist)) \ return; \ probe_pos = next_probe_pos & TDEFL_LZ_DICT_SIZE_MASK; \ if ((d->m_dict[probe_pos + match_len] == c0) && (d->m_dict[probe_pos + match_len - 1] == c1)) \ break; TDEFL_PROBE; TDEFL_PROBE; TDEFL_PROBE; } if (!dist) break; p = s; q = d->m_dict + probe_pos; for (probe_len = 0; probe_len < max_match_len; probe_len++) if (*p++ != *q++) break; if (probe_len > match_len) { *pMatch_dist = dist; if ((*pMatch_len = match_len = probe_len) == max_match_len) return; c0 = d->m_dict[pos + match_len]; c1 = d->m_dict[pos + match_len - 1]; } } } #endif /* #if MINIZ_USE_UNALIGNED_LOADS_AND_STORES */ #if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN #ifdef MINIZ_UNALIGNED_USE_MEMCPY static mz_uint32 TDEFL_READ_UNALIGNED_WORD32(const mz_uint8* p) { mz_uint32 ret; memcpy(&ret, p, sizeof(mz_uint32)); return ret; } #else #define TDEFL_READ_UNALIGNED_WORD32(p) *(const mz_uint32 *)(p) #endif static mz_bool tdefl_compress_fast(tdefl_compressor *d) { /* Faster, minimally featured LZRW1-style match+parse loop with better register utilization. Intended for applications where raw throughput is valued more highly than ratio. */ mz_uint lookahead_pos = d->m_lookahead_pos, lookahead_size = d->m_lookahead_size, dict_size = d->m_dict_size, total_lz_bytes = d->m_total_lz_bytes, num_flags_left = d->m_num_flags_left; mz_uint8 *pLZ_code_buf = d->m_pLZ_code_buf, *pLZ_flags = d->m_pLZ_flags; mz_uint cur_pos = lookahead_pos & TDEFL_LZ_DICT_SIZE_MASK; while ((d->m_src_buf_left) || ((d->m_flush) && (lookahead_size))) { const mz_uint TDEFL_COMP_FAST_LOOKAHEAD_SIZE = 4096; mz_uint dst_pos = (lookahead_pos + lookahead_size) & TDEFL_LZ_DICT_SIZE_MASK; mz_uint num_bytes_to_process = (mz_uint)MZ_MIN(d->m_src_buf_left, TDEFL_COMP_FAST_LOOKAHEAD_SIZE - lookahead_size); d->m_src_buf_left -= num_bytes_to_process; lookahead_size += num_bytes_to_process; while (num_bytes_to_process) { mz_uint32 n = MZ_MIN(TDEFL_LZ_DICT_SIZE - dst_pos, num_bytes_to_process); memcpy(d->m_dict + dst_pos, d->m_pSrc, n); if (dst_pos < (TDEFL_MAX_MATCH_LEN - 1)) memcpy(d->m_dict + TDEFL_LZ_DICT_SIZE + dst_pos, d->m_pSrc, MZ_MIN(n, (TDEFL_MAX_MATCH_LEN - 1) - dst_pos)); d->m_pSrc += n; dst_pos = (dst_pos + n) & TDEFL_LZ_DICT_SIZE_MASK; num_bytes_to_process -= n; } dict_size = MZ_MIN(TDEFL_LZ_DICT_SIZE - lookahead_size, dict_size); if ((!d->m_flush) && (lookahead_size < TDEFL_COMP_FAST_LOOKAHEAD_SIZE)) break; while (lookahead_size >= 4) { mz_uint cur_match_dist, cur_match_len = 1; mz_uint8 *pCur_dict = d->m_dict + cur_pos; mz_uint first_trigram = TDEFL_READ_UNALIGNED_WORD32(pCur_dict) & 0xFFFFFF; mz_uint hash = (first_trigram ^ (first_trigram >> (24 - (TDEFL_LZ_HASH_BITS - 8)))) & TDEFL_LEVEL1_HASH_SIZE_MASK; mz_uint probe_pos = d->m_hash[hash]; d->m_hash[hash] = (mz_uint16)lookahead_pos; if (((cur_match_dist = (mz_uint16)(lookahead_pos - probe_pos)) <= dict_size) && ((TDEFL_READ_UNALIGNED_WORD32(d->m_dict + (probe_pos &= TDEFL_LZ_DICT_SIZE_MASK)) & 0xFFFFFF) == first_trigram)) { const mz_uint16 *p = (const mz_uint16 *)pCur_dict; const mz_uint16 *q = (const mz_uint16 *)(d->m_dict + probe_pos); mz_uint32 probe_len = 32; do { } while ((TDEFL_READ_UNALIGNED_WORD2(++p) == TDEFL_READ_UNALIGNED_WORD2(++q)) && (TDEFL_READ_UNALIGNED_WORD2(++p) == TDEFL_READ_UNALIGNED_WORD2(++q)) && (TDEFL_READ_UNALIGNED_WORD2(++p) == TDEFL_READ_UNALIGNED_WORD2(++q)) && (TDEFL_READ_UNALIGNED_WORD2(++p) == TDEFL_READ_UNALIGNED_WORD2(++q)) && (--probe_len > 0)); cur_match_len = ((mz_uint)(p - (const mz_uint16 *)pCur_dict) * 2) + (mz_uint)(*(const mz_uint8 *)p == *(const mz_uint8 *)q); if (!probe_len) cur_match_len = cur_match_dist ? TDEFL_MAX_MATCH_LEN : 0; if ((cur_match_len < TDEFL_MIN_MATCH_LEN) || ((cur_match_len == TDEFL_MIN_MATCH_LEN) && (cur_match_dist >= 8U * 1024U))) { cur_match_len = 1; *pLZ_code_buf++ = (mz_uint8)first_trigram; *pLZ_flags = (mz_uint8)(*pLZ_flags >> 1); d->m_huff_count[0][(mz_uint8)first_trigram]++; } else { mz_uint32 s0, s1; cur_match_len = MZ_MIN(cur_match_len, lookahead_size); MZ_ASSERT((cur_match_len >= TDEFL_MIN_MATCH_LEN) && (cur_match_dist >= 1) && (cur_match_dist <= TDEFL_LZ_DICT_SIZE)); cur_match_dist--; pLZ_code_buf[0] = (mz_uint8)(cur_match_len - TDEFL_MIN_MATCH_LEN); #ifdef MINIZ_UNALIGNED_USE_MEMCPY memcpy(&pLZ_code_buf[1], &cur_match_dist, sizeof(cur_match_dist)); #else *(mz_uint16 *)(&pLZ_code_buf[1]) = (mz_uint16)cur_match_dist; #endif pLZ_code_buf += 3; *pLZ_flags = (mz_uint8)((*pLZ_flags >> 1) | 0x80); s0 = s_tdefl_small_dist_sym[cur_match_dist & 511]; s1 = s_tdefl_large_dist_sym[cur_match_dist >> 8]; d->m_huff_count[1][(cur_match_dist < 512) ? s0 : s1]++; d->m_huff_count[0][s_tdefl_len_sym[cur_match_len - TDEFL_MIN_MATCH_LEN]]++; } } else { *pLZ_code_buf++ = (mz_uint8)first_trigram; *pLZ_flags = (mz_uint8)(*pLZ_flags >> 1); d->m_huff_count[0][(mz_uint8)first_trigram]++; } if (--num_flags_left == 0) { num_flags_left = 8; pLZ_flags = pLZ_code_buf++; } total_lz_bytes += cur_match_len; lookahead_pos += cur_match_len; dict_size = MZ_MIN(dict_size + cur_match_len, (mz_uint)TDEFL_LZ_DICT_SIZE); cur_pos = (cur_pos + cur_match_len) & TDEFL_LZ_DICT_SIZE_MASK; MZ_ASSERT(lookahead_size >= cur_match_len); lookahead_size -= cur_match_len; if (pLZ_code_buf > &d->m_lz_code_buf[TDEFL_LZ_CODE_BUF_SIZE - 8]) { int n; d->m_lookahead_pos = lookahead_pos; d->m_lookahead_size = lookahead_size; d->m_dict_size = dict_size; d->m_total_lz_bytes = total_lz_bytes; d->m_pLZ_code_buf = pLZ_code_buf; d->m_pLZ_flags = pLZ_flags; d->m_num_flags_left = num_flags_left; if ((n = tdefl_flush_block(d, 0)) != 0) return (n < 0) ? MZ_FALSE : MZ_TRUE; total_lz_bytes = d->m_total_lz_bytes; pLZ_code_buf = d->m_pLZ_code_buf; pLZ_flags = d->m_pLZ_flags; num_flags_left = d->m_num_flags_left; } } while (lookahead_size) { mz_uint8 lit = d->m_dict[cur_pos]; total_lz_bytes++; *pLZ_code_buf++ = lit; *pLZ_flags = (mz_uint8)(*pLZ_flags >> 1); if (--num_flags_left == 0) { num_flags_left = 8; pLZ_flags = pLZ_code_buf++; } d->m_huff_count[0][lit]++; lookahead_pos++; dict_size = MZ_MIN(dict_size + 1, (mz_uint)TDEFL_LZ_DICT_SIZE); cur_pos = (cur_pos + 1) & TDEFL_LZ_DICT_SIZE_MASK; lookahead_size--; if (pLZ_code_buf > &d->m_lz_code_buf[TDEFL_LZ_CODE_BUF_SIZE - 8]) { int n; d->m_lookahead_pos = lookahead_pos; d->m_lookahead_size = lookahead_size; d->m_dict_size = dict_size; d->m_total_lz_bytes = total_lz_bytes; d->m_pLZ_code_buf = pLZ_code_buf; d->m_pLZ_flags = pLZ_flags; d->m_num_flags_left = num_flags_left; if ((n = tdefl_flush_block(d, 0)) != 0) return (n < 0) ? MZ_FALSE : MZ_TRUE; total_lz_bytes = d->m_total_lz_bytes; pLZ_code_buf = d->m_pLZ_code_buf; pLZ_flags = d->m_pLZ_flags; num_flags_left = d->m_num_flags_left; } } } d->m_lookahead_pos = lookahead_pos; d->m_lookahead_size = lookahead_size; d->m_dict_size = dict_size; d->m_total_lz_bytes = total_lz_bytes; d->m_pLZ_code_buf = pLZ_code_buf; d->m_pLZ_flags = pLZ_flags; d->m_num_flags_left = num_flags_left; return MZ_TRUE; } #endif /* MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN */ static MZ_FORCEINLINE void tdefl_record_literal(tdefl_compressor *d, mz_uint8 lit) { d->m_total_lz_bytes++; *d->m_pLZ_code_buf++ = lit; *d->m_pLZ_flags = (mz_uint8)(*d->m_pLZ_flags >> 1); if (--d->m_num_flags_left == 0) { d->m_num_flags_left = 8; d->m_pLZ_flags = d->m_pLZ_code_buf++; } d->m_huff_count[0][lit]++; } static MZ_FORCEINLINE void tdefl_record_match(tdefl_compressor *d, mz_uint match_len, mz_uint match_dist) { mz_uint32 s0, s1; MZ_ASSERT((match_len >= TDEFL_MIN_MATCH_LEN) && (match_dist >= 1) && (match_dist <= TDEFL_LZ_DICT_SIZE)); d->m_total_lz_bytes += match_len; d->m_pLZ_code_buf[0] = (mz_uint8)(match_len - TDEFL_MIN_MATCH_LEN); match_dist -= 1; d->m_pLZ_code_buf[1] = (mz_uint8)(match_dist & 0xFF); d->m_pLZ_code_buf[2] = (mz_uint8)(match_dist >> 8); d->m_pLZ_code_buf += 3; *d->m_pLZ_flags = (mz_uint8)((*d->m_pLZ_flags >> 1) | 0x80); if (--d->m_num_flags_left == 0) { d->m_num_flags_left = 8; d->m_pLZ_flags = d->m_pLZ_code_buf++; } s0 = s_tdefl_small_dist_sym[match_dist & 511]; s1 = s_tdefl_large_dist_sym[(match_dist >> 8) & 127]; d->m_huff_count[1][(match_dist < 512) ? s0 : s1]++; d->m_huff_count[0][s_tdefl_len_sym[match_len - TDEFL_MIN_MATCH_LEN]]++; } static mz_bool tdefl_compress_normal(tdefl_compressor *d) { const mz_uint8 *pSrc = d->m_pSrc; size_t src_buf_left = d->m_src_buf_left; tdefl_flush flush = d->m_flush; while ((src_buf_left) || ((flush) && (d->m_lookahead_size))) { mz_uint len_to_move, cur_match_dist, cur_match_len, cur_pos; /* Update dictionary and hash chains. Keeps the lookahead size equal to TDEFL_MAX_MATCH_LEN. */ if ((d->m_lookahead_size + d->m_dict_size) >= (TDEFL_MIN_MATCH_LEN - 1)) { mz_uint dst_pos = (d->m_lookahead_pos + d->m_lookahead_size) & TDEFL_LZ_DICT_SIZE_MASK, ins_pos = d->m_lookahead_pos + d->m_lookahead_size - 2; mz_uint hash = (d->m_dict[ins_pos & TDEFL_LZ_DICT_SIZE_MASK] << TDEFL_LZ_HASH_SHIFT) ^ d->m_dict[(ins_pos + 1) & TDEFL_LZ_DICT_SIZE_MASK]; mz_uint num_bytes_to_process = (mz_uint)MZ_MIN(src_buf_left, TDEFL_MAX_MATCH_LEN - d->m_lookahead_size); const mz_uint8 *pSrc_end = pSrc ? pSrc + num_bytes_to_process : NULL; src_buf_left -= num_bytes_to_process; d->m_lookahead_size += num_bytes_to_process; while (pSrc != pSrc_end) { mz_uint8 c = *pSrc++; d->m_dict[dst_pos] = c; if (dst_pos < (TDEFL_MAX_MATCH_LEN - 1)) d->m_dict[TDEFL_LZ_DICT_SIZE + dst_pos] = c; hash = ((hash << TDEFL_LZ_HASH_SHIFT) ^ c) & (TDEFL_LZ_HASH_SIZE - 1); d->m_next[ins_pos & TDEFL_LZ_DICT_SIZE_MASK] = d->m_hash[hash]; d->m_hash[hash] = (mz_uint16)(ins_pos); dst_pos = (dst_pos + 1) & TDEFL_LZ_DICT_SIZE_MASK; ins_pos++; } } else { while ((src_buf_left) && (d->m_lookahead_size < TDEFL_MAX_MATCH_LEN)) { mz_uint8 c = *pSrc++; mz_uint dst_pos = (d->m_lookahead_pos + d->m_lookahead_size) & TDEFL_LZ_DICT_SIZE_MASK; src_buf_left--; d->m_dict[dst_pos] = c; if (dst_pos < (TDEFL_MAX_MATCH_LEN - 1)) d->m_dict[TDEFL_LZ_DICT_SIZE + dst_pos] = c; if ((++d->m_lookahead_size + d->m_dict_size) >= TDEFL_MIN_MATCH_LEN) { mz_uint ins_pos = d->m_lookahead_pos + (d->m_lookahead_size - 1) - 2; mz_uint hash = ((d->m_dict[ins_pos & TDEFL_LZ_DICT_SIZE_MASK] << (TDEFL_LZ_HASH_SHIFT * 2)) ^ (d->m_dict[(ins_pos + 1) & TDEFL_LZ_DICT_SIZE_MASK] << TDEFL_LZ_HASH_SHIFT) ^ c) & (TDEFL_LZ_HASH_SIZE - 1); d->m_next[ins_pos & TDEFL_LZ_DICT_SIZE_MASK] = d->m_hash[hash]; d->m_hash[hash] = (mz_uint16)(ins_pos); } } } d->m_dict_size = MZ_MIN(TDEFL_LZ_DICT_SIZE - d->m_lookahead_size, d->m_dict_size); if ((!flush) && (d->m_lookahead_size < TDEFL_MAX_MATCH_LEN)) break; /* Simple lazy/greedy parsing state machine. */ len_to_move = 1; cur_match_dist = 0; cur_match_len = d->m_saved_match_len ? d->m_saved_match_len : (TDEFL_MIN_MATCH_LEN - 1); cur_pos = d->m_lookahead_pos & TDEFL_LZ_DICT_SIZE_MASK; if (d->m_flags & (TDEFL_RLE_MATCHES | TDEFL_FORCE_ALL_RAW_BLOCKS)) { if ((d->m_dict_size) && (!(d->m_flags & TDEFL_FORCE_ALL_RAW_BLOCKS))) { mz_uint8 c = d->m_dict[(cur_pos - 1) & TDEFL_LZ_DICT_SIZE_MASK]; cur_match_len = 0; while (cur_match_len < d->m_lookahead_size) { if (d->m_dict[cur_pos + cur_match_len] != c) break; cur_match_len++; } if (cur_match_len < TDEFL_MIN_MATCH_LEN) cur_match_len = 0; else cur_match_dist = 1; } } else { tdefl_find_match(d, d->m_lookahead_pos, d->m_dict_size, d->m_lookahead_size, &cur_match_dist, &cur_match_len); } if (((cur_match_len == TDEFL_MIN_MATCH_LEN) && (cur_match_dist >= 8U * 1024U)) || (cur_pos == cur_match_dist) || ((d->m_flags & TDEFL_FILTER_MATCHES) && (cur_match_len <= 5))) { cur_match_dist = cur_match_len = 0; } if (d->m_saved_match_len) { if (cur_match_len > d->m_saved_match_len) { tdefl_record_literal(d, (mz_uint8)d->m_saved_lit); if (cur_match_len >= 128) { tdefl_record_match(d, cur_match_len, cur_match_dist); d->m_saved_match_len = 0; len_to_move = cur_match_len; } else { d->m_saved_lit = d->m_dict[cur_pos]; d->m_saved_match_dist = cur_match_dist; d->m_saved_match_len = cur_match_len; } } else { tdefl_record_match(d, d->m_saved_match_len, d->m_saved_match_dist); len_to_move = d->m_saved_match_len - 1; d->m_saved_match_len = 0; } } else if (!cur_match_dist) tdefl_record_literal(d, d->m_dict[MZ_MIN(cur_pos, sizeof(d->m_dict) - 1)]); else if ((d->m_greedy_parsing) || (d->m_flags & TDEFL_RLE_MATCHES) || (cur_match_len >= 128)) { tdefl_record_match(d, cur_match_len, cur_match_dist); len_to_move = cur_match_len; } else { d->m_saved_lit = d->m_dict[MZ_MIN(cur_pos, sizeof(d->m_dict) - 1)]; d->m_saved_match_dist = cur_match_dist; d->m_saved_match_len = cur_match_len; } /* Move the lookahead forward by len_to_move bytes. */ d->m_lookahead_pos += len_to_move; MZ_ASSERT(d->m_lookahead_size >= len_to_move); d->m_lookahead_size -= len_to_move; d->m_dict_size = MZ_MIN(d->m_dict_size + len_to_move, (mz_uint)TDEFL_LZ_DICT_SIZE); /* Check if it's time to flush the current LZ codes to the internal output buffer. */ if ((d->m_pLZ_code_buf > &d->m_lz_code_buf[TDEFL_LZ_CODE_BUF_SIZE - 8]) || ((d->m_total_lz_bytes > 31 * 1024) && (((((mz_uint)(d->m_pLZ_code_buf - d->m_lz_code_buf) * 115) >> 7) >= d->m_total_lz_bytes) || (d->m_flags & TDEFL_FORCE_ALL_RAW_BLOCKS)))) { int n; d->m_pSrc = pSrc; d->m_src_buf_left = src_buf_left; if ((n = tdefl_flush_block(d, 0)) != 0) return (n < 0) ? MZ_FALSE : MZ_TRUE; } } d->m_pSrc = pSrc; d->m_src_buf_left = src_buf_left; return MZ_TRUE; } static tdefl_status tdefl_flush_output_buffer(tdefl_compressor *d) { if (d->m_pIn_buf_size) { *d->m_pIn_buf_size = d->m_pSrc - (const mz_uint8 *)d->m_pIn_buf; } if (d->m_pOut_buf_size) { size_t n = MZ_MIN(*d->m_pOut_buf_size - d->m_out_buf_ofs, d->m_output_flush_remaining); memcpy((mz_uint8 *)d->m_pOut_buf + d->m_out_buf_ofs, d->m_output_buf + d->m_output_flush_ofs, n); d->m_output_flush_ofs += (mz_uint)n; d->m_output_flush_remaining -= (mz_uint)n; d->m_out_buf_ofs += n; *d->m_pOut_buf_size = d->m_out_buf_ofs; } return (d->m_finished && !d->m_output_flush_remaining) ? TDEFL_STATUS_DONE : TDEFL_STATUS_OKAY; } tdefl_status tdefl_compress(tdefl_compressor *d, const void *pIn_buf, size_t *pIn_buf_size, void *pOut_buf, size_t *pOut_buf_size, tdefl_flush flush) { if (!d) { if (pIn_buf_size) *pIn_buf_size = 0; if (pOut_buf_size) *pOut_buf_size = 0; return TDEFL_STATUS_BAD_PARAM; } d->m_pIn_buf = pIn_buf; d->m_pIn_buf_size = pIn_buf_size; d->m_pOut_buf = pOut_buf; d->m_pOut_buf_size = pOut_buf_size; d->m_pSrc = (const mz_uint8 *)(pIn_buf); d->m_src_buf_left = pIn_buf_size ? *pIn_buf_size : 0; d->m_out_buf_ofs = 0; d->m_flush = flush; if (((d->m_pPut_buf_func != NULL) == ((pOut_buf != NULL) || (pOut_buf_size != NULL))) || (d->m_prev_return_status != TDEFL_STATUS_OKAY) || (d->m_wants_to_finish && (flush != TDEFL_FINISH)) || (pIn_buf_size && *pIn_buf_size && !pIn_buf) || (pOut_buf_size && *pOut_buf_size && !pOut_buf)) { if (pIn_buf_size) *pIn_buf_size = 0; if (pOut_buf_size) *pOut_buf_size = 0; return (d->m_prev_return_status = TDEFL_STATUS_BAD_PARAM); } d->m_wants_to_finish |= (flush == TDEFL_FINISH); if ((d->m_output_flush_remaining) || (d->m_finished)) return (d->m_prev_return_status = tdefl_flush_output_buffer(d)); #if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN if (((d->m_flags & TDEFL_MAX_PROBES_MASK) == 1) && ((d->m_flags & TDEFL_GREEDY_PARSING_FLAG) != 0) && ((d->m_flags & (TDEFL_FILTER_MATCHES | TDEFL_FORCE_ALL_RAW_BLOCKS | TDEFL_RLE_MATCHES)) == 0)) { if (!tdefl_compress_fast(d)) return d->m_prev_return_status; } else #endif /* #if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN */ { if (!tdefl_compress_normal(d)) return d->m_prev_return_status; } if ((d->m_flags & (TDEFL_WRITE_ZLIB_HEADER | TDEFL_COMPUTE_ADLER32)) && (pIn_buf)) d->m_adler32 = (mz_uint32)mz_adler32(d->m_adler32, (const mz_uint8 *)pIn_buf, d->m_pSrc - (const mz_uint8 *)pIn_buf); if ((flush) && (!d->m_lookahead_size) && (!d->m_src_buf_left) && (!d->m_output_flush_remaining)) { if (tdefl_flush_block(d, flush) < 0) return d->m_prev_return_status; d->m_finished = (flush == TDEFL_FINISH); if (flush == TDEFL_FULL_FLUSH) { MZ_CLEAR_ARR(d->m_hash); MZ_CLEAR_ARR(d->m_next); d->m_dict_size = 0; } } return (d->m_prev_return_status = tdefl_flush_output_buffer(d)); } tdefl_status tdefl_compress_buffer(tdefl_compressor *d, const void *pIn_buf, size_t in_buf_size, tdefl_flush flush) { MZ_ASSERT(d->m_pPut_buf_func); return tdefl_compress(d, pIn_buf, &in_buf_size, NULL, NULL, flush); } tdefl_status tdefl_init(tdefl_compressor *d, tdefl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags) { d->m_pPut_buf_func = pPut_buf_func; d->m_pPut_buf_user = pPut_buf_user; d->m_flags = (mz_uint)(flags); d->m_max_probes[0] = 1 + ((flags & 0xFFF) + 2) / 3; d->m_greedy_parsing = (flags & TDEFL_GREEDY_PARSING_FLAG) != 0; d->m_max_probes[1] = 1 + (((flags & 0xFFF) >> 2) + 2) / 3; if (!(flags & TDEFL_NONDETERMINISTIC_PARSING_FLAG)) MZ_CLEAR_ARR(d->m_hash); d->m_lookahead_pos = d->m_lookahead_size = d->m_dict_size = d->m_total_lz_bytes = d->m_lz_code_buf_dict_pos = d->m_bits_in = 0; d->m_output_flush_ofs = d->m_output_flush_remaining = d->m_finished = d->m_block_index = d->m_bit_buffer = d->m_wants_to_finish = 0; d->m_pLZ_code_buf = d->m_lz_code_buf + 1; d->m_pLZ_flags = d->m_lz_code_buf; *d->m_pLZ_flags = 0; d->m_num_flags_left = 8; d->m_pOutput_buf = d->m_output_buf; d->m_pOutput_buf_end = d->m_output_buf; d->m_prev_return_status = TDEFL_STATUS_OKAY; d->m_saved_match_dist = d->m_saved_match_len = d->m_saved_lit = 0; d->m_adler32 = 1; d->m_pIn_buf = NULL; d->m_pOut_buf = NULL; d->m_pIn_buf_size = NULL; d->m_pOut_buf_size = NULL; d->m_flush = TDEFL_NO_FLUSH; d->m_pSrc = NULL; d->m_src_buf_left = 0; d->m_out_buf_ofs = 0; if (!(flags & TDEFL_NONDETERMINISTIC_PARSING_FLAG)) MZ_CLEAR_ARR(d->m_dict); memset(&d->m_huff_count[0][0], 0, sizeof(d->m_huff_count[0][0]) * TDEFL_MAX_HUFF_SYMBOLS_0); memset(&d->m_huff_count[1][0], 0, sizeof(d->m_huff_count[1][0]) * TDEFL_MAX_HUFF_SYMBOLS_1); return TDEFL_STATUS_OKAY; } tdefl_status tdefl_get_prev_return_status(tdefl_compressor *d) { return d->m_prev_return_status; } mz_uint32 tdefl_get_adler32(tdefl_compressor *d) { return d->m_adler32; } mz_bool tdefl_compress_mem_to_output(const void *pBuf, size_t buf_len, tdefl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags) { tdefl_compressor *pComp; mz_bool succeeded; if (((buf_len) && (!pBuf)) || (!pPut_buf_func)) return MZ_FALSE; pComp = (tdefl_compressor *)MZ_MALLOC(sizeof(tdefl_compressor)); if (!pComp) return MZ_FALSE; succeeded = (tdefl_init(pComp, pPut_buf_func, pPut_buf_user, flags) == TDEFL_STATUS_OKAY); succeeded = succeeded && (tdefl_compress_buffer(pComp, pBuf, buf_len, TDEFL_FINISH) == TDEFL_STATUS_DONE); MZ_FREE(pComp); return succeeded; } typedef struct { size_t m_size, m_capacity; mz_uint8 *m_pBuf; mz_bool m_expandable; } tdefl_output_buffer; static mz_bool tdefl_output_buffer_putter(const void *pBuf, int len, void *pUser) { tdefl_output_buffer *p = (tdefl_output_buffer *)pUser; size_t new_size = p->m_size + len; if (new_size > p->m_capacity) { size_t new_capacity = p->m_capacity; mz_uint8 *pNew_buf; if (!p->m_expandable) return MZ_FALSE; do { new_capacity = MZ_MAX(128U, new_capacity << 1U); } while (new_size > new_capacity); pNew_buf = (mz_uint8 *)MZ_REALLOC(p->m_pBuf, new_capacity); if (!pNew_buf) return MZ_FALSE; p->m_pBuf = pNew_buf; p->m_capacity = new_capacity; } memcpy((mz_uint8 *)p->m_pBuf + p->m_size, pBuf, len); p->m_size = new_size; return MZ_TRUE; } void *tdefl_compress_mem_to_heap(const void *pSrc_buf, size_t src_buf_len, size_t *pOut_len, int flags) { tdefl_output_buffer out_buf; MZ_CLEAR_OBJ(out_buf); if (!pOut_len) return MZ_FALSE; else *pOut_len = 0; out_buf.m_expandable = MZ_TRUE; if (!tdefl_compress_mem_to_output(pSrc_buf, src_buf_len, tdefl_output_buffer_putter, &out_buf, flags)) return NULL; *pOut_len = out_buf.m_size; return out_buf.m_pBuf; } size_t tdefl_compress_mem_to_mem(void *pOut_buf, size_t out_buf_len, const void *pSrc_buf, size_t src_buf_len, int flags) { tdefl_output_buffer out_buf; MZ_CLEAR_OBJ(out_buf); if (!pOut_buf) return 0; out_buf.m_pBuf = (mz_uint8 *)pOut_buf; out_buf.m_capacity = out_buf_len; if (!tdefl_compress_mem_to_output(pSrc_buf, src_buf_len, tdefl_output_buffer_putter, &out_buf, flags)) return 0; return out_buf.m_size; } static const mz_uint s_tdefl_num_probes[11] = { 0, 1, 6, 32, 16, 32, 128, 256, 512, 768, 1500 }; /* level may actually range from [0,10] (10 is a "hidden" max level, where we want a bit more compression and it's fine if throughput to fall off a cliff on some files). */ mz_uint tdefl_create_comp_flags_from_zip_params(int level, int window_bits, int strategy) { mz_uint comp_flags = s_tdefl_num_probes[(level >= 0) ? MZ_MIN(10, level) : MZ_DEFAULT_LEVEL] | ((level <= 3) ? TDEFL_GREEDY_PARSING_FLAG : 0); if (window_bits > 0) comp_flags |= TDEFL_WRITE_ZLIB_HEADER; if (!level) comp_flags |= TDEFL_FORCE_ALL_RAW_BLOCKS; else if (strategy == MZ_FILTERED) comp_flags |= TDEFL_FILTER_MATCHES; else if (strategy == MZ_HUFFMAN_ONLY) comp_flags &= ~TDEFL_MAX_PROBES_MASK; else if (strategy == MZ_FIXED) comp_flags |= TDEFL_FORCE_ALL_STATIC_BLOCKS; else if (strategy == MZ_RLE) comp_flags |= TDEFL_RLE_MATCHES; return comp_flags; } #ifdef _MSC_VER #pragma warning(push) #pragma warning(disable : 4204) /* nonstandard extension used : non-constant aggregate initializer (also supported by GNU C and C99, so no big deal) */ #endif /* Simple PNG writer function by Alex Evans, 2011. Released into the public domain: https://gist.github.com/908299, more context at http://altdevblogaday.org/2011/04/06/a-smaller-jpg-encoder/. This is actually a modification of Alex's original code so PNG files generated by this function pass pngcheck. */ void *tdefl_write_image_to_png_file_in_memory_ex(const void *pImage, int w, int h, int num_chans, size_t *pLen_out, mz_uint level, mz_bool flip) { /* Using a local copy of this array here in case MINIZ_NO_ZLIB_APIS was defined. */ static const mz_uint s_tdefl_png_num_probes[11] = { 0, 1, 6, 32, 16, 32, 128, 256, 512, 768, 1500 }; tdefl_compressor *pComp = (tdefl_compressor *)MZ_MALLOC(sizeof(tdefl_compressor)); tdefl_output_buffer out_buf; int i, bpl = w * num_chans, y, z; mz_uint32 c; *pLen_out = 0; if (!pComp) return NULL; MZ_CLEAR_OBJ(out_buf); out_buf.m_expandable = MZ_TRUE; out_buf.m_capacity = 57 + MZ_MAX(64, (1 + bpl) * h); if (NULL == (out_buf.m_pBuf = (mz_uint8 *)MZ_MALLOC(out_buf.m_capacity))) { MZ_FREE(pComp); return NULL; } /* write dummy header */ for (z = 41; z; --z) tdefl_output_buffer_putter(&z, 1, &out_buf); /* compress image data */ tdefl_init(pComp, tdefl_output_buffer_putter, &out_buf, s_tdefl_png_num_probes[MZ_MIN(10, level)] | TDEFL_WRITE_ZLIB_HEADER); for (y = 0; y < h; ++y) { tdefl_compress_buffer(pComp, &z, 1, TDEFL_NO_FLUSH); tdefl_compress_buffer(pComp, (mz_uint8 *)pImage + (flip ? (h - 1 - y) : y) * bpl, bpl, TDEFL_NO_FLUSH); } if (tdefl_compress_buffer(pComp, NULL, 0, TDEFL_FINISH) != TDEFL_STATUS_DONE) { MZ_FREE(pComp); MZ_FREE(out_buf.m_pBuf); return NULL; } /* write real header */ *pLen_out = out_buf.m_size - 41; { static const mz_uint8 chans[] = { 0x00, 0x00, 0x04, 0x02, 0x06 }; mz_uint8 pnghdr[41] = { 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x49, 0x44, 0x41, 0x54 }; pnghdr[18] = (mz_uint8)(w >> 8); pnghdr[19] = (mz_uint8)w; pnghdr[22] = (mz_uint8)(h >> 8); pnghdr[23] = (mz_uint8)h; pnghdr[25] = chans[num_chans]; pnghdr[33] = (mz_uint8)(*pLen_out >> 24); pnghdr[34] = (mz_uint8)(*pLen_out >> 16); pnghdr[35] = (mz_uint8)(*pLen_out >> 8); pnghdr[36] = (mz_uint8)*pLen_out; c = (mz_uint32)mz_crc32(MZ_CRC32_INIT, pnghdr + 12, 17); for (i = 0; i < 4; ++i, c <<= 8) ((mz_uint8 *)(pnghdr + 29))[i] = (mz_uint8)(c >> 24); memcpy(out_buf.m_pBuf, pnghdr, 41); } /* write footer (IDAT CRC-32, followed by IEND chunk) */ if (!tdefl_output_buffer_putter("\0\0\0\0\0\0\0\0\x49\x45\x4e\x44\xae\x42\x60\x82", 16, &out_buf)) { *pLen_out = 0; MZ_FREE(pComp); MZ_FREE(out_buf.m_pBuf); return NULL; } c = (mz_uint32)mz_crc32(MZ_CRC32_INIT, out_buf.m_pBuf + 41 - 4, *pLen_out + 4); for (i = 0; i < 4; ++i, c <<= 8) (out_buf.m_pBuf + out_buf.m_size - 16)[i] = (mz_uint8)(c >> 24); /* compute final size of file, grab compressed data buffer and return */ *pLen_out += 57; MZ_FREE(pComp); return out_buf.m_pBuf; } void *tdefl_write_image_to_png_file_in_memory(const void *pImage, int w, int h, int num_chans, size_t *pLen_out) { /* Level 6 corresponds to TDEFL_DEFAULT_MAX_PROBES or MZ_DEFAULT_LEVEL (but we can't depend on MZ_DEFAULT_LEVEL being available in case the zlib API's where #defined out) */ return tdefl_write_image_to_png_file_in_memory_ex(pImage, w, h, num_chans, pLen_out, 6, MZ_FALSE); } #ifndef MINIZ_NO_MALLOC /* Allocate the tdefl_compressor and tinfl_decompressor structures in C so that */ /* non-C language bindings to tdefL_ and tinfl_ API don't need to worry about */ /* structure size and allocation mechanism. */ tdefl_compressor *tdefl_compressor_alloc(void) { return (tdefl_compressor *)MZ_MALLOC(sizeof(tdefl_compressor)); } void tdefl_compressor_free(tdefl_compressor *pComp) { MZ_FREE(pComp); } #endif #ifdef _MSC_VER #pragma warning(pop) #endif #ifdef __cplusplus } #endif #endif /*#ifndef MINIZ_NO_DEFLATE_APIS*/ /************************************************************************** * * Copyright 2013-2014 RAD Game Tools and Valve Software * Copyright 2010-2014 Rich Geldreich and Tenacious Software LLC * All Rights Reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. * **************************************************************************/ #ifndef MINIZ_NO_INFLATE_APIS #ifdef __cplusplus extern "C" { #endif /* ------------------- Low-level Decompression (completely independent from all compression API's) */ #define TINFL_MEMCPY(d, s, l) memcpy(d, s, l) #define TINFL_MEMSET(p, c, l) memset(p, c, l) #define TINFL_CR_BEGIN \ switch (r->m_state) \ { \ case 0: #define TINFL_CR_RETURN(state_index, result) \ do \ { \ status = result; \ r->m_state = state_index; \ goto common_exit; \ case state_index:; \ } \ MZ_MACRO_END #define TINFL_CR_RETURN_FOREVER(state_index, result) \ do \ { \ for (;;) \ { \ TINFL_CR_RETURN(state_index, result); \ } \ } \ MZ_MACRO_END #define TINFL_CR_FINISH } #define TINFL_GET_BYTE(state_index, c) \ do \ { \ while (pIn_buf_cur >= pIn_buf_end) \ { \ TINFL_CR_RETURN(state_index, (decomp_flags & TINFL_FLAG_HAS_MORE_INPUT) ? TINFL_STATUS_NEEDS_MORE_INPUT : TINFL_STATUS_FAILED_CANNOT_MAKE_PROGRESS); \ } \ c = *pIn_buf_cur++; \ } \ MZ_MACRO_END #define TINFL_NEED_BITS(state_index, n) \ do \ { \ mz_uint c; \ TINFL_GET_BYTE(state_index, c); \ bit_buf |= (((tinfl_bit_buf_t)c) << num_bits); \ num_bits += 8; \ } while (num_bits < (mz_uint)(n)) #define TINFL_SKIP_BITS(state_index, n) \ do \ { \ if (num_bits < (mz_uint)(n)) \ { \ TINFL_NEED_BITS(state_index, n); \ } \ bit_buf >>= (n); \ num_bits -= (n); \ } \ MZ_MACRO_END #define TINFL_GET_BITS(state_index, b, n) \ do \ { \ if (num_bits < (mz_uint)(n)) \ { \ TINFL_NEED_BITS(state_index, n); \ } \ b = bit_buf & ((1 << (n)) - 1); \ bit_buf >>= (n); \ num_bits -= (n); \ } \ MZ_MACRO_END /* TINFL_HUFF_BITBUF_FILL() is only used rarely, when the number of bytes remaining in the input buffer falls below 2. */ /* It reads just enough bytes from the input stream that are needed to decode the next Huffman code (and absolutely no more). It works by trying to fully decode a */ /* Huffman code by using whatever bits are currently present in the bit buffer. If this fails, it reads another byte, and tries again until it succeeds or until the */ /* bit buffer contains >=15 bits (deflate's max. Huffman code size). */ #define TINFL_HUFF_BITBUF_FILL(state_index, pLookUp, pTree) \ do \ { \ temp = pLookUp[bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]; \ if (temp >= 0) \ { \ code_len = temp >> 9; \ if ((code_len) && (num_bits >= code_len)) \ break; \ } \ else if (num_bits > TINFL_FAST_LOOKUP_BITS) \ { \ code_len = TINFL_FAST_LOOKUP_BITS; \ do \ { \ temp = pTree[~temp + ((bit_buf >> code_len++) & 1)]; \ } while ((temp < 0) && (num_bits >= (code_len + 1))); \ if (temp >= 0) \ break; \ } \ TINFL_GET_BYTE(state_index, c); \ bit_buf |= (((tinfl_bit_buf_t)c) << num_bits); \ num_bits += 8; \ } while (num_bits < 15); /* TINFL_HUFF_DECODE() decodes the next Huffman coded symbol. It's more complex than you would initially expect because the zlib API expects the decompressor to never read */ /* beyond the final byte of the deflate stream. (In other words, when this macro wants to read another byte from the input, it REALLY needs another byte in order to fully */ /* decode the next Huffman code.) Handling this properly is particularly important on raw deflate (non-zlib) streams, which aren't followed by a byte aligned adler-32. */ /* The slow path is only executed at the very end of the input buffer. */ /* v1.16: The original macro handled the case at the very end of the passed-in input buffer, but we also need to handle the case where the user passes in 1+zillion bytes */ /* following the deflate data and our non-conservative read-ahead path won't kick in here on this code. This is much trickier. */ #define TINFL_HUFF_DECODE(state_index, sym, pLookUp, pTree) \ do \ { \ int temp; \ mz_uint code_len, c; \ if (num_bits < 15) \ { \ if ((pIn_buf_end - pIn_buf_cur) < 2) \ { \ TINFL_HUFF_BITBUF_FILL(state_index, pLookUp, pTree); \ } \ else \ { \ bit_buf |= (((tinfl_bit_buf_t)pIn_buf_cur[0]) << num_bits) | (((tinfl_bit_buf_t)pIn_buf_cur[1]) << (num_bits + 8)); \ pIn_buf_cur += 2; \ num_bits += 16; \ } \ } \ if ((temp = pLookUp[bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]) >= 0) \ code_len = temp >> 9, temp &= 511; \ else \ { \ code_len = TINFL_FAST_LOOKUP_BITS; \ do \ { \ temp = pTree[~temp + ((bit_buf >> code_len++) & 1)]; \ } while (temp < 0); \ } \ sym = temp; \ bit_buf >>= code_len; \ num_bits -= code_len; \ } \ MZ_MACRO_END static void tinfl_clear_tree(tinfl_decompressor *r) { if (r->m_type == 0) MZ_CLEAR_ARR(r->m_tree_0); else if (r->m_type == 1) MZ_CLEAR_ARR(r->m_tree_1); else MZ_CLEAR_ARR(r->m_tree_2); } tinfl_status tinfl_decompress(tinfl_decompressor *r, const mz_uint8 *pIn_buf_next, size_t *pIn_buf_size, mz_uint8 *pOut_buf_start, mz_uint8 *pOut_buf_next, size_t *pOut_buf_size, const mz_uint32 decomp_flags) { static const mz_uint16 s_length_base[31] = { 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258, 0, 0 }; static const mz_uint8 s_length_extra[31] = { 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0, 0, 0 }; static const mz_uint16 s_dist_base[32] = { 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, 8193, 12289, 16385, 24577, 0, 0 }; static const mz_uint8 s_dist_extra[32] = { 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13 }; static const mz_uint8 s_length_dezigzag[19] = { 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 }; static const mz_uint16 s_min_table_sizes[3] = { 257, 1, 4 }; mz_int16 *pTrees[3]; mz_uint8 *pCode_sizes[3]; tinfl_status status = TINFL_STATUS_FAILED; mz_uint32 num_bits, dist, counter, num_extra; tinfl_bit_buf_t bit_buf; const mz_uint8 *pIn_buf_cur = pIn_buf_next, *const pIn_buf_end = pIn_buf_next + *pIn_buf_size; mz_uint8 *pOut_buf_cur = pOut_buf_next, *const pOut_buf_end = pOut_buf_next ? pOut_buf_next + *pOut_buf_size : NULL; size_t out_buf_size_mask = (decomp_flags & TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF) ? (size_t)-1 : ((pOut_buf_next - pOut_buf_start) + *pOut_buf_size) - 1, dist_from_out_buf_start; /* Ensure the output buffer's size is a power of 2, unless the output buffer is large enough to hold the entire output file (in which case it doesn't matter). */ if (((out_buf_size_mask + 1) & out_buf_size_mask) || (pOut_buf_next < pOut_buf_start)) { *pIn_buf_size = *pOut_buf_size = 0; return TINFL_STATUS_BAD_PARAM; } pTrees[0] = r->m_tree_0; pTrees[1] = r->m_tree_1; pTrees[2] = r->m_tree_2; pCode_sizes[0] = r->m_code_size_0; pCode_sizes[1] = r->m_code_size_1; pCode_sizes[2] = r->m_code_size_2; num_bits = r->m_num_bits; bit_buf = r->m_bit_buf; dist = r->m_dist; counter = r->m_counter; num_extra = r->m_num_extra; dist_from_out_buf_start = r->m_dist_from_out_buf_start; TINFL_CR_BEGIN bit_buf = num_bits = dist = counter = num_extra = r->m_zhdr0 = r->m_zhdr1 = 0; r->m_z_adler32 = r->m_check_adler32 = 1; if (decomp_flags & TINFL_FLAG_PARSE_ZLIB_HEADER) { TINFL_GET_BYTE(1, r->m_zhdr0); TINFL_GET_BYTE(2, r->m_zhdr1); counter = (((r->m_zhdr0 * 256 + r->m_zhdr1) % 31 != 0) || (r->m_zhdr1 & 32) || ((r->m_zhdr0 & 15) != 8)); if (!(decomp_flags & TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF)) counter |= (((1U << (8U + (r->m_zhdr0 >> 4))) > 32768U) || ((out_buf_size_mask + 1) < (size_t)((size_t)1 << (8U + (r->m_zhdr0 >> 4))))); if (counter) { TINFL_CR_RETURN_FOREVER(36, TINFL_STATUS_FAILED); } } do { TINFL_GET_BITS(3, r->m_final, 3); r->m_type = r->m_final >> 1; if (r->m_type == 0) { TINFL_SKIP_BITS(5, num_bits & 7); for (counter = 0; counter < 4; ++counter) { if (num_bits) TINFL_GET_BITS(6, r->m_raw_header[counter], 8); else TINFL_GET_BYTE(7, r->m_raw_header[counter]); } if ((counter = (r->m_raw_header[0] | (r->m_raw_header[1] << 8))) != (mz_uint)(0xFFFF ^ (r->m_raw_header[2] | (r->m_raw_header[3] << 8)))) { TINFL_CR_RETURN_FOREVER(39, TINFL_STATUS_FAILED); } while ((counter) && (num_bits)) { TINFL_GET_BITS(51, dist, 8); while (pOut_buf_cur >= pOut_buf_end) { TINFL_CR_RETURN(52, TINFL_STATUS_HAS_MORE_OUTPUT); } *pOut_buf_cur++ = (mz_uint8)dist; counter--; } while (counter) { size_t n; while (pOut_buf_cur >= pOut_buf_end) { TINFL_CR_RETURN(9, TINFL_STATUS_HAS_MORE_OUTPUT); } while (pIn_buf_cur >= pIn_buf_end) { TINFL_CR_RETURN(38, (decomp_flags & TINFL_FLAG_HAS_MORE_INPUT) ? TINFL_STATUS_NEEDS_MORE_INPUT : TINFL_STATUS_FAILED_CANNOT_MAKE_PROGRESS); } n = MZ_MIN(MZ_MIN((size_t)(pOut_buf_end - pOut_buf_cur), (size_t)(pIn_buf_end - pIn_buf_cur)), counter); TINFL_MEMCPY(pOut_buf_cur, pIn_buf_cur, n); pIn_buf_cur += n; pOut_buf_cur += n; counter -= (mz_uint)n; } } else if (r->m_type == 3) { TINFL_CR_RETURN_FOREVER(10, TINFL_STATUS_FAILED); } else { if (r->m_type == 1) { mz_uint8 *p = r->m_code_size_0; mz_uint i; r->m_table_sizes[0] = 288; r->m_table_sizes[1] = 32; TINFL_MEMSET(r->m_code_size_1, 5, 32); for (i = 0; i <= 143; ++i) *p++ = 8; for (; i <= 255; ++i) *p++ = 9; for (; i <= 279; ++i) *p++ = 7; for (; i <= 287; ++i) *p++ = 8; } else { for (counter = 0; counter < 3; counter++) { TINFL_GET_BITS(11, r->m_table_sizes[counter], "\05\05\04"[counter]); r->m_table_sizes[counter] += s_min_table_sizes[counter]; } MZ_CLEAR_ARR(r->m_code_size_2); for (counter = 0; counter < r->m_table_sizes[2]; counter++) { mz_uint s; TINFL_GET_BITS(14, s, 3); r->m_code_size_2[s_length_dezigzag[counter]] = (mz_uint8)s; } r->m_table_sizes[2] = 19; } for (; (int)r->m_type >= 0; r->m_type--) { int tree_next, tree_cur; mz_int16 *pLookUp; mz_int16 *pTree; mz_uint8 *pCode_size; mz_uint i, j, used_syms, total, sym_index, next_code[17], total_syms[16]; pLookUp = r->m_look_up[r->m_type]; pTree = pTrees[r->m_type]; pCode_size = pCode_sizes[r->m_type]; MZ_CLEAR_ARR(total_syms); TINFL_MEMSET(pLookUp, 0, sizeof(r->m_look_up[0])); tinfl_clear_tree(r); for (i = 0; i < r->m_table_sizes[r->m_type]; ++i) total_syms[pCode_size[i]]++; used_syms = 0, total = 0; next_code[0] = next_code[1] = 0; for (i = 1; i <= 15; ++i) { used_syms += total_syms[i]; next_code[i + 1] = (total = ((total + total_syms[i]) << 1)); } if ((65536 != total) && (used_syms > 1)) { TINFL_CR_RETURN_FOREVER(35, TINFL_STATUS_FAILED); } for (tree_next = -1, sym_index = 0; sym_index < r->m_table_sizes[r->m_type]; ++sym_index) { mz_uint rev_code = 0, l, cur_code, code_size = pCode_size[sym_index]; if (!code_size) continue; cur_code = next_code[code_size]++; for (l = code_size; l > 0; l--, cur_code >>= 1) rev_code = (rev_code << 1) | (cur_code & 1); if (code_size <= TINFL_FAST_LOOKUP_BITS) { mz_int16 k = (mz_int16)((code_size << 9) | sym_index); while (rev_code < TINFL_FAST_LOOKUP_SIZE) { pLookUp[rev_code] = k; rev_code += (1 << code_size); } continue; } if (0 == (tree_cur = pLookUp[rev_code & (TINFL_FAST_LOOKUP_SIZE - 1)])) { pLookUp[rev_code & (TINFL_FAST_LOOKUP_SIZE - 1)] = (mz_int16)tree_next; tree_cur = tree_next; tree_next -= 2; } rev_code >>= (TINFL_FAST_LOOKUP_BITS - 1); for (j = code_size; j > (TINFL_FAST_LOOKUP_BITS + 1); j--) { tree_cur -= ((rev_code >>= 1) & 1); if (!pTree[-tree_cur - 1]) { pTree[-tree_cur - 1] = (mz_int16)tree_next; tree_cur = tree_next; tree_next -= 2; } else tree_cur = pTree[-tree_cur - 1]; } tree_cur -= ((rev_code >>= 1) & 1); pTree[-tree_cur - 1] = (mz_int16)sym_index; } if (r->m_type == 2) { for (counter = 0; counter < (r->m_table_sizes[0] + r->m_table_sizes[1]);) { mz_uint s; TINFL_HUFF_DECODE(16, dist, r->m_look_up[2], r->m_tree_2); if (dist < 16) { r->m_len_codes[counter++] = (mz_uint8)dist; continue; } if ((dist == 16) && (!counter)) { TINFL_CR_RETURN_FOREVER(17, TINFL_STATUS_FAILED); } num_extra = "\02\03\07"[dist - 16]; TINFL_GET_BITS(18, s, num_extra); s += "\03\03\013"[dist - 16]; TINFL_MEMSET(r->m_len_codes + counter, (dist == 16) ? r->m_len_codes[counter - 1] : 0, s); counter += s; } if ((r->m_table_sizes[0] + r->m_table_sizes[1]) != counter) { TINFL_CR_RETURN_FOREVER(21, TINFL_STATUS_FAILED); } TINFL_MEMCPY(r->m_code_size_0, r->m_len_codes, r->m_table_sizes[0]); TINFL_MEMCPY(r->m_code_size_1, r->m_len_codes + r->m_table_sizes[0], r->m_table_sizes[1]); } } for (;;) { mz_uint8 *pSrc; for (;;) { if (((pIn_buf_end - pIn_buf_cur) < 4) || ((pOut_buf_end - pOut_buf_cur) < 2)) { TINFL_HUFF_DECODE(23, counter, r->m_look_up[0], r->m_tree_0); if (counter >= 256) break; while (pOut_buf_cur >= pOut_buf_end) { TINFL_CR_RETURN(24, TINFL_STATUS_HAS_MORE_OUTPUT); } *pOut_buf_cur++ = (mz_uint8)counter; } else { int sym2; mz_uint code_len; #if TINFL_USE_64BIT_BITBUF if (num_bits < 30) { bit_buf |= (((tinfl_bit_buf_t)MZ_READ_LE32(pIn_buf_cur)) << num_bits); pIn_buf_cur += 4; num_bits += 32; } #else if (num_bits < 15) { bit_buf |= (((tinfl_bit_buf_t)MZ_READ_LE16(pIn_buf_cur)) << num_bits); pIn_buf_cur += 2; num_bits += 16; } #endif if ((sym2 = r->m_look_up[0][bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]) >= 0) code_len = sym2 >> 9; else { code_len = TINFL_FAST_LOOKUP_BITS; do { sym2 = r->m_tree_0[~sym2 + ((bit_buf >> code_len++) & 1)]; } while (sym2 < 0); } counter = sym2; bit_buf >>= code_len; num_bits -= code_len; if (counter & 256) break; #if !TINFL_USE_64BIT_BITBUF if (num_bits < 15) { bit_buf |= (((tinfl_bit_buf_t)MZ_READ_LE16(pIn_buf_cur)) << num_bits); pIn_buf_cur += 2; num_bits += 16; } #endif if ((sym2 = r->m_look_up[0][bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]) >= 0) code_len = sym2 >> 9; else { code_len = TINFL_FAST_LOOKUP_BITS; do { sym2 = r->m_tree_0[~sym2 + ((bit_buf >> code_len++) & 1)]; } while (sym2 < 0); } bit_buf >>= code_len; num_bits -= code_len; pOut_buf_cur[0] = (mz_uint8)counter; if (sym2 & 256) { pOut_buf_cur++; counter = sym2; break; } pOut_buf_cur[1] = (mz_uint8)sym2; pOut_buf_cur += 2; } } if ((counter &= 511) == 256) break; num_extra = s_length_extra[counter - 257]; counter = s_length_base[counter - 257]; if (num_extra) { mz_uint extra_bits; TINFL_GET_BITS(25, extra_bits, num_extra); counter += extra_bits; } TINFL_HUFF_DECODE(26, dist, r->m_look_up[1], r->m_tree_1); num_extra = s_dist_extra[dist]; dist = s_dist_base[dist]; if (num_extra) { mz_uint extra_bits; TINFL_GET_BITS(27, extra_bits, num_extra); dist += extra_bits; } dist_from_out_buf_start = pOut_buf_cur - pOut_buf_start; if ((dist == 0 || dist > dist_from_out_buf_start || dist_from_out_buf_start == 0) && (decomp_flags & TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF)) { TINFL_CR_RETURN_FOREVER(37, TINFL_STATUS_FAILED); } pSrc = pOut_buf_start + ((dist_from_out_buf_start - dist) & out_buf_size_mask); if ((MZ_MAX(pOut_buf_cur, pSrc) + counter) > pOut_buf_end) { while (counter--) { while (pOut_buf_cur >= pOut_buf_end) { TINFL_CR_RETURN(53, TINFL_STATUS_HAS_MORE_OUTPUT); } *pOut_buf_cur++ = pOut_buf_start[(dist_from_out_buf_start++ - dist) & out_buf_size_mask]; } continue; } #if MINIZ_USE_UNALIGNED_LOADS_AND_STORES else if ((counter >= 9) && (counter <= dist)) { const mz_uint8 *pSrc_end = pSrc + (counter & ~7); do { #ifdef MINIZ_UNALIGNED_USE_MEMCPY memcpy(pOut_buf_cur, pSrc, sizeof(mz_uint32)*2); #else ((mz_uint32 *)pOut_buf_cur)[0] = ((const mz_uint32 *)pSrc)[0]; ((mz_uint32 *)pOut_buf_cur)[1] = ((const mz_uint32 *)pSrc)[1]; #endif pOut_buf_cur += 8; } while ((pSrc += 8) < pSrc_end); if ((counter &= 7) < 3) { if (counter) { pOut_buf_cur[0] = pSrc[0]; if (counter > 1) pOut_buf_cur[1] = pSrc[1]; pOut_buf_cur += counter; } continue; } } #endif while(counter>2) { pOut_buf_cur[0] = pSrc[0]; pOut_buf_cur[1] = pSrc[1]; pOut_buf_cur[2] = pSrc[2]; pOut_buf_cur += 3; pSrc += 3; counter -= 3; } if (counter > 0) { pOut_buf_cur[0] = pSrc[0]; if (counter > 1) pOut_buf_cur[1] = pSrc[1]; pOut_buf_cur += counter; } } } } while (!(r->m_final & 1)); /* Ensure byte alignment and put back any bytes from the bitbuf if we've looked ahead too far on gzip, or other Deflate streams followed by arbitrary data. */ /* I'm being super conservative here. A number of simplifications can be made to the byte alignment part, and the Adler32 check shouldn't ever need to worry about reading from the bitbuf now. */ TINFL_SKIP_BITS(32, num_bits & 7); while ((pIn_buf_cur > pIn_buf_next) && (num_bits >= 8)) { --pIn_buf_cur; num_bits -= 8; } bit_buf &= ~(~(tinfl_bit_buf_t)0 << num_bits); MZ_ASSERT(!num_bits); /* if this assert fires then we've read beyond the end of non-deflate/zlib streams with following data (such as gzip streams). */ if (decomp_flags & TINFL_FLAG_PARSE_ZLIB_HEADER) { for (counter = 0; counter < 4; ++counter) { mz_uint s; if (num_bits) TINFL_GET_BITS(41, s, 8); else TINFL_GET_BYTE(42, s); r->m_z_adler32 = (r->m_z_adler32 << 8) | s; } } TINFL_CR_RETURN_FOREVER(34, TINFL_STATUS_DONE); TINFL_CR_FINISH common_exit: /* As long as we aren't telling the caller that we NEED more input to make forward progress: */ /* Put back any bytes from the bitbuf in case we've looked ahead too far on gzip, or other Deflate streams followed by arbitrary data. */ /* We need to be very careful here to NOT push back any bytes we definitely know we need to make forward progress, though, or we'll lock the caller up into an inf loop. */ if ((status != TINFL_STATUS_NEEDS_MORE_INPUT) && (status != TINFL_STATUS_FAILED_CANNOT_MAKE_PROGRESS)) { while ((pIn_buf_cur > pIn_buf_next) && (num_bits >= 8)) { --pIn_buf_cur; num_bits -= 8; } } r->m_num_bits = num_bits; r->m_bit_buf = bit_buf & ~(~(tinfl_bit_buf_t)0 << num_bits); r->m_dist = dist; r->m_counter = counter; r->m_num_extra = num_extra; r->m_dist_from_out_buf_start = dist_from_out_buf_start; *pIn_buf_size = pIn_buf_cur - pIn_buf_next; *pOut_buf_size = pOut_buf_cur - pOut_buf_next; if ((decomp_flags & (TINFL_FLAG_PARSE_ZLIB_HEADER | TINFL_FLAG_COMPUTE_ADLER32)) && (status >= 0)) { const mz_uint8 *ptr = pOut_buf_next; size_t buf_len = *pOut_buf_size; mz_uint32 i, s1 = r->m_check_adler32 & 0xffff, s2 = r->m_check_adler32 >> 16; size_t block_len = buf_len % 5552; while (buf_len) { for (i = 0; i + 7 < block_len; i += 8, ptr += 8) { s1 += ptr[0], s2 += s1; s1 += ptr[1], s2 += s1; s1 += ptr[2], s2 += s1; s1 += ptr[3], s2 += s1; s1 += ptr[4], s2 += s1; s1 += ptr[5], s2 += s1; s1 += ptr[6], s2 += s1; s1 += ptr[7], s2 += s1; } for (; i < block_len; ++i) s1 += *ptr++, s2 += s1; s1 %= 65521U, s2 %= 65521U; buf_len -= block_len; block_len = 5552; } r->m_check_adler32 = (s2 << 16) + s1; if ((status == TINFL_STATUS_DONE) && (decomp_flags & TINFL_FLAG_PARSE_ZLIB_HEADER) && (r->m_check_adler32 != r->m_z_adler32)) status = TINFL_STATUS_ADLER32_MISMATCH; } return status; } /* Higher level helper functions. */ void *tinfl_decompress_mem_to_heap(const void *pSrc_buf, size_t src_buf_len, size_t *pOut_len, int flags) { tinfl_decompressor decomp; void *pBuf = NULL, *pNew_buf; size_t src_buf_ofs = 0, out_buf_capacity = 0; *pOut_len = 0; tinfl_init(&decomp); for (;;) { size_t src_buf_size = src_buf_len - src_buf_ofs, dst_buf_size = out_buf_capacity - *pOut_len, new_out_buf_capacity; tinfl_status status = tinfl_decompress(&decomp, (const mz_uint8 *)pSrc_buf + src_buf_ofs, &src_buf_size, (mz_uint8 *)pBuf, pBuf ? (mz_uint8 *)pBuf + *pOut_len : NULL, &dst_buf_size, (flags & ~TINFL_FLAG_HAS_MORE_INPUT) | TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF); if ((status < 0) || (status == TINFL_STATUS_NEEDS_MORE_INPUT)) { MZ_FREE(pBuf); *pOut_len = 0; return NULL; } src_buf_ofs += src_buf_size; *pOut_len += dst_buf_size; if (status == TINFL_STATUS_DONE) break; new_out_buf_capacity = out_buf_capacity * 2; if (new_out_buf_capacity < 128) new_out_buf_capacity = 128; pNew_buf = MZ_REALLOC(pBuf, new_out_buf_capacity); if (!pNew_buf) { MZ_FREE(pBuf); *pOut_len = 0; return NULL; } pBuf = pNew_buf; out_buf_capacity = new_out_buf_capacity; } return pBuf; } size_t tinfl_decompress_mem_to_mem(void *pOut_buf, size_t out_buf_len, const void *pSrc_buf, size_t src_buf_len, int flags) { tinfl_decompressor decomp; tinfl_status status; tinfl_init(&decomp); status = tinfl_decompress(&decomp, (const mz_uint8 *)pSrc_buf, &src_buf_len, (mz_uint8 *)pOut_buf, (mz_uint8 *)pOut_buf, &out_buf_len, (flags & ~TINFL_FLAG_HAS_MORE_INPUT) | TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF); return (status != TINFL_STATUS_DONE) ? TINFL_DECOMPRESS_MEM_TO_MEM_FAILED : out_buf_len; } int tinfl_decompress_mem_to_callback(const void *pIn_buf, size_t *pIn_buf_size, tinfl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags) { int result = 0; tinfl_decompressor decomp; mz_uint8 *pDict = (mz_uint8 *)MZ_MALLOC(TINFL_LZ_DICT_SIZE); size_t in_buf_ofs = 0, dict_ofs = 0; if (!pDict) return TINFL_STATUS_FAILED; memset(pDict,0,TINFL_LZ_DICT_SIZE); tinfl_init(&decomp); for (;;) { size_t in_buf_size = *pIn_buf_size - in_buf_ofs, dst_buf_size = TINFL_LZ_DICT_SIZE - dict_ofs; tinfl_status status = tinfl_decompress(&decomp, (const mz_uint8 *)pIn_buf + in_buf_ofs, &in_buf_size, pDict, pDict + dict_ofs, &dst_buf_size, (flags & ~(TINFL_FLAG_HAS_MORE_INPUT | TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF))); in_buf_ofs += in_buf_size; if ((dst_buf_size) && (!(*pPut_buf_func)(pDict + dict_ofs, (int)dst_buf_size, pPut_buf_user))) break; if (status != TINFL_STATUS_HAS_MORE_OUTPUT) { result = (status == TINFL_STATUS_DONE); break; } dict_ofs = (dict_ofs + dst_buf_size) & (TINFL_LZ_DICT_SIZE - 1); } MZ_FREE(pDict); *pIn_buf_size = in_buf_ofs; return result; } #ifndef MINIZ_NO_MALLOC tinfl_decompressor *tinfl_decompressor_alloc(void) { tinfl_decompressor *pDecomp = (tinfl_decompressor *)MZ_MALLOC(sizeof(tinfl_decompressor)); if (pDecomp) tinfl_init(pDecomp); return pDecomp; } void tinfl_decompressor_free(tinfl_decompressor *pDecomp) { MZ_FREE(pDecomp); } #endif #ifdef __cplusplus } #endif #endif /*#ifndef MINIZ_NO_INFLATE_APIS*/ /************************************************************************** * * Copyright 2013-2014 RAD Game Tools and Valve Software * Copyright 2010-2014 Rich Geldreich and Tenacious Software LLC * Copyright 2016 Martin Raiber * All Rights Reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. * **************************************************************************/ #ifndef MINIZ_NO_ARCHIVE_APIS #ifdef __cplusplus extern "C" { #endif /* ------------------- .ZIP archive reading */ #ifdef MINIZ_NO_STDIO #define MZ_FILE void * #else #include #if defined(_MSC_VER) || defined(__MINGW64__) #define WIN32_LEAN_AND_MEAN #include static WCHAR* mz_utf8z_to_widechar(const char* str) { int reqChars = MultiByteToWideChar(CP_UTF8, 0, str, -1, NULL, 0); WCHAR* wStr = (WCHAR*)malloc(reqChars * sizeof(WCHAR)); MultiByteToWideChar(CP_UTF8, 0, str, -1, wStr, reqChars); return wStr; } static FILE *mz_fopen(const char *pFilename, const char *pMode) { WCHAR* wFilename = mz_utf8z_to_widechar(pFilename); WCHAR* wMode = mz_utf8z_to_widechar(pMode); FILE* pFile = NULL; errno_t err = _wfopen_s(&pFile, wFilename, wMode); free(wFilename); free(wMode); return err ? NULL : pFile; } static FILE *mz_freopen(const char *pPath, const char *pMode, FILE *pStream) { WCHAR* wPath = mz_utf8z_to_widechar(pPath); WCHAR* wMode = mz_utf8z_to_widechar(pMode); FILE* pFile = NULL; errno_t err = _wfreopen_s(&pFile, wPath, wMode, pStream); free(wPath); free(wMode); return err ? NULL : pFile; } static int mz_stat64(const char *path, struct __stat64 *buffer) { WCHAR* wPath = mz_utf8z_to_widechar(path); int res = _wstat64(wPath, buffer); free(wPath); return res; } #ifndef MINIZ_NO_TIME #include #endif #define MZ_FOPEN mz_fopen #define MZ_FCLOSE fclose #define MZ_FREAD fread #define MZ_FWRITE fwrite #define MZ_FTELL64 _ftelli64 #define MZ_FSEEK64 _fseeki64 #define MZ_FILE_STAT_STRUCT _stat64 #define MZ_FILE_STAT mz_stat64 #define MZ_FFLUSH fflush #define MZ_FREOPEN mz_freopen #define MZ_DELETE_FILE remove #elif defined(__MINGW32__) || defined(__WATCOMC__) #ifndef MINIZ_NO_TIME #include #endif #define MZ_FOPEN(f, m) fopen(f, m) #define MZ_FCLOSE fclose #define MZ_FREAD fread #define MZ_FWRITE fwrite #define MZ_FTELL64 _ftelli64 #define MZ_FSEEK64 _fseeki64 #define MZ_FILE_STAT_STRUCT stat #define MZ_FILE_STAT stat #define MZ_FFLUSH fflush #define MZ_FREOPEN(f, m, s) freopen(f, m, s) #define MZ_DELETE_FILE remove #elif defined(__TINYC__) #ifndef MINIZ_NO_TIME #include #endif #define MZ_FOPEN(f, m) fopen(f, m) #define MZ_FCLOSE fclose #define MZ_FREAD fread #define MZ_FWRITE fwrite #define MZ_FTELL64 ftell #define MZ_FSEEK64 fseek #define MZ_FILE_STAT_STRUCT stat #define MZ_FILE_STAT stat #define MZ_FFLUSH fflush #define MZ_FREOPEN(f, m, s) freopen(f, m, s) #define MZ_DELETE_FILE remove #elif defined(__USE_LARGEFILE64) /* gcc, clang */ #ifndef MINIZ_NO_TIME #include #endif #define MZ_FOPEN(f, m) fopen64(f, m) #define MZ_FCLOSE fclose #define MZ_FREAD fread #define MZ_FWRITE fwrite #define MZ_FTELL64 ftello64 #define MZ_FSEEK64 fseeko64 #define MZ_FILE_STAT_STRUCT stat64 #define MZ_FILE_STAT stat64 #define MZ_FFLUSH fflush #define MZ_FREOPEN(p, m, s) freopen64(p, m, s) #define MZ_DELETE_FILE remove #elif defined(__APPLE__) || defined(__FreeBSD__) #ifndef MINIZ_NO_TIME #include #endif #define MZ_FOPEN(f, m) fopen(f, m) #define MZ_FCLOSE fclose #define MZ_FREAD fread #define MZ_FWRITE fwrite #define MZ_FTELL64 ftello #define MZ_FSEEK64 fseeko #define MZ_FILE_STAT_STRUCT stat #define MZ_FILE_STAT stat #define MZ_FFLUSH fflush #define MZ_FREOPEN(p, m, s) freopen(p, m, s) #define MZ_DELETE_FILE remove #else #pragma message("Using fopen, ftello, fseeko, stat() etc. path for file I/O - this path may not support large files.") #ifndef MINIZ_NO_TIME #include #endif #define MZ_FOPEN(f, m) fopen(f, m) #define MZ_FCLOSE fclose #define MZ_FREAD fread #define MZ_FWRITE fwrite #ifdef __STRICT_ANSI__ #define MZ_FTELL64 ftell #define MZ_FSEEK64 fseek #else #define MZ_FTELL64 ftello #define MZ_FSEEK64 fseeko #endif #define MZ_FILE_STAT_STRUCT stat #define MZ_FILE_STAT stat #define MZ_FFLUSH fflush #define MZ_FREOPEN(f, m, s) freopen(f, m, s) #define MZ_DELETE_FILE remove #endif /* #ifdef _MSC_VER */ #endif /* #ifdef MINIZ_NO_STDIO */ #define MZ_TOLOWER(c) ((((c) >= 'A') && ((c) <= 'Z')) ? ((c) - 'A' + 'a') : (c)) /* Various ZIP archive enums. To completely avoid cross platform compiler alignment and platform endian issues, miniz.c doesn't use structs for any of this stuff. */ enum { /* ZIP archive identifiers and record sizes */ MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG = 0x06054b50, MZ_ZIP_CENTRAL_DIR_HEADER_SIG = 0x02014b50, MZ_ZIP_LOCAL_DIR_HEADER_SIG = 0x04034b50, MZ_ZIP_LOCAL_DIR_HEADER_SIZE = 30, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE = 46, MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE = 22, /* ZIP64 archive identifier and record sizes */ MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIG = 0x06064b50, MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIG = 0x07064b50, MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE = 56, MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE = 20, MZ_ZIP64_EXTENDED_INFORMATION_FIELD_HEADER_ID = 0x0001, MZ_ZIP_DATA_DESCRIPTOR_ID = 0x08074b50, MZ_ZIP_DATA_DESCRIPTER_SIZE64 = 24, MZ_ZIP_DATA_DESCRIPTER_SIZE32 = 16, /* Central directory header record offsets */ MZ_ZIP_CDH_SIG_OFS = 0, MZ_ZIP_CDH_VERSION_MADE_BY_OFS = 4, MZ_ZIP_CDH_VERSION_NEEDED_OFS = 6, MZ_ZIP_CDH_BIT_FLAG_OFS = 8, MZ_ZIP_CDH_METHOD_OFS = 10, MZ_ZIP_CDH_FILE_TIME_OFS = 12, MZ_ZIP_CDH_FILE_DATE_OFS = 14, MZ_ZIP_CDH_CRC32_OFS = 16, MZ_ZIP_CDH_COMPRESSED_SIZE_OFS = 20, MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS = 24, MZ_ZIP_CDH_FILENAME_LEN_OFS = 28, MZ_ZIP_CDH_EXTRA_LEN_OFS = 30, MZ_ZIP_CDH_COMMENT_LEN_OFS = 32, MZ_ZIP_CDH_DISK_START_OFS = 34, MZ_ZIP_CDH_INTERNAL_ATTR_OFS = 36, MZ_ZIP_CDH_EXTERNAL_ATTR_OFS = 38, MZ_ZIP_CDH_LOCAL_HEADER_OFS = 42, /* Local directory header offsets */ MZ_ZIP_LDH_SIG_OFS = 0, MZ_ZIP_LDH_VERSION_NEEDED_OFS = 4, MZ_ZIP_LDH_BIT_FLAG_OFS = 6, MZ_ZIP_LDH_METHOD_OFS = 8, MZ_ZIP_LDH_FILE_TIME_OFS = 10, MZ_ZIP_LDH_FILE_DATE_OFS = 12, MZ_ZIP_LDH_CRC32_OFS = 14, MZ_ZIP_LDH_COMPRESSED_SIZE_OFS = 18, MZ_ZIP_LDH_DECOMPRESSED_SIZE_OFS = 22, MZ_ZIP_LDH_FILENAME_LEN_OFS = 26, MZ_ZIP_LDH_EXTRA_LEN_OFS = 28, MZ_ZIP_LDH_BIT_FLAG_HAS_LOCATOR = 1 << 3, /* End of central directory offsets */ MZ_ZIP_ECDH_SIG_OFS = 0, MZ_ZIP_ECDH_NUM_THIS_DISK_OFS = 4, MZ_ZIP_ECDH_NUM_DISK_CDIR_OFS = 6, MZ_ZIP_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS = 8, MZ_ZIP_ECDH_CDIR_TOTAL_ENTRIES_OFS = 10, MZ_ZIP_ECDH_CDIR_SIZE_OFS = 12, MZ_ZIP_ECDH_CDIR_OFS_OFS = 16, MZ_ZIP_ECDH_COMMENT_SIZE_OFS = 20, /* ZIP64 End of central directory locator offsets */ MZ_ZIP64_ECDL_SIG_OFS = 0, /* 4 bytes */ MZ_ZIP64_ECDL_NUM_DISK_CDIR_OFS = 4, /* 4 bytes */ MZ_ZIP64_ECDL_REL_OFS_TO_ZIP64_ECDR_OFS = 8, /* 8 bytes */ MZ_ZIP64_ECDL_TOTAL_NUMBER_OF_DISKS_OFS = 16, /* 4 bytes */ /* ZIP64 End of central directory header offsets */ MZ_ZIP64_ECDH_SIG_OFS = 0, /* 4 bytes */ MZ_ZIP64_ECDH_SIZE_OF_RECORD_OFS = 4, /* 8 bytes */ MZ_ZIP64_ECDH_VERSION_MADE_BY_OFS = 12, /* 2 bytes */ MZ_ZIP64_ECDH_VERSION_NEEDED_OFS = 14, /* 2 bytes */ MZ_ZIP64_ECDH_NUM_THIS_DISK_OFS = 16, /* 4 bytes */ MZ_ZIP64_ECDH_NUM_DISK_CDIR_OFS = 20, /* 4 bytes */ MZ_ZIP64_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS = 24, /* 8 bytes */ MZ_ZIP64_ECDH_CDIR_TOTAL_ENTRIES_OFS = 32, /* 8 bytes */ MZ_ZIP64_ECDH_CDIR_SIZE_OFS = 40, /* 8 bytes */ MZ_ZIP64_ECDH_CDIR_OFS_OFS = 48, /* 8 bytes */ MZ_ZIP_VERSION_MADE_BY_DOS_FILESYSTEM_ID = 0, MZ_ZIP_DOS_DIR_ATTRIBUTE_BITFLAG = 0x10, MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_IS_ENCRYPTED = 1, MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_COMPRESSED_PATCH_FLAG = 32, MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_USES_STRONG_ENCRYPTION = 64, MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_LOCAL_DIR_IS_MASKED = 8192, MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_UTF8 = 1 << 11 }; typedef struct { void *m_p; size_t m_size, m_capacity; mz_uint m_element_size; } mz_zip_array; struct mz_zip_internal_state_tag { mz_zip_array m_central_dir; mz_zip_array m_central_dir_offsets; mz_zip_array m_sorted_central_dir_offsets; /* The flags passed in when the archive is initially opened. */ mz_uint32 m_init_flags; /* MZ_TRUE if the archive has a zip64 end of central directory headers, etc. */ mz_bool m_zip64; /* MZ_TRUE if we found zip64 extended info in the central directory (m_zip64 will also be slammed to true too, even if we didn't find a zip64 end of central dir header, etc.) */ mz_bool m_zip64_has_extended_info_fields; /* These fields are used by the file, FILE, memory, and memory/heap read/write helpers. */ MZ_FILE *m_pFile; mz_uint64 m_file_archive_start_ofs; void *m_pMem; size_t m_mem_size; size_t m_mem_capacity; }; #define MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(array_ptr, element_size) (array_ptr)->m_element_size = element_size #if defined(DEBUG) || defined(_DEBUG) static MZ_FORCEINLINE mz_uint mz_zip_array_range_check(const mz_zip_array *pArray, mz_uint index) { MZ_ASSERT(index < pArray->m_size); return index; } #define MZ_ZIP_ARRAY_ELEMENT(array_ptr, element_type, index) ((element_type *)((array_ptr)->m_p))[mz_zip_array_range_check(array_ptr, index)] #else #define MZ_ZIP_ARRAY_ELEMENT(array_ptr, element_type, index) ((element_type *)((array_ptr)->m_p))[index] #endif static MZ_FORCEINLINE void mz_zip_array_init(mz_zip_array *pArray, mz_uint32 element_size) { memset(pArray, 0, sizeof(mz_zip_array)); pArray->m_element_size = element_size; } static MZ_FORCEINLINE void mz_zip_array_clear(mz_zip_archive *pZip, mz_zip_array *pArray) { pZip->m_pFree(pZip->m_pAlloc_opaque, pArray->m_p); memset(pArray, 0, sizeof(mz_zip_array)); } static mz_bool mz_zip_array_ensure_capacity(mz_zip_archive *pZip, mz_zip_array *pArray, size_t min_new_capacity, mz_uint growing) { void *pNew_p; size_t new_capacity = min_new_capacity; MZ_ASSERT(pArray->m_element_size); if (pArray->m_capacity >= min_new_capacity) return MZ_TRUE; if (growing) { new_capacity = MZ_MAX(1, pArray->m_capacity); while (new_capacity < min_new_capacity) new_capacity *= 2; } if (NULL == (pNew_p = pZip->m_pRealloc(pZip->m_pAlloc_opaque, pArray->m_p, pArray->m_element_size, new_capacity))) return MZ_FALSE; pArray->m_p = pNew_p; pArray->m_capacity = new_capacity; return MZ_TRUE; } static MZ_FORCEINLINE mz_bool mz_zip_array_reserve(mz_zip_archive *pZip, mz_zip_array *pArray, size_t new_capacity, mz_uint growing) { if (new_capacity > pArray->m_capacity) { if (!mz_zip_array_ensure_capacity(pZip, pArray, new_capacity, growing)) return MZ_FALSE; } return MZ_TRUE; } static MZ_FORCEINLINE mz_bool mz_zip_array_resize(mz_zip_archive *pZip, mz_zip_array *pArray, size_t new_size, mz_uint growing) { if (new_size > pArray->m_capacity) { if (!mz_zip_array_ensure_capacity(pZip, pArray, new_size, growing)) return MZ_FALSE; } pArray->m_size = new_size; return MZ_TRUE; } static MZ_FORCEINLINE mz_bool mz_zip_array_ensure_room(mz_zip_archive *pZip, mz_zip_array *pArray, size_t n) { return mz_zip_array_reserve(pZip, pArray, pArray->m_size + n, MZ_TRUE); } static MZ_FORCEINLINE mz_bool mz_zip_array_push_back(mz_zip_archive *pZip, mz_zip_array *pArray, const void *pElements, size_t n) { size_t orig_size = pArray->m_size; if (!mz_zip_array_resize(pZip, pArray, orig_size + n, MZ_TRUE)) return MZ_FALSE; if (n > 0) memcpy((mz_uint8 *)pArray->m_p + orig_size * pArray->m_element_size, pElements, n * pArray->m_element_size); return MZ_TRUE; } #ifndef MINIZ_NO_TIME static MZ_TIME_T mz_zip_dos_to_time_t(int dos_time, int dos_date) { struct tm tm; memset(&tm, 0, sizeof(tm)); tm.tm_isdst = -1; tm.tm_year = ((dos_date >> 9) & 127) + 1980 - 1900; tm.tm_mon = ((dos_date >> 5) & 15) - 1; tm.tm_mday = dos_date & 31; tm.tm_hour = (dos_time >> 11) & 31; tm.tm_min = (dos_time >> 5) & 63; tm.tm_sec = (dos_time << 1) & 62; return mktime(&tm); } #ifndef MINIZ_NO_ARCHIVE_WRITING_APIS static void mz_zip_time_t_to_dos_time(MZ_TIME_T time, mz_uint16 *pDOS_time, mz_uint16 *pDOS_date) { #ifdef _MSC_VER struct tm tm_struct; struct tm *tm = &tm_struct; errno_t err = localtime_s(tm, &time); if (err) { *pDOS_date = 0; *pDOS_time = 0; return; } #else struct tm *tm = localtime(&time); #endif /* #ifdef _MSC_VER */ *pDOS_time = (mz_uint16)(((tm->tm_hour) << 11) + ((tm->tm_min) << 5) + ((tm->tm_sec) >> 1)); *pDOS_date = (mz_uint16)(((tm->tm_year + 1900 - 1980) << 9) + ((tm->tm_mon + 1) << 5) + tm->tm_mday); } #endif /* MINIZ_NO_ARCHIVE_WRITING_APIS */ #ifndef MINIZ_NO_STDIO #ifndef MINIZ_NO_ARCHIVE_WRITING_APIS static mz_bool mz_zip_get_file_modified_time(const char *pFilename, MZ_TIME_T *pTime) { struct MZ_FILE_STAT_STRUCT file_stat; /* On Linux with x86 glibc, this call will fail on large files (I think >= 0x80000000 bytes) unless you compiled with _LARGEFILE64_SOURCE. Argh. */ if (MZ_FILE_STAT(pFilename, &file_stat) != 0) return MZ_FALSE; *pTime = file_stat.st_mtime; return MZ_TRUE; } #endif /* #ifndef MINIZ_NO_ARCHIVE_WRITING_APIS*/ static mz_bool mz_zip_set_file_times(const char *pFilename, MZ_TIME_T access_time, MZ_TIME_T modified_time) { struct utimbuf t; memset(&t, 0, sizeof(t)); t.actime = access_time; t.modtime = modified_time; return !utime(pFilename, &t); } #endif /* #ifndef MINIZ_NO_STDIO */ #endif /* #ifndef MINIZ_NO_TIME */ static MZ_FORCEINLINE mz_bool mz_zip_set_error(mz_zip_archive *pZip, mz_zip_error err_num) { if (pZip) pZip->m_last_error = err_num; return MZ_FALSE; } static mz_bool mz_zip_reader_init_internal(mz_zip_archive *pZip, mz_uint flags) { (void)flags; if ((!pZip) || (pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_INVALID)) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); if (!pZip->m_pAlloc) pZip->m_pAlloc = miniz_def_alloc_func; if (!pZip->m_pFree) pZip->m_pFree = miniz_def_free_func; if (!pZip->m_pRealloc) pZip->m_pRealloc = miniz_def_realloc_func; pZip->m_archive_size = 0; pZip->m_central_directory_file_ofs = 0; pZip->m_total_files = 0; pZip->m_last_error = MZ_ZIP_NO_ERROR; if (NULL == (pZip->m_pState = (mz_zip_internal_state *)pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, sizeof(mz_zip_internal_state)))) return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); memset(pZip->m_pState, 0, sizeof(mz_zip_internal_state)); MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_central_dir, sizeof(mz_uint8)); MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_central_dir_offsets, sizeof(mz_uint32)); MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_sorted_central_dir_offsets, sizeof(mz_uint32)); pZip->m_pState->m_init_flags = flags; pZip->m_pState->m_zip64 = MZ_FALSE; pZip->m_pState->m_zip64_has_extended_info_fields = MZ_FALSE; pZip->m_zip_mode = MZ_ZIP_MODE_READING; return MZ_TRUE; } static MZ_FORCEINLINE mz_bool mz_zip_reader_filename_less(const mz_zip_array *pCentral_dir_array, const mz_zip_array *pCentral_dir_offsets, mz_uint l_index, mz_uint r_index) { const mz_uint8 *pL = &MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_array, mz_uint8, MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_offsets, mz_uint32, l_index)), *pE; const mz_uint8 *pR = &MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_array, mz_uint8, MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_offsets, mz_uint32, r_index)); mz_uint l_len = MZ_READ_LE16(pL + MZ_ZIP_CDH_FILENAME_LEN_OFS), r_len = MZ_READ_LE16(pR + MZ_ZIP_CDH_FILENAME_LEN_OFS); mz_uint8 l = 0, r = 0; pL += MZ_ZIP_CENTRAL_DIR_HEADER_SIZE; pR += MZ_ZIP_CENTRAL_DIR_HEADER_SIZE; pE = pL + MZ_MIN(l_len, r_len); while (pL < pE) { if ((l = MZ_TOLOWER(*pL)) != (r = MZ_TOLOWER(*pR))) break; pL++; pR++; } return (pL == pE) ? (l_len < r_len) : (l < r); } #define MZ_SWAP_UINT32(a, b) \ do \ { \ mz_uint32 t = a; \ a = b; \ b = t; \ } \ MZ_MACRO_END /* Heap sort of lowercased filenames, used to help accelerate plain central directory searches by mz_zip_reader_locate_file(). (Could also use qsort(), but it could allocate memory.) */ static void mz_zip_reader_sort_central_dir_offsets_by_filename(mz_zip_archive *pZip) { mz_zip_internal_state *pState = pZip->m_pState; const mz_zip_array *pCentral_dir_offsets = &pState->m_central_dir_offsets; const mz_zip_array *pCentral_dir = &pState->m_central_dir; mz_uint32 *pIndices; mz_uint32 start, end; const mz_uint32 size = pZip->m_total_files; if (size <= 1U) return; pIndices = &MZ_ZIP_ARRAY_ELEMENT(&pState->m_sorted_central_dir_offsets, mz_uint32, 0); start = (size - 2U) >> 1U; for (;;) { mz_uint64 child, root = start; for (;;) { if ((child = (root << 1U) + 1U) >= size) break; child += (((child + 1U) < size) && (mz_zip_reader_filename_less(pCentral_dir, pCentral_dir_offsets, pIndices[child], pIndices[child + 1U]))); if (!mz_zip_reader_filename_less(pCentral_dir, pCentral_dir_offsets, pIndices[root], pIndices[child])) break; MZ_SWAP_UINT32(pIndices[root], pIndices[child]); root = child; } if (!start) break; start--; } end = size - 1; while (end > 0) { mz_uint64 child, root = 0; MZ_SWAP_UINT32(pIndices[end], pIndices[0]); for (;;) { if ((child = (root << 1U) + 1U) >= end) break; child += (((child + 1U) < end) && mz_zip_reader_filename_less(pCentral_dir, pCentral_dir_offsets, pIndices[child], pIndices[child + 1U])); if (!mz_zip_reader_filename_less(pCentral_dir, pCentral_dir_offsets, pIndices[root], pIndices[child])) break; MZ_SWAP_UINT32(pIndices[root], pIndices[child]); root = child; } end--; } } static mz_bool mz_zip_reader_locate_header_sig(mz_zip_archive *pZip, mz_uint32 record_sig, mz_uint32 record_size, mz_int64 *pOfs) { mz_int64 cur_file_ofs; mz_uint32 buf_u32[4096 / sizeof(mz_uint32)]; mz_uint8 *pBuf = (mz_uint8 *)buf_u32; /* Basic sanity checks - reject files which are too small */ if (pZip->m_archive_size < record_size) return MZ_FALSE; /* Find the record by scanning the file from the end towards the beginning. */ cur_file_ofs = MZ_MAX((mz_int64)pZip->m_archive_size - (mz_int64)sizeof(buf_u32), 0); for (;;) { int i, n = (int)MZ_MIN(sizeof(buf_u32), pZip->m_archive_size - cur_file_ofs); if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pBuf, n) != (mz_uint)n) return MZ_FALSE; for (i = n - 4; i >= 0; --i) { mz_uint s = MZ_READ_LE32(pBuf + i); if (s == record_sig) { if ((pZip->m_archive_size - (cur_file_ofs + i)) >= record_size) break; } } if (i >= 0) { cur_file_ofs += i; break; } /* Give up if we've searched the entire file, or we've gone back "too far" (~64kb) */ if ((!cur_file_ofs) || ((pZip->m_archive_size - cur_file_ofs) >= (MZ_UINT16_MAX + record_size))) return MZ_FALSE; cur_file_ofs = MZ_MAX(cur_file_ofs - (sizeof(buf_u32) - 3), 0); } *pOfs = cur_file_ofs; return MZ_TRUE; } static mz_bool mz_zip_reader_read_central_dir(mz_zip_archive *pZip, mz_uint flags) { mz_uint cdir_size = 0, cdir_entries_on_this_disk = 0, num_this_disk = 0, cdir_disk_index = 0; mz_uint64 cdir_ofs = 0; mz_int64 cur_file_ofs = 0; const mz_uint8 *p; mz_uint32 buf_u32[4096 / sizeof(mz_uint32)]; mz_uint8 *pBuf = (mz_uint8 *)buf_u32; mz_bool sort_central_dir = ((flags & MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY) == 0); mz_uint32 zip64_end_of_central_dir_locator_u32[(MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE + sizeof(mz_uint32) - 1) / sizeof(mz_uint32)]; mz_uint8 *pZip64_locator = (mz_uint8 *)zip64_end_of_central_dir_locator_u32; mz_uint32 zip64_end_of_central_dir_header_u32[(MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE + sizeof(mz_uint32) - 1) / sizeof(mz_uint32)]; mz_uint8 *pZip64_end_of_central_dir = (mz_uint8 *)zip64_end_of_central_dir_header_u32; mz_uint64 zip64_end_of_central_dir_ofs = 0; /* Basic sanity checks - reject files which are too small, and check the first 4 bytes of the file to make sure a local header is there. */ if (pZip->m_archive_size < MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) return mz_zip_set_error(pZip, MZ_ZIP_NOT_AN_ARCHIVE); if (!mz_zip_reader_locate_header_sig(pZip, MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG, MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE, &cur_file_ofs)) return mz_zip_set_error(pZip, MZ_ZIP_FAILED_FINDING_CENTRAL_DIR); /* Read and verify the end of central directory record. */ if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pBuf, MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) != MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); if (MZ_READ_LE32(pBuf + MZ_ZIP_ECDH_SIG_OFS) != MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG) return mz_zip_set_error(pZip, MZ_ZIP_NOT_AN_ARCHIVE); if (cur_file_ofs >= (MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE + MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE)) { if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs - MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE, pZip64_locator, MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE) == MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE) { if (MZ_READ_LE32(pZip64_locator + MZ_ZIP64_ECDL_SIG_OFS) == MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIG) { zip64_end_of_central_dir_ofs = MZ_READ_LE64(pZip64_locator + MZ_ZIP64_ECDL_REL_OFS_TO_ZIP64_ECDR_OFS); if (zip64_end_of_central_dir_ofs > (pZip->m_archive_size - MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE)) return mz_zip_set_error(pZip, MZ_ZIP_NOT_AN_ARCHIVE); if (pZip->m_pRead(pZip->m_pIO_opaque, zip64_end_of_central_dir_ofs, pZip64_end_of_central_dir, MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE) == MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE) { if (MZ_READ_LE32(pZip64_end_of_central_dir + MZ_ZIP64_ECDH_SIG_OFS) == MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIG) { pZip->m_pState->m_zip64 = MZ_TRUE; } } } } } pZip->m_total_files = MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_CDIR_TOTAL_ENTRIES_OFS); cdir_entries_on_this_disk = MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS); num_this_disk = MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_NUM_THIS_DISK_OFS); cdir_disk_index = MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_NUM_DISK_CDIR_OFS); cdir_size = MZ_READ_LE32(pBuf + MZ_ZIP_ECDH_CDIR_SIZE_OFS); cdir_ofs = MZ_READ_LE32(pBuf + MZ_ZIP_ECDH_CDIR_OFS_OFS); if (pZip->m_pState->m_zip64) { mz_uint32 zip64_total_num_of_disks = MZ_READ_LE32(pZip64_locator + MZ_ZIP64_ECDL_TOTAL_NUMBER_OF_DISKS_OFS); mz_uint64 zip64_cdir_total_entries = MZ_READ_LE64(pZip64_end_of_central_dir + MZ_ZIP64_ECDH_CDIR_TOTAL_ENTRIES_OFS); mz_uint64 zip64_cdir_total_entries_on_this_disk = MZ_READ_LE64(pZip64_end_of_central_dir + MZ_ZIP64_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS); mz_uint64 zip64_size_of_end_of_central_dir_record = MZ_READ_LE64(pZip64_end_of_central_dir + MZ_ZIP64_ECDH_SIZE_OF_RECORD_OFS); mz_uint64 zip64_size_of_central_directory = MZ_READ_LE64(pZip64_end_of_central_dir + MZ_ZIP64_ECDH_CDIR_SIZE_OFS); if (zip64_size_of_end_of_central_dir_record < (MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE - 12)) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); if (zip64_total_num_of_disks != 1U) return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_MULTIDISK); /* Check for miniz's practical limits */ if (zip64_cdir_total_entries > MZ_UINT32_MAX) return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); pZip->m_total_files = (mz_uint32)zip64_cdir_total_entries; if (zip64_cdir_total_entries_on_this_disk > MZ_UINT32_MAX) return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); cdir_entries_on_this_disk = (mz_uint32)zip64_cdir_total_entries_on_this_disk; /* Check for miniz's current practical limits (sorry, this should be enough for millions of files) */ if (zip64_size_of_central_directory > MZ_UINT32_MAX) return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_CDIR_SIZE); cdir_size = (mz_uint32)zip64_size_of_central_directory; num_this_disk = MZ_READ_LE32(pZip64_end_of_central_dir + MZ_ZIP64_ECDH_NUM_THIS_DISK_OFS); cdir_disk_index = MZ_READ_LE32(pZip64_end_of_central_dir + MZ_ZIP64_ECDH_NUM_DISK_CDIR_OFS); cdir_ofs = MZ_READ_LE64(pZip64_end_of_central_dir + MZ_ZIP64_ECDH_CDIR_OFS_OFS); } if (pZip->m_total_files != cdir_entries_on_this_disk) return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_MULTIDISK); if (((num_this_disk | cdir_disk_index) != 0) && ((num_this_disk != 1) || (cdir_disk_index != 1))) return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_MULTIDISK); if (cdir_size < (mz_uint64)pZip->m_total_files * MZ_ZIP_CENTRAL_DIR_HEADER_SIZE) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); if ((cdir_ofs + (mz_uint64)cdir_size) > pZip->m_archive_size) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); pZip->m_central_directory_file_ofs = cdir_ofs; if (pZip->m_total_files) { mz_uint i, n; /* Read the entire central directory into a heap block, and allocate another heap block to hold the unsorted central dir file record offsets, and possibly another to hold the sorted indices. */ if ((!mz_zip_array_resize(pZip, &pZip->m_pState->m_central_dir, cdir_size, MZ_FALSE)) || (!mz_zip_array_resize(pZip, &pZip->m_pState->m_central_dir_offsets, pZip->m_total_files, MZ_FALSE))) return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); if (sort_central_dir) { if (!mz_zip_array_resize(pZip, &pZip->m_pState->m_sorted_central_dir_offsets, pZip->m_total_files, MZ_FALSE)) return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); } if (pZip->m_pRead(pZip->m_pIO_opaque, cdir_ofs, pZip->m_pState->m_central_dir.m_p, cdir_size) != cdir_size) return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); /* Now create an index into the central directory file records, do some basic sanity checking on each record */ p = (const mz_uint8 *)pZip->m_pState->m_central_dir.m_p; for (n = cdir_size, i = 0; i < pZip->m_total_files; ++i) { mz_uint total_header_size, disk_index, bit_flags, filename_size, ext_data_size; mz_uint64 comp_size, decomp_size, local_header_ofs; if ((n < MZ_ZIP_CENTRAL_DIR_HEADER_SIZE) || (MZ_READ_LE32(p) != MZ_ZIP_CENTRAL_DIR_HEADER_SIG)) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir_offsets, mz_uint32, i) = (mz_uint32)(p - (const mz_uint8 *)pZip->m_pState->m_central_dir.m_p); if (sort_central_dir) MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_sorted_central_dir_offsets, mz_uint32, i) = i; comp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS); decomp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS); local_header_ofs = MZ_READ_LE32(p + MZ_ZIP_CDH_LOCAL_HEADER_OFS); filename_size = MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS); ext_data_size = MZ_READ_LE16(p + MZ_ZIP_CDH_EXTRA_LEN_OFS); if ((!pZip->m_pState->m_zip64_has_extended_info_fields) && (ext_data_size) && (MZ_MAX(MZ_MAX(comp_size, decomp_size), local_header_ofs) == MZ_UINT32_MAX)) { /* Attempt to find zip64 extended information field in the entry's extra data */ mz_uint32 extra_size_remaining = ext_data_size; if (extra_size_remaining) { const mz_uint8 *pExtra_data; void* buf = NULL; if (MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + filename_size + ext_data_size > n) { buf = MZ_MALLOC(ext_data_size); if(buf==NULL) return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); if (pZip->m_pRead(pZip->m_pIO_opaque, cdir_ofs + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + filename_size, buf, ext_data_size) != ext_data_size) { MZ_FREE(buf); return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); } pExtra_data = (mz_uint8*)buf; } else { pExtra_data = p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + filename_size; } do { mz_uint32 field_id; mz_uint32 field_data_size; if (extra_size_remaining < (sizeof(mz_uint16) * 2)) { MZ_FREE(buf); return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); } field_id = MZ_READ_LE16(pExtra_data); field_data_size = MZ_READ_LE16(pExtra_data + sizeof(mz_uint16)); if ((field_data_size + sizeof(mz_uint16) * 2) > extra_size_remaining) { MZ_FREE(buf); return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); } if (field_id == MZ_ZIP64_EXTENDED_INFORMATION_FIELD_HEADER_ID) { /* Ok, the archive didn't have any zip64 headers but it uses a zip64 extended information field so mark it as zip64 anyway (this can occur with infozip's zip util when it reads compresses files from stdin). */ pZip->m_pState->m_zip64 = MZ_TRUE; pZip->m_pState->m_zip64_has_extended_info_fields = MZ_TRUE; break; } pExtra_data += sizeof(mz_uint16) * 2 + field_data_size; extra_size_remaining = extra_size_remaining - sizeof(mz_uint16) * 2 - field_data_size; } while (extra_size_remaining); MZ_FREE(buf); } } /* I've seen archives that aren't marked as zip64 that uses zip64 ext data, argh */ if ((comp_size != MZ_UINT32_MAX) && (decomp_size != MZ_UINT32_MAX)) { if (((!MZ_READ_LE32(p + MZ_ZIP_CDH_METHOD_OFS)) && (decomp_size != comp_size)) || (decomp_size && !comp_size)) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); } disk_index = MZ_READ_LE16(p + MZ_ZIP_CDH_DISK_START_OFS); if ((disk_index == MZ_UINT16_MAX) || ((disk_index != num_this_disk) && (disk_index != 1))) return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_MULTIDISK); if (comp_size != MZ_UINT32_MAX) { if (((mz_uint64)MZ_READ_LE32(p + MZ_ZIP_CDH_LOCAL_HEADER_OFS) + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + comp_size) > pZip->m_archive_size) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); } bit_flags = MZ_READ_LE16(p + MZ_ZIP_CDH_BIT_FLAG_OFS); if (bit_flags & MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_LOCAL_DIR_IS_MASKED) return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_ENCRYPTION); if ((total_header_size = MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS) + MZ_READ_LE16(p + MZ_ZIP_CDH_EXTRA_LEN_OFS) + MZ_READ_LE16(p + MZ_ZIP_CDH_COMMENT_LEN_OFS)) > n) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); n -= total_header_size; p += total_header_size; } } if (sort_central_dir) mz_zip_reader_sort_central_dir_offsets_by_filename(pZip); return MZ_TRUE; } void mz_zip_zero_struct(mz_zip_archive *pZip) { if (pZip) MZ_CLEAR_PTR(pZip); } static mz_bool mz_zip_reader_end_internal(mz_zip_archive *pZip, mz_bool set_last_error) { mz_bool status = MZ_TRUE; if (!pZip) return MZ_FALSE; if ((!pZip->m_pState) || (!pZip->m_pAlloc) || (!pZip->m_pFree) || (pZip->m_zip_mode != MZ_ZIP_MODE_READING)) { if (set_last_error) pZip->m_last_error = MZ_ZIP_INVALID_PARAMETER; return MZ_FALSE; } if (pZip->m_pState) { mz_zip_internal_state *pState = pZip->m_pState; pZip->m_pState = NULL; mz_zip_array_clear(pZip, &pState->m_central_dir); mz_zip_array_clear(pZip, &pState->m_central_dir_offsets); mz_zip_array_clear(pZip, &pState->m_sorted_central_dir_offsets); #ifndef MINIZ_NO_STDIO if (pState->m_pFile) { if (pZip->m_zip_type == MZ_ZIP_TYPE_FILE) { if (MZ_FCLOSE(pState->m_pFile) == EOF) { if (set_last_error) pZip->m_last_error = MZ_ZIP_FILE_CLOSE_FAILED; status = MZ_FALSE; } } pState->m_pFile = NULL; } #endif /* #ifndef MINIZ_NO_STDIO */ pZip->m_pFree(pZip->m_pAlloc_opaque, pState); } pZip->m_zip_mode = MZ_ZIP_MODE_INVALID; return status; } mz_bool mz_zip_reader_end(mz_zip_archive *pZip) { return mz_zip_reader_end_internal(pZip, MZ_TRUE); } mz_bool mz_zip_reader_init(mz_zip_archive *pZip, mz_uint64 size, mz_uint flags) { if ((!pZip) || (!pZip->m_pRead)) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); if (!mz_zip_reader_init_internal(pZip, flags)) return MZ_FALSE; pZip->m_zip_type = MZ_ZIP_TYPE_USER; pZip->m_archive_size = size; if (!mz_zip_reader_read_central_dir(pZip, flags)) { mz_zip_reader_end_internal(pZip, MZ_FALSE); return MZ_FALSE; } return MZ_TRUE; } static size_t mz_zip_mem_read_func(void *pOpaque, mz_uint64 file_ofs, void *pBuf, size_t n) { mz_zip_archive *pZip = (mz_zip_archive *)pOpaque; size_t s = (file_ofs >= pZip->m_archive_size) ? 0 : (size_t)MZ_MIN(pZip->m_archive_size - file_ofs, n); memcpy(pBuf, (const mz_uint8 *)pZip->m_pState->m_pMem + file_ofs, s); return s; } mz_bool mz_zip_reader_init_mem(mz_zip_archive *pZip, const void *pMem, size_t size, mz_uint flags) { if (!pMem) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); if (size < MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) return mz_zip_set_error(pZip, MZ_ZIP_NOT_AN_ARCHIVE); if (!mz_zip_reader_init_internal(pZip, flags)) return MZ_FALSE; pZip->m_zip_type = MZ_ZIP_TYPE_MEMORY; pZip->m_archive_size = size; pZip->m_pRead = mz_zip_mem_read_func; pZip->m_pIO_opaque = pZip; pZip->m_pNeeds_keepalive = NULL; #ifdef __cplusplus pZip->m_pState->m_pMem = const_cast(pMem); #else pZip->m_pState->m_pMem = (void *)pMem; #endif pZip->m_pState->m_mem_size = size; if (!mz_zip_reader_read_central_dir(pZip, flags)) { mz_zip_reader_end_internal(pZip, MZ_FALSE); return MZ_FALSE; } return MZ_TRUE; } #ifndef MINIZ_NO_STDIO static size_t mz_zip_file_read_func(void *pOpaque, mz_uint64 file_ofs, void *pBuf, size_t n) { mz_zip_archive *pZip = (mz_zip_archive *)pOpaque; mz_int64 cur_ofs = MZ_FTELL64(pZip->m_pState->m_pFile); file_ofs += pZip->m_pState->m_file_archive_start_ofs; if (((mz_int64)file_ofs < 0) || (((cur_ofs != (mz_int64)file_ofs)) && (MZ_FSEEK64(pZip->m_pState->m_pFile, (mz_int64)file_ofs, SEEK_SET)))) return 0; return MZ_FREAD(pBuf, 1, n, pZip->m_pState->m_pFile); } mz_bool mz_zip_reader_init_file(mz_zip_archive *pZip, const char *pFilename, mz_uint32 flags) { return mz_zip_reader_init_file_v2(pZip, pFilename, flags, 0, 0); } mz_bool mz_zip_reader_init_file_v2(mz_zip_archive *pZip, const char *pFilename, mz_uint flags, mz_uint64 file_start_ofs, mz_uint64 archive_size) { mz_uint64 file_size; MZ_FILE *pFile; if ((!pZip) || (!pFilename) || ((archive_size) && (archive_size < MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE))) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); pFile = MZ_FOPEN(pFilename, "rb"); if (!pFile) return mz_zip_set_error(pZip, MZ_ZIP_FILE_OPEN_FAILED); file_size = archive_size; if (!file_size) { if (MZ_FSEEK64(pFile, 0, SEEK_END)) { MZ_FCLOSE(pFile); return mz_zip_set_error(pZip, MZ_ZIP_FILE_SEEK_FAILED); } file_size = MZ_FTELL64(pFile); } /* TODO: Better sanity check archive_size and the # of actual remaining bytes */ if (file_size < MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) { MZ_FCLOSE(pFile); return mz_zip_set_error(pZip, MZ_ZIP_NOT_AN_ARCHIVE); } if (!mz_zip_reader_init_internal(pZip, flags)) { MZ_FCLOSE(pFile); return MZ_FALSE; } pZip->m_zip_type = MZ_ZIP_TYPE_FILE; pZip->m_pRead = mz_zip_file_read_func; pZip->m_pIO_opaque = pZip; pZip->m_pState->m_pFile = pFile; pZip->m_archive_size = file_size; pZip->m_pState->m_file_archive_start_ofs = file_start_ofs; if (!mz_zip_reader_read_central_dir(pZip, flags)) { mz_zip_reader_end_internal(pZip, MZ_FALSE); return MZ_FALSE; } return MZ_TRUE; } mz_bool mz_zip_reader_init_cfile(mz_zip_archive *pZip, MZ_FILE *pFile, mz_uint64 archive_size, mz_uint flags) { mz_uint64 cur_file_ofs; if ((!pZip) || (!pFile)) return mz_zip_set_error(pZip, MZ_ZIP_FILE_OPEN_FAILED); cur_file_ofs = MZ_FTELL64(pFile); if (!archive_size) { if (MZ_FSEEK64(pFile, 0, SEEK_END)) return mz_zip_set_error(pZip, MZ_ZIP_FILE_SEEK_FAILED); archive_size = MZ_FTELL64(pFile) - cur_file_ofs; if (archive_size < MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) return mz_zip_set_error(pZip, MZ_ZIP_NOT_AN_ARCHIVE); } if (!mz_zip_reader_init_internal(pZip, flags)) return MZ_FALSE; pZip->m_zip_type = MZ_ZIP_TYPE_CFILE; pZip->m_pRead = mz_zip_file_read_func; pZip->m_pIO_opaque = pZip; pZip->m_pState->m_pFile = pFile; pZip->m_archive_size = archive_size; pZip->m_pState->m_file_archive_start_ofs = cur_file_ofs; if (!mz_zip_reader_read_central_dir(pZip, flags)) { mz_zip_reader_end_internal(pZip, MZ_FALSE); return MZ_FALSE; } return MZ_TRUE; } #endif /* #ifndef MINIZ_NO_STDIO */ static MZ_FORCEINLINE const mz_uint8 *mz_zip_get_cdh(mz_zip_archive *pZip, mz_uint file_index) { if ((!pZip) || (!pZip->m_pState) || (file_index >= pZip->m_total_files)) return NULL; return &MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir, mz_uint8, MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir_offsets, mz_uint32, file_index)); } mz_bool mz_zip_reader_is_file_encrypted(mz_zip_archive *pZip, mz_uint file_index) { mz_uint m_bit_flag; const mz_uint8 *p = mz_zip_get_cdh(pZip, file_index); if (!p) { mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); return MZ_FALSE; } m_bit_flag = MZ_READ_LE16(p + MZ_ZIP_CDH_BIT_FLAG_OFS); return (m_bit_flag & (MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_IS_ENCRYPTED | MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_USES_STRONG_ENCRYPTION)) != 0; } mz_bool mz_zip_reader_is_file_supported(mz_zip_archive *pZip, mz_uint file_index) { mz_uint bit_flag; mz_uint method; const mz_uint8 *p = mz_zip_get_cdh(pZip, file_index); if (!p) { mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); return MZ_FALSE; } method = MZ_READ_LE16(p + MZ_ZIP_CDH_METHOD_OFS); bit_flag = MZ_READ_LE16(p + MZ_ZIP_CDH_BIT_FLAG_OFS); if ((method != 0) && (method != MZ_DEFLATED)) { mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_METHOD); return MZ_FALSE; } if (bit_flag & (MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_IS_ENCRYPTED | MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_USES_STRONG_ENCRYPTION)) { mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_ENCRYPTION); return MZ_FALSE; } if (bit_flag & MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_COMPRESSED_PATCH_FLAG) { mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_FEATURE); return MZ_FALSE; } return MZ_TRUE; } mz_bool mz_zip_reader_is_file_a_directory(mz_zip_archive *pZip, mz_uint file_index) { mz_uint filename_len, attribute_mapping_id, external_attr; const mz_uint8 *p = mz_zip_get_cdh(pZip, file_index); if (!p) { mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); return MZ_FALSE; } filename_len = MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS); if (filename_len) { if (*(p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + filename_len - 1) == '/') return MZ_TRUE; } /* Bugfix: This code was also checking if the internal attribute was non-zero, which wasn't correct. */ /* Most/all zip writers (hopefully) set DOS file/directory attributes in the low 16-bits, so check for the DOS directory flag and ignore the source OS ID in the created by field. */ /* FIXME: Remove this check? Is it necessary - we already check the filename. */ attribute_mapping_id = MZ_READ_LE16(p + MZ_ZIP_CDH_VERSION_MADE_BY_OFS) >> 8; (void)attribute_mapping_id; external_attr = MZ_READ_LE32(p + MZ_ZIP_CDH_EXTERNAL_ATTR_OFS); if ((external_attr & MZ_ZIP_DOS_DIR_ATTRIBUTE_BITFLAG) != 0) { return MZ_TRUE; } return MZ_FALSE; } static mz_bool mz_zip_file_stat_internal(mz_zip_archive *pZip, mz_uint file_index, const mz_uint8 *pCentral_dir_header, mz_zip_archive_file_stat *pStat, mz_bool *pFound_zip64_extra_data) { mz_uint n; const mz_uint8 *p = pCentral_dir_header; if (pFound_zip64_extra_data) *pFound_zip64_extra_data = MZ_FALSE; if ((!p) || (!pStat)) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); /* Extract fields from the central directory record. */ pStat->m_file_index = file_index; pStat->m_central_dir_ofs = MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir_offsets, mz_uint32, file_index); pStat->m_version_made_by = MZ_READ_LE16(p + MZ_ZIP_CDH_VERSION_MADE_BY_OFS); pStat->m_version_needed = MZ_READ_LE16(p + MZ_ZIP_CDH_VERSION_NEEDED_OFS); pStat->m_bit_flag = MZ_READ_LE16(p + MZ_ZIP_CDH_BIT_FLAG_OFS); pStat->m_method = MZ_READ_LE16(p + MZ_ZIP_CDH_METHOD_OFS); #ifndef MINIZ_NO_TIME pStat->m_time = mz_zip_dos_to_time_t(MZ_READ_LE16(p + MZ_ZIP_CDH_FILE_TIME_OFS), MZ_READ_LE16(p + MZ_ZIP_CDH_FILE_DATE_OFS)); #endif pStat->m_crc32 = MZ_READ_LE32(p + MZ_ZIP_CDH_CRC32_OFS); pStat->m_comp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS); pStat->m_uncomp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS); pStat->m_internal_attr = MZ_READ_LE16(p + MZ_ZIP_CDH_INTERNAL_ATTR_OFS); pStat->m_external_attr = MZ_READ_LE32(p + MZ_ZIP_CDH_EXTERNAL_ATTR_OFS); pStat->m_local_header_ofs = MZ_READ_LE32(p + MZ_ZIP_CDH_LOCAL_HEADER_OFS); /* Copy as much of the filename and comment as possible. */ n = MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS); n = MZ_MIN(n, MZ_ZIP_MAX_ARCHIVE_FILENAME_SIZE - 1); memcpy(pStat->m_filename, p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE, n); pStat->m_filename[n] = '\0'; n = MZ_READ_LE16(p + MZ_ZIP_CDH_COMMENT_LEN_OFS); n = MZ_MIN(n, MZ_ZIP_MAX_ARCHIVE_FILE_COMMENT_SIZE - 1); pStat->m_comment_size = n; memcpy(pStat->m_comment, p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS) + MZ_READ_LE16(p + MZ_ZIP_CDH_EXTRA_LEN_OFS), n); pStat->m_comment[n] = '\0'; /* Set some flags for convienance */ pStat->m_is_directory = mz_zip_reader_is_file_a_directory(pZip, file_index); pStat->m_is_encrypted = mz_zip_reader_is_file_encrypted(pZip, file_index); pStat->m_is_supported = mz_zip_reader_is_file_supported(pZip, file_index); /* See if we need to read any zip64 extended information fields. */ /* Confusingly, these zip64 fields can be present even on non-zip64 archives (Debian zip on a huge files from stdin piped to stdout creates them). */ if (MZ_MAX(MZ_MAX(pStat->m_comp_size, pStat->m_uncomp_size), pStat->m_local_header_ofs) == MZ_UINT32_MAX) { /* Attempt to find zip64 extended information field in the entry's extra data */ mz_uint32 extra_size_remaining = MZ_READ_LE16(p + MZ_ZIP_CDH_EXTRA_LEN_OFS); if (extra_size_remaining) { const mz_uint8 *pExtra_data = p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS); do { mz_uint32 field_id; mz_uint32 field_data_size; if (extra_size_remaining < (sizeof(mz_uint16) * 2)) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); field_id = MZ_READ_LE16(pExtra_data); field_data_size = MZ_READ_LE16(pExtra_data + sizeof(mz_uint16)); if ((field_data_size + sizeof(mz_uint16) * 2) > extra_size_remaining) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); if (field_id == MZ_ZIP64_EXTENDED_INFORMATION_FIELD_HEADER_ID) { const mz_uint8 *pField_data = pExtra_data + sizeof(mz_uint16) * 2; mz_uint32 field_data_remaining = field_data_size; if (pFound_zip64_extra_data) *pFound_zip64_extra_data = MZ_TRUE; if (pStat->m_uncomp_size == MZ_UINT32_MAX) { if (field_data_remaining < sizeof(mz_uint64)) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); pStat->m_uncomp_size = MZ_READ_LE64(pField_data); pField_data += sizeof(mz_uint64); field_data_remaining -= sizeof(mz_uint64); } if (pStat->m_comp_size == MZ_UINT32_MAX) { if (field_data_remaining < sizeof(mz_uint64)) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); pStat->m_comp_size = MZ_READ_LE64(pField_data); pField_data += sizeof(mz_uint64); field_data_remaining -= sizeof(mz_uint64); } if (pStat->m_local_header_ofs == MZ_UINT32_MAX) { if (field_data_remaining < sizeof(mz_uint64)) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); pStat->m_local_header_ofs = MZ_READ_LE64(pField_data); pField_data += sizeof(mz_uint64); field_data_remaining -= sizeof(mz_uint64); } break; } pExtra_data += sizeof(mz_uint16) * 2 + field_data_size; extra_size_remaining = extra_size_remaining - sizeof(mz_uint16) * 2 - field_data_size; } while (extra_size_remaining); } } return MZ_TRUE; } static MZ_FORCEINLINE mz_bool mz_zip_string_equal(const char *pA, const char *pB, mz_uint len, mz_uint flags) { mz_uint i; if (flags & MZ_ZIP_FLAG_CASE_SENSITIVE) return 0 == memcmp(pA, pB, len); for (i = 0; i < len; ++i) if (MZ_TOLOWER(pA[i]) != MZ_TOLOWER(pB[i])) return MZ_FALSE; return MZ_TRUE; } static MZ_FORCEINLINE int mz_zip_filename_compare(const mz_zip_array *pCentral_dir_array, const mz_zip_array *pCentral_dir_offsets, mz_uint l_index, const char *pR, mz_uint r_len) { const mz_uint8 *pL = &MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_array, mz_uint8, MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_offsets, mz_uint32, l_index)), *pE; mz_uint l_len = MZ_READ_LE16(pL + MZ_ZIP_CDH_FILENAME_LEN_OFS); mz_uint8 l = 0, r = 0; pL += MZ_ZIP_CENTRAL_DIR_HEADER_SIZE; pE = pL + MZ_MIN(l_len, r_len); while (pL < pE) { if ((l = MZ_TOLOWER(*pL)) != (r = MZ_TOLOWER(*pR))) break; pL++; pR++; } return (pL == pE) ? (int)(l_len - r_len) : (l - r); } static mz_bool mz_zip_locate_file_binary_search(mz_zip_archive *pZip, const char *pFilename, mz_uint32 *pIndex) { mz_zip_internal_state *pState = pZip->m_pState; const mz_zip_array *pCentral_dir_offsets = &pState->m_central_dir_offsets; const mz_zip_array *pCentral_dir = &pState->m_central_dir; mz_uint32 *pIndices = &MZ_ZIP_ARRAY_ELEMENT(&pState->m_sorted_central_dir_offsets, mz_uint32, 0); const mz_uint32 size = pZip->m_total_files; const mz_uint filename_len = (mz_uint)strlen(pFilename); if (pIndex) *pIndex = 0; if (size) { /* yes I could use uint32_t's, but then we would have to add some special case checks in the loop, argh, and */ /* honestly the major expense here on 32-bit CPU's will still be the filename compare */ mz_int64 l = 0, h = (mz_int64)size - 1; while (l <= h) { mz_int64 m = l + ((h - l) >> 1); mz_uint32 file_index = pIndices[(mz_uint32)m]; int comp = mz_zip_filename_compare(pCentral_dir, pCentral_dir_offsets, file_index, pFilename, filename_len); if (!comp) { if (pIndex) *pIndex = file_index; return MZ_TRUE; } else if (comp < 0) l = m + 1; else h = m - 1; } } return mz_zip_set_error(pZip, MZ_ZIP_FILE_NOT_FOUND); } int mz_zip_reader_locate_file(mz_zip_archive *pZip, const char *pName, const char *pComment, mz_uint flags) { mz_uint32 index; if (!mz_zip_reader_locate_file_v2(pZip, pName, pComment, flags, &index)) return -1; else return (int)index; } mz_bool mz_zip_reader_locate_file_v2(mz_zip_archive *pZip, const char *pName, const char *pComment, mz_uint flags, mz_uint32 *pIndex) { mz_uint file_index; size_t name_len, comment_len; if (pIndex) *pIndex = 0; if ((!pZip) || (!pZip->m_pState) || (!pName)) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); /* See if we can use a binary search */ if (((pZip->m_pState->m_init_flags & MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY) == 0) && (pZip->m_zip_mode == MZ_ZIP_MODE_READING) && ((flags & (MZ_ZIP_FLAG_IGNORE_PATH | MZ_ZIP_FLAG_CASE_SENSITIVE)) == 0) && (!pComment) && (pZip->m_pState->m_sorted_central_dir_offsets.m_size)) { return mz_zip_locate_file_binary_search(pZip, pName, pIndex); } /* Locate the entry by scanning the entire central directory */ name_len = strlen(pName); if (name_len > MZ_UINT16_MAX) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); comment_len = pComment ? strlen(pComment) : 0; if (comment_len > MZ_UINT16_MAX) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); for (file_index = 0; file_index < pZip->m_total_files; file_index++) { const mz_uint8 *pHeader = &MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir, mz_uint8, MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir_offsets, mz_uint32, file_index)); mz_uint filename_len = MZ_READ_LE16(pHeader + MZ_ZIP_CDH_FILENAME_LEN_OFS); const char *pFilename = (const char *)pHeader + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE; if (filename_len < name_len) continue; if (comment_len) { mz_uint file_extra_len = MZ_READ_LE16(pHeader + MZ_ZIP_CDH_EXTRA_LEN_OFS), file_comment_len = MZ_READ_LE16(pHeader + MZ_ZIP_CDH_COMMENT_LEN_OFS); const char *pFile_comment = pFilename + filename_len + file_extra_len; if ((file_comment_len != comment_len) || (!mz_zip_string_equal(pComment, pFile_comment, file_comment_len, flags))) continue; } if ((flags & MZ_ZIP_FLAG_IGNORE_PATH) && (filename_len)) { int ofs = filename_len - 1; do { if ((pFilename[ofs] == '/') || (pFilename[ofs] == '\\') || (pFilename[ofs] == ':')) break; } while (--ofs >= 0); ofs++; pFilename += ofs; filename_len -= ofs; } if ((filename_len == name_len) && (mz_zip_string_equal(pName, pFilename, filename_len, flags))) { if (pIndex) *pIndex = file_index; return MZ_TRUE; } } return mz_zip_set_error(pZip, MZ_ZIP_FILE_NOT_FOUND); } static mz_bool mz_zip_reader_extract_to_mem_no_alloc1(mz_zip_archive *pZip, mz_uint file_index, void *pBuf, size_t buf_size, mz_uint flags, void *pUser_read_buf, size_t user_read_buf_size, const mz_zip_archive_file_stat *st) { int status = TINFL_STATUS_DONE; mz_uint64 needed_size, cur_file_ofs, comp_remaining, out_buf_ofs = 0, read_buf_size, read_buf_ofs = 0, read_buf_avail; mz_zip_archive_file_stat file_stat; void *pRead_buf; mz_uint32 local_header_u32[(MZ_ZIP_LOCAL_DIR_HEADER_SIZE + sizeof(mz_uint32) - 1) / sizeof(mz_uint32)]; mz_uint8 *pLocal_header = (mz_uint8 *)local_header_u32; tinfl_decompressor inflator; if ((!pZip) || (!pZip->m_pState) || ((buf_size) && (!pBuf)) || ((user_read_buf_size) && (!pUser_read_buf)) || (!pZip->m_pRead)) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); if (st) { file_stat = *st; } else if (!mz_zip_reader_file_stat(pZip, file_index, &file_stat)) return MZ_FALSE; /* A directory or zero length file */ if ((file_stat.m_is_directory) || (!file_stat.m_comp_size)) return MZ_TRUE; /* Encryption and patch files are not supported. */ if (file_stat.m_bit_flag & (MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_IS_ENCRYPTED | MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_USES_STRONG_ENCRYPTION | MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_COMPRESSED_PATCH_FLAG)) return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_ENCRYPTION); /* This function only supports decompressing stored and deflate. */ if ((!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) && (file_stat.m_method != 0) && (file_stat.m_method != MZ_DEFLATED)) return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_METHOD); /* Ensure supplied output buffer is large enough. */ needed_size = (flags & MZ_ZIP_FLAG_COMPRESSED_DATA) ? file_stat.m_comp_size : file_stat.m_uncomp_size; if (buf_size < needed_size) return mz_zip_set_error(pZip, MZ_ZIP_BUF_TOO_SMALL); /* Read and parse the local directory entry. */ cur_file_ofs = file_stat.m_local_header_ofs; if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pLocal_header, MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != MZ_ZIP_LOCAL_DIR_HEADER_SIZE) return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); if (MZ_READ_LE32(pLocal_header) != MZ_ZIP_LOCAL_DIR_HEADER_SIG) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); cur_file_ofs += MZ_ZIP_LOCAL_DIR_HEADER_SIZE + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_FILENAME_LEN_OFS) + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_EXTRA_LEN_OFS); if ((cur_file_ofs + file_stat.m_comp_size) > pZip->m_archive_size) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); if ((flags & MZ_ZIP_FLAG_COMPRESSED_DATA) || (!file_stat.m_method)) { /* The file is stored or the caller has requested the compressed data. */ if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pBuf, (size_t)needed_size) != needed_size) return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); #ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS if ((flags & MZ_ZIP_FLAG_COMPRESSED_DATA) == 0) { if (mz_crc32(MZ_CRC32_INIT, (const mz_uint8 *)pBuf, (size_t)file_stat.m_uncomp_size) != file_stat.m_crc32) return mz_zip_set_error(pZip, MZ_ZIP_CRC_CHECK_FAILED); } #endif return MZ_TRUE; } /* Decompress the file either directly from memory or from a file input buffer. */ tinfl_init(&inflator); if (pZip->m_pState->m_pMem) { /* Read directly from the archive in memory. */ pRead_buf = (mz_uint8 *)pZip->m_pState->m_pMem + cur_file_ofs; read_buf_size = read_buf_avail = file_stat.m_comp_size; comp_remaining = 0; } else if (pUser_read_buf) { /* Use a user provided read buffer. */ if (!user_read_buf_size) return MZ_FALSE; pRead_buf = (mz_uint8 *)pUser_read_buf; read_buf_size = user_read_buf_size; read_buf_avail = 0; comp_remaining = file_stat.m_comp_size; } else { /* Temporarily allocate a read buffer. */ read_buf_size = MZ_MIN(file_stat.m_comp_size, (mz_uint64)MZ_ZIP_MAX_IO_BUF_SIZE); if (((sizeof(size_t) == sizeof(mz_uint32))) && (read_buf_size > 0x7FFFFFFF)) return mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR); if (NULL == (pRead_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, (size_t)read_buf_size))) return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); read_buf_avail = 0; comp_remaining = file_stat.m_comp_size; } do { /* The size_t cast here should be OK because we've verified that the output buffer is >= file_stat.m_uncomp_size above */ size_t in_buf_size, out_buf_size = (size_t)(file_stat.m_uncomp_size - out_buf_ofs); if ((!read_buf_avail) && (!pZip->m_pState->m_pMem)) { read_buf_avail = MZ_MIN(read_buf_size, comp_remaining); if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pRead_buf, (size_t)read_buf_avail) != read_buf_avail) { status = TINFL_STATUS_FAILED; mz_zip_set_error(pZip, MZ_ZIP_DECOMPRESSION_FAILED); break; } cur_file_ofs += read_buf_avail; comp_remaining -= read_buf_avail; read_buf_ofs = 0; } in_buf_size = (size_t)read_buf_avail; status = tinfl_decompress(&inflator, (mz_uint8 *)pRead_buf + read_buf_ofs, &in_buf_size, (mz_uint8 *)pBuf, (mz_uint8 *)pBuf + out_buf_ofs, &out_buf_size, TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF | (comp_remaining ? TINFL_FLAG_HAS_MORE_INPUT : 0)); read_buf_avail -= in_buf_size; read_buf_ofs += in_buf_size; out_buf_ofs += out_buf_size; } while (status == TINFL_STATUS_NEEDS_MORE_INPUT); if (status == TINFL_STATUS_DONE) { /* Make sure the entire file was decompressed, and check its CRC. */ if (out_buf_ofs != file_stat.m_uncomp_size) { mz_zip_set_error(pZip, MZ_ZIP_UNEXPECTED_DECOMPRESSED_SIZE); status = TINFL_STATUS_FAILED; } #ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS else if (mz_crc32(MZ_CRC32_INIT, (const mz_uint8 *)pBuf, (size_t)file_stat.m_uncomp_size) != file_stat.m_crc32) { mz_zip_set_error(pZip, MZ_ZIP_CRC_CHECK_FAILED); status = TINFL_STATUS_FAILED; } #endif } if ((!pZip->m_pState->m_pMem) && (!pUser_read_buf)) pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); return status == TINFL_STATUS_DONE; } mz_bool mz_zip_reader_extract_to_mem_no_alloc(mz_zip_archive *pZip, mz_uint file_index, void *pBuf, size_t buf_size, mz_uint flags, void *pUser_read_buf, size_t user_read_buf_size) { return mz_zip_reader_extract_to_mem_no_alloc1(pZip, file_index, pBuf, buf_size, flags, pUser_read_buf, user_read_buf_size, NULL); } mz_bool mz_zip_reader_extract_file_to_mem_no_alloc(mz_zip_archive *pZip, const char *pFilename, void *pBuf, size_t buf_size, mz_uint flags, void *pUser_read_buf, size_t user_read_buf_size) { mz_uint32 file_index; if (!mz_zip_reader_locate_file_v2(pZip, pFilename, NULL, flags, &file_index)) return MZ_FALSE; return mz_zip_reader_extract_to_mem_no_alloc1(pZip, file_index, pBuf, buf_size, flags, pUser_read_buf, user_read_buf_size, NULL); } mz_bool mz_zip_reader_extract_to_mem(mz_zip_archive *pZip, mz_uint file_index, void *pBuf, size_t buf_size, mz_uint flags) { return mz_zip_reader_extract_to_mem_no_alloc1(pZip, file_index, pBuf, buf_size, flags, NULL, 0, NULL); } mz_bool mz_zip_reader_extract_file_to_mem(mz_zip_archive *pZip, const char *pFilename, void *pBuf, size_t buf_size, mz_uint flags) { return mz_zip_reader_extract_file_to_mem_no_alloc(pZip, pFilename, pBuf, buf_size, flags, NULL, 0); } void *mz_zip_reader_extract_to_heap(mz_zip_archive *pZip, mz_uint file_index, size_t *pSize, mz_uint flags) { mz_zip_archive_file_stat file_stat; mz_uint64 alloc_size; void *pBuf; if (pSize) *pSize = 0; if (!mz_zip_reader_file_stat(pZip, file_index, &file_stat)) return NULL; alloc_size = (flags & MZ_ZIP_FLAG_COMPRESSED_DATA) ? file_stat.m_comp_size : file_stat.m_uncomp_size; if (((sizeof(size_t) == sizeof(mz_uint32))) && (alloc_size > 0x7FFFFFFF)) { mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR); return NULL; } if (NULL == (pBuf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, (size_t)alloc_size))) { mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); return NULL; } if (!mz_zip_reader_extract_to_mem_no_alloc1(pZip, file_index, pBuf, (size_t)alloc_size, flags, NULL, 0, &file_stat)) { pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); return NULL; } if (pSize) *pSize = (size_t)alloc_size; return pBuf; } void *mz_zip_reader_extract_file_to_heap(mz_zip_archive *pZip, const char *pFilename, size_t *pSize, mz_uint flags) { mz_uint32 file_index; if (!mz_zip_reader_locate_file_v2(pZip, pFilename, NULL, flags, &file_index)) { if (pSize) *pSize = 0; return MZ_FALSE; } return mz_zip_reader_extract_to_heap(pZip, file_index, pSize, flags); } mz_bool mz_zip_reader_extract_to_callback(mz_zip_archive *pZip, mz_uint file_index, mz_file_write_func pCallback, void *pOpaque, mz_uint flags) { int status = TINFL_STATUS_DONE; #ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS mz_uint file_crc32 = MZ_CRC32_INIT; #endif mz_uint64 read_buf_size, read_buf_ofs = 0, read_buf_avail, comp_remaining, out_buf_ofs = 0, cur_file_ofs; mz_zip_archive_file_stat file_stat; void *pRead_buf = NULL; void *pWrite_buf = NULL; mz_uint32 local_header_u32[(MZ_ZIP_LOCAL_DIR_HEADER_SIZE + sizeof(mz_uint32) - 1) / sizeof(mz_uint32)]; mz_uint8 *pLocal_header = (mz_uint8 *)local_header_u32; if ((!pZip) || (!pZip->m_pState) || (!pCallback) || (!pZip->m_pRead)) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); if (!mz_zip_reader_file_stat(pZip, file_index, &file_stat)) return MZ_FALSE; /* A directory or zero length file */ if ((file_stat.m_is_directory) || (!file_stat.m_comp_size)) return MZ_TRUE; /* Encryption and patch files are not supported. */ if (file_stat.m_bit_flag & (MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_IS_ENCRYPTED | MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_USES_STRONG_ENCRYPTION | MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_COMPRESSED_PATCH_FLAG)) return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_ENCRYPTION); /* This function only supports decompressing stored and deflate. */ if ((!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) && (file_stat.m_method != 0) && (file_stat.m_method != MZ_DEFLATED)) return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_METHOD); /* Read and do some minimal validation of the local directory entry (this doesn't crack the zip64 stuff, which we already have from the central dir) */ cur_file_ofs = file_stat.m_local_header_ofs; if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pLocal_header, MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != MZ_ZIP_LOCAL_DIR_HEADER_SIZE) return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); if (MZ_READ_LE32(pLocal_header) != MZ_ZIP_LOCAL_DIR_HEADER_SIG) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); cur_file_ofs += MZ_ZIP_LOCAL_DIR_HEADER_SIZE + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_FILENAME_LEN_OFS) + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_EXTRA_LEN_OFS); if ((cur_file_ofs + file_stat.m_comp_size) > pZip->m_archive_size) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); /* Decompress the file either directly from memory or from a file input buffer. */ if (pZip->m_pState->m_pMem) { pRead_buf = (mz_uint8 *)pZip->m_pState->m_pMem + cur_file_ofs; read_buf_size = read_buf_avail = file_stat.m_comp_size; comp_remaining = 0; } else { read_buf_size = MZ_MIN(file_stat.m_comp_size, (mz_uint64)MZ_ZIP_MAX_IO_BUF_SIZE); if (NULL == (pRead_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, (size_t)read_buf_size))) return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); read_buf_avail = 0; comp_remaining = file_stat.m_comp_size; } if ((flags & MZ_ZIP_FLAG_COMPRESSED_DATA) || (!file_stat.m_method)) { /* The file is stored or the caller has requested the compressed data. */ if (pZip->m_pState->m_pMem) { if (((sizeof(size_t) == sizeof(mz_uint32))) && (file_stat.m_comp_size > MZ_UINT32_MAX)) return mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR); if (pCallback(pOpaque, out_buf_ofs, pRead_buf, (size_t)file_stat.m_comp_size) != file_stat.m_comp_size) { mz_zip_set_error(pZip, MZ_ZIP_WRITE_CALLBACK_FAILED); status = TINFL_STATUS_FAILED; } else if (!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) { #ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS file_crc32 = (mz_uint32)mz_crc32(file_crc32, (const mz_uint8 *)pRead_buf, (size_t)file_stat.m_comp_size); #endif } cur_file_ofs += file_stat.m_comp_size; out_buf_ofs += file_stat.m_comp_size; comp_remaining = 0; } else { while (comp_remaining) { read_buf_avail = MZ_MIN(read_buf_size, comp_remaining); if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pRead_buf, (size_t)read_buf_avail) != read_buf_avail) { mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); status = TINFL_STATUS_FAILED; break; } #ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS if (!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) { file_crc32 = (mz_uint32)mz_crc32(file_crc32, (const mz_uint8 *)pRead_buf, (size_t)read_buf_avail); } #endif if (pCallback(pOpaque, out_buf_ofs, pRead_buf, (size_t)read_buf_avail) != read_buf_avail) { mz_zip_set_error(pZip, MZ_ZIP_WRITE_CALLBACK_FAILED); status = TINFL_STATUS_FAILED; break; } cur_file_ofs += read_buf_avail; out_buf_ofs += read_buf_avail; comp_remaining -= read_buf_avail; } } } else { tinfl_decompressor inflator; tinfl_init(&inflator); if (NULL == (pWrite_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, TINFL_LZ_DICT_SIZE))) { mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); status = TINFL_STATUS_FAILED; } else { do { mz_uint8 *pWrite_buf_cur = (mz_uint8 *)pWrite_buf + (out_buf_ofs & (TINFL_LZ_DICT_SIZE - 1)); size_t in_buf_size, out_buf_size = TINFL_LZ_DICT_SIZE - (out_buf_ofs & (TINFL_LZ_DICT_SIZE - 1)); if ((!read_buf_avail) && (!pZip->m_pState->m_pMem)) { read_buf_avail = MZ_MIN(read_buf_size, comp_remaining); if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pRead_buf, (size_t)read_buf_avail) != read_buf_avail) { mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); status = TINFL_STATUS_FAILED; break; } cur_file_ofs += read_buf_avail; comp_remaining -= read_buf_avail; read_buf_ofs = 0; } in_buf_size = (size_t)read_buf_avail; status = tinfl_decompress(&inflator, (const mz_uint8 *)pRead_buf + read_buf_ofs, &in_buf_size, (mz_uint8 *)pWrite_buf, pWrite_buf_cur, &out_buf_size, comp_remaining ? TINFL_FLAG_HAS_MORE_INPUT : 0); read_buf_avail -= in_buf_size; read_buf_ofs += in_buf_size; if (out_buf_size) { if (pCallback(pOpaque, out_buf_ofs, pWrite_buf_cur, out_buf_size) != out_buf_size) { mz_zip_set_error(pZip, MZ_ZIP_WRITE_CALLBACK_FAILED); status = TINFL_STATUS_FAILED; break; } #ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS file_crc32 = (mz_uint32)mz_crc32(file_crc32, pWrite_buf_cur, out_buf_size); #endif if ((out_buf_ofs += out_buf_size) > file_stat.m_uncomp_size) { mz_zip_set_error(pZip, MZ_ZIP_DECOMPRESSION_FAILED); status = TINFL_STATUS_FAILED; break; } } } while ((status == TINFL_STATUS_NEEDS_MORE_INPUT) || (status == TINFL_STATUS_HAS_MORE_OUTPUT)); } } if ((status == TINFL_STATUS_DONE) && (!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA))) { /* Make sure the entire file was decompressed, and check its CRC. */ if (out_buf_ofs != file_stat.m_uncomp_size) { mz_zip_set_error(pZip, MZ_ZIP_UNEXPECTED_DECOMPRESSED_SIZE); status = TINFL_STATUS_FAILED; } #ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS else if (file_crc32 != file_stat.m_crc32) { mz_zip_set_error(pZip, MZ_ZIP_DECOMPRESSION_FAILED); status = TINFL_STATUS_FAILED; } #endif } if (!pZip->m_pState->m_pMem) pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); if (pWrite_buf) pZip->m_pFree(pZip->m_pAlloc_opaque, pWrite_buf); return status == TINFL_STATUS_DONE; } mz_bool mz_zip_reader_extract_file_to_callback(mz_zip_archive *pZip, const char *pFilename, mz_file_write_func pCallback, void *pOpaque, mz_uint flags) { mz_uint32 file_index; if (!mz_zip_reader_locate_file_v2(pZip, pFilename, NULL, flags, &file_index)) return MZ_FALSE; return mz_zip_reader_extract_to_callback(pZip, file_index, pCallback, pOpaque, flags); } mz_zip_reader_extract_iter_state* mz_zip_reader_extract_iter_new(mz_zip_archive *pZip, mz_uint file_index, mz_uint flags) { mz_zip_reader_extract_iter_state *pState; mz_uint32 local_header_u32[(MZ_ZIP_LOCAL_DIR_HEADER_SIZE + sizeof(mz_uint32) - 1) / sizeof(mz_uint32)]; mz_uint8 *pLocal_header = (mz_uint8 *)local_header_u32; /* Argument sanity check */ if ((!pZip) || (!pZip->m_pState)) return NULL; /* Allocate an iterator status structure */ pState = (mz_zip_reader_extract_iter_state*)pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, sizeof(mz_zip_reader_extract_iter_state)); if (!pState) { mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); return NULL; } /* Fetch file details */ if (!mz_zip_reader_file_stat(pZip, file_index, &pState->file_stat)) { pZip->m_pFree(pZip->m_pAlloc_opaque, pState); return NULL; } /* Encryption and patch files are not supported. */ if (pState->file_stat.m_bit_flag & (MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_IS_ENCRYPTED | MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_USES_STRONG_ENCRYPTION | MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_COMPRESSED_PATCH_FLAG)) { mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_ENCRYPTION); pZip->m_pFree(pZip->m_pAlloc_opaque, pState); return NULL; } /* This function only supports decompressing stored and deflate. */ if ((!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) && (pState->file_stat.m_method != 0) && (pState->file_stat.m_method != MZ_DEFLATED)) { mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_METHOD); pZip->m_pFree(pZip->m_pAlloc_opaque, pState); return NULL; } /* Init state - save args */ pState->pZip = pZip; pState->flags = flags; /* Init state - reset variables to defaults */ pState->status = TINFL_STATUS_DONE; #ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS pState->file_crc32 = MZ_CRC32_INIT; #endif pState->read_buf_ofs = 0; pState->out_buf_ofs = 0; pState->pRead_buf = NULL; pState->pWrite_buf = NULL; pState->out_blk_remain = 0; /* Read and parse the local directory entry. */ pState->cur_file_ofs = pState->file_stat.m_local_header_ofs; if (pZip->m_pRead(pZip->m_pIO_opaque, pState->cur_file_ofs, pLocal_header, MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != MZ_ZIP_LOCAL_DIR_HEADER_SIZE) { mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); pZip->m_pFree(pZip->m_pAlloc_opaque, pState); return NULL; } if (MZ_READ_LE32(pLocal_header) != MZ_ZIP_LOCAL_DIR_HEADER_SIG) { mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); pZip->m_pFree(pZip->m_pAlloc_opaque, pState); return NULL; } pState->cur_file_ofs += MZ_ZIP_LOCAL_DIR_HEADER_SIZE + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_FILENAME_LEN_OFS) + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_EXTRA_LEN_OFS); if ((pState->cur_file_ofs + pState->file_stat.m_comp_size) > pZip->m_archive_size) { mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); pZip->m_pFree(pZip->m_pAlloc_opaque, pState); return NULL; } /* Decompress the file either directly from memory or from a file input buffer. */ if (pZip->m_pState->m_pMem) { pState->pRead_buf = (mz_uint8 *)pZip->m_pState->m_pMem + pState->cur_file_ofs; pState->read_buf_size = pState->read_buf_avail = pState->file_stat.m_comp_size; pState->comp_remaining = pState->file_stat.m_comp_size; } else { if (!((flags & MZ_ZIP_FLAG_COMPRESSED_DATA) || (!pState->file_stat.m_method))) { /* Decompression required, therefore intermediate read buffer required */ pState->read_buf_size = MZ_MIN(pState->file_stat.m_comp_size, (mz_uint64)MZ_ZIP_MAX_IO_BUF_SIZE); if (NULL == (pState->pRead_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, (size_t)pState->read_buf_size))) { mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); pZip->m_pFree(pZip->m_pAlloc_opaque, pState); return NULL; } } else { /* Decompression not required - we will be reading directly into user buffer, no temp buf required */ pState->read_buf_size = 0; } pState->read_buf_avail = 0; pState->comp_remaining = pState->file_stat.m_comp_size; } if (!((flags & MZ_ZIP_FLAG_COMPRESSED_DATA) || (!pState->file_stat.m_method))) { /* Decompression required, init decompressor */ tinfl_init( &pState->inflator ); /* Allocate write buffer */ if (NULL == (pState->pWrite_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, TINFL_LZ_DICT_SIZE))) { mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); if (pState->pRead_buf) pZip->m_pFree(pZip->m_pAlloc_opaque, pState->pRead_buf); pZip->m_pFree(pZip->m_pAlloc_opaque, pState); return NULL; } } return pState; } mz_zip_reader_extract_iter_state* mz_zip_reader_extract_file_iter_new(mz_zip_archive *pZip, const char *pFilename, mz_uint flags) { mz_uint32 file_index; /* Locate file index by name */ if (!mz_zip_reader_locate_file_v2(pZip, pFilename, NULL, flags, &file_index)) return NULL; /* Construct iterator */ return mz_zip_reader_extract_iter_new(pZip, file_index, flags); } size_t mz_zip_reader_extract_iter_read(mz_zip_reader_extract_iter_state* pState, void* pvBuf, size_t buf_size) { size_t copied_to_caller = 0; /* Argument sanity check */ if ((!pState) || (!pState->pZip) || (!pState->pZip->m_pState) || (!pvBuf)) return 0; if ((pState->flags & MZ_ZIP_FLAG_COMPRESSED_DATA) || (!pState->file_stat.m_method)) { /* The file is stored or the caller has requested the compressed data, calc amount to return. */ copied_to_caller = (size_t)MZ_MIN( buf_size, pState->comp_remaining ); /* Zip is in memory....or requires reading from a file? */ if (pState->pZip->m_pState->m_pMem) { /* Copy data to caller's buffer */ memcpy( pvBuf, pState->pRead_buf, copied_to_caller ); pState->pRead_buf = ((mz_uint8*)pState->pRead_buf) + copied_to_caller; } else { /* Read directly into caller's buffer */ if (pState->pZip->m_pRead(pState->pZip->m_pIO_opaque, pState->cur_file_ofs, pvBuf, copied_to_caller) != copied_to_caller) { /* Failed to read all that was asked for, flag failure and alert user */ mz_zip_set_error(pState->pZip, MZ_ZIP_FILE_READ_FAILED); pState->status = TINFL_STATUS_FAILED; copied_to_caller = 0; } } #ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS /* Compute CRC if not returning compressed data only */ if (!(pState->flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) pState->file_crc32 = (mz_uint32)mz_crc32(pState->file_crc32, (const mz_uint8 *)pvBuf, copied_to_caller); #endif /* Advance offsets, dec counters */ pState->cur_file_ofs += copied_to_caller; pState->out_buf_ofs += copied_to_caller; pState->comp_remaining -= copied_to_caller; } else { do { /* Calc ptr to write buffer - given current output pos and block size */ mz_uint8 *pWrite_buf_cur = (mz_uint8 *)pState->pWrite_buf + (pState->out_buf_ofs & (TINFL_LZ_DICT_SIZE - 1)); /* Calc max output size - given current output pos and block size */ size_t in_buf_size, out_buf_size = TINFL_LZ_DICT_SIZE - (pState->out_buf_ofs & (TINFL_LZ_DICT_SIZE - 1)); if (!pState->out_blk_remain) { /* Read more data from file if none available (and reading from file) */ if ((!pState->read_buf_avail) && (!pState->pZip->m_pState->m_pMem)) { /* Calc read size */ pState->read_buf_avail = MZ_MIN(pState->read_buf_size, pState->comp_remaining); if (pState->pZip->m_pRead(pState->pZip->m_pIO_opaque, pState->cur_file_ofs, pState->pRead_buf, (size_t)pState->read_buf_avail) != pState->read_buf_avail) { mz_zip_set_error(pState->pZip, MZ_ZIP_FILE_READ_FAILED); pState->status = TINFL_STATUS_FAILED; break; } /* Advance offsets, dec counters */ pState->cur_file_ofs += pState->read_buf_avail; pState->comp_remaining -= pState->read_buf_avail; pState->read_buf_ofs = 0; } /* Perform decompression */ in_buf_size = (size_t)pState->read_buf_avail; pState->status = tinfl_decompress(&pState->inflator, (const mz_uint8 *)pState->pRead_buf + pState->read_buf_ofs, &in_buf_size, (mz_uint8 *)pState->pWrite_buf, pWrite_buf_cur, &out_buf_size, pState->comp_remaining ? TINFL_FLAG_HAS_MORE_INPUT : 0); pState->read_buf_avail -= in_buf_size; pState->read_buf_ofs += in_buf_size; /* Update current output block size remaining */ pState->out_blk_remain = out_buf_size; } if (pState->out_blk_remain) { /* Calc amount to return. */ size_t to_copy = MZ_MIN( (buf_size - copied_to_caller), pState->out_blk_remain ); /* Copy data to caller's buffer */ memcpy( (mz_uint8*)pvBuf + copied_to_caller, pWrite_buf_cur, to_copy ); #ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS /* Perform CRC */ pState->file_crc32 = (mz_uint32)mz_crc32(pState->file_crc32, pWrite_buf_cur, to_copy); #endif /* Decrement data consumed from block */ pState->out_blk_remain -= to_copy; /* Inc output offset, while performing sanity check */ if ((pState->out_buf_ofs += to_copy) > pState->file_stat.m_uncomp_size) { mz_zip_set_error(pState->pZip, MZ_ZIP_DECOMPRESSION_FAILED); pState->status = TINFL_STATUS_FAILED; break; } /* Increment counter of data copied to caller */ copied_to_caller += to_copy; } } while ( (copied_to_caller < buf_size) && ((pState->status == TINFL_STATUS_NEEDS_MORE_INPUT) || (pState->status == TINFL_STATUS_HAS_MORE_OUTPUT)) ); } /* Return how many bytes were copied into user buffer */ return copied_to_caller; } mz_bool mz_zip_reader_extract_iter_free(mz_zip_reader_extract_iter_state* pState) { int status; /* Argument sanity check */ if ((!pState) || (!pState->pZip) || (!pState->pZip->m_pState)) return MZ_FALSE; /* Was decompression completed and requested? */ if ((pState->status == TINFL_STATUS_DONE) && (!(pState->flags & MZ_ZIP_FLAG_COMPRESSED_DATA))) { /* Make sure the entire file was decompressed, and check its CRC. */ if (pState->out_buf_ofs != pState->file_stat.m_uncomp_size) { mz_zip_set_error(pState->pZip, MZ_ZIP_UNEXPECTED_DECOMPRESSED_SIZE); pState->status = TINFL_STATUS_FAILED; } #ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS else if (pState->file_crc32 != pState->file_stat.m_crc32) { mz_zip_set_error(pState->pZip, MZ_ZIP_DECOMPRESSION_FAILED); pState->status = TINFL_STATUS_FAILED; } #endif } /* Free buffers */ if (!pState->pZip->m_pState->m_pMem) pState->pZip->m_pFree(pState->pZip->m_pAlloc_opaque, pState->pRead_buf); if (pState->pWrite_buf) pState->pZip->m_pFree(pState->pZip->m_pAlloc_opaque, pState->pWrite_buf); /* Save status */ status = pState->status; /* Free context */ pState->pZip->m_pFree(pState->pZip->m_pAlloc_opaque, pState); return status == TINFL_STATUS_DONE; } #ifndef MINIZ_NO_STDIO static size_t mz_zip_file_write_callback(void *pOpaque, mz_uint64 ofs, const void *pBuf, size_t n) { (void)ofs; return MZ_FWRITE(pBuf, 1, n, (MZ_FILE *)pOpaque); } mz_bool mz_zip_reader_extract_to_file(mz_zip_archive *pZip, mz_uint file_index, const char *pDst_filename, mz_uint flags) { mz_bool status; mz_zip_archive_file_stat file_stat; MZ_FILE *pFile; if (!mz_zip_reader_file_stat(pZip, file_index, &file_stat)) return MZ_FALSE; if ((file_stat.m_is_directory) || (!file_stat.m_is_supported)) return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_FEATURE); pFile = MZ_FOPEN(pDst_filename, "wb"); if (!pFile) return mz_zip_set_error(pZip, MZ_ZIP_FILE_OPEN_FAILED); status = mz_zip_reader_extract_to_callback(pZip, file_index, mz_zip_file_write_callback, pFile, flags); if (MZ_FCLOSE(pFile) == EOF) { if (status) mz_zip_set_error(pZip, MZ_ZIP_FILE_CLOSE_FAILED); status = MZ_FALSE; } #if !defined(MINIZ_NO_TIME) && !defined(MINIZ_NO_STDIO) if (status) mz_zip_set_file_times(pDst_filename, file_stat.m_time, file_stat.m_time); #endif return status; } mz_bool mz_zip_reader_extract_file_to_file(mz_zip_archive *pZip, const char *pArchive_filename, const char *pDst_filename, mz_uint flags) { mz_uint32 file_index; if (!mz_zip_reader_locate_file_v2(pZip, pArchive_filename, NULL, flags, &file_index)) return MZ_FALSE; return mz_zip_reader_extract_to_file(pZip, file_index, pDst_filename, flags); } mz_bool mz_zip_reader_extract_to_cfile(mz_zip_archive *pZip, mz_uint file_index, MZ_FILE *pFile, mz_uint flags) { mz_zip_archive_file_stat file_stat; if (!mz_zip_reader_file_stat(pZip, file_index, &file_stat)) return MZ_FALSE; if ((file_stat.m_is_directory) || (!file_stat.m_is_supported)) return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_FEATURE); return mz_zip_reader_extract_to_callback(pZip, file_index, mz_zip_file_write_callback, pFile, flags); } mz_bool mz_zip_reader_extract_file_to_cfile(mz_zip_archive *pZip, const char *pArchive_filename, MZ_FILE *pFile, mz_uint flags) { mz_uint32 file_index; if (!mz_zip_reader_locate_file_v2(pZip, pArchive_filename, NULL, flags, &file_index)) return MZ_FALSE; return mz_zip_reader_extract_to_cfile(pZip, file_index, pFile, flags); } #endif /* #ifndef MINIZ_NO_STDIO */ static size_t mz_zip_compute_crc32_callback(void *pOpaque, mz_uint64 file_ofs, const void *pBuf, size_t n) { mz_uint32 *p = (mz_uint32 *)pOpaque; (void)file_ofs; *p = (mz_uint32)mz_crc32(*p, (const mz_uint8 *)pBuf, n); return n; } mz_bool mz_zip_validate_file(mz_zip_archive *pZip, mz_uint file_index, mz_uint flags) { mz_zip_archive_file_stat file_stat; mz_zip_internal_state *pState; const mz_uint8 *pCentral_dir_header; mz_bool found_zip64_ext_data_in_cdir = MZ_FALSE; mz_bool found_zip64_ext_data_in_ldir = MZ_FALSE; mz_uint32 local_header_u32[(MZ_ZIP_LOCAL_DIR_HEADER_SIZE + sizeof(mz_uint32) - 1) / sizeof(mz_uint32)]; mz_uint8 *pLocal_header = (mz_uint8 *)local_header_u32; mz_uint64 local_header_ofs = 0; mz_uint32 local_header_filename_len, local_header_extra_len, local_header_crc32; mz_uint64 local_header_comp_size, local_header_uncomp_size; mz_uint32 uncomp_crc32 = MZ_CRC32_INIT; mz_bool has_data_descriptor; mz_uint32 local_header_bit_flags; mz_zip_array file_data_array; mz_zip_array_init(&file_data_array, 1); if ((!pZip) || (!pZip->m_pState) || (!pZip->m_pAlloc) || (!pZip->m_pFree) || (!pZip->m_pRead)) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); if (file_index > pZip->m_total_files) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); pState = pZip->m_pState; pCentral_dir_header = mz_zip_get_cdh(pZip, file_index); if (!mz_zip_file_stat_internal(pZip, file_index, pCentral_dir_header, &file_stat, &found_zip64_ext_data_in_cdir)) return MZ_FALSE; /* A directory or zero length file */ if ((file_stat.m_is_directory) || (!file_stat.m_uncomp_size)) return MZ_TRUE; /* Encryption and patch files are not supported. */ if (file_stat.m_is_encrypted) return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_ENCRYPTION); /* This function only supports stored and deflate. */ if ((file_stat.m_method != 0) && (file_stat.m_method != MZ_DEFLATED)) return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_METHOD); if (!file_stat.m_is_supported) return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_FEATURE); /* Read and parse the local directory entry. */ local_header_ofs = file_stat.m_local_header_ofs; if (pZip->m_pRead(pZip->m_pIO_opaque, local_header_ofs, pLocal_header, MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != MZ_ZIP_LOCAL_DIR_HEADER_SIZE) return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); if (MZ_READ_LE32(pLocal_header) != MZ_ZIP_LOCAL_DIR_HEADER_SIG) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); local_header_filename_len = MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_FILENAME_LEN_OFS); local_header_extra_len = MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_EXTRA_LEN_OFS); local_header_comp_size = MZ_READ_LE32(pLocal_header + MZ_ZIP_LDH_COMPRESSED_SIZE_OFS); local_header_uncomp_size = MZ_READ_LE32(pLocal_header + MZ_ZIP_LDH_DECOMPRESSED_SIZE_OFS); local_header_crc32 = MZ_READ_LE32(pLocal_header + MZ_ZIP_LDH_CRC32_OFS); local_header_bit_flags = MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_BIT_FLAG_OFS); has_data_descriptor = (local_header_bit_flags & 8) != 0; if (local_header_filename_len != strlen(file_stat.m_filename)) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); if ((local_header_ofs + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + local_header_filename_len + local_header_extra_len + file_stat.m_comp_size) > pZip->m_archive_size) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); if (!mz_zip_array_resize(pZip, &file_data_array, MZ_MAX(local_header_filename_len, local_header_extra_len), MZ_FALSE)) { mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); goto handle_failure; } if (local_header_filename_len) { if (pZip->m_pRead(pZip->m_pIO_opaque, local_header_ofs + MZ_ZIP_LOCAL_DIR_HEADER_SIZE, file_data_array.m_p, local_header_filename_len) != local_header_filename_len) { mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); goto handle_failure; } /* I've seen 1 archive that had the same pathname, but used backslashes in the local dir and forward slashes in the central dir. Do we care about this? For now, this case will fail validation. */ if (memcmp(file_stat.m_filename, file_data_array.m_p, local_header_filename_len) != 0) { mz_zip_set_error(pZip, MZ_ZIP_VALIDATION_FAILED); goto handle_failure; } } if ((local_header_extra_len) && ((local_header_comp_size == MZ_UINT32_MAX) || (local_header_uncomp_size == MZ_UINT32_MAX))) { mz_uint32 extra_size_remaining = local_header_extra_len; const mz_uint8 *pExtra_data = (const mz_uint8 *)file_data_array.m_p; if (pZip->m_pRead(pZip->m_pIO_opaque, local_header_ofs + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + local_header_filename_len, file_data_array.m_p, local_header_extra_len) != local_header_extra_len) { mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); goto handle_failure; } do { mz_uint32 field_id, field_data_size, field_total_size; if (extra_size_remaining < (sizeof(mz_uint16) * 2)) { mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); goto handle_failure; } field_id = MZ_READ_LE16(pExtra_data); field_data_size = MZ_READ_LE16(pExtra_data + sizeof(mz_uint16)); field_total_size = field_data_size + sizeof(mz_uint16) * 2; if (field_total_size > extra_size_remaining) { mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); goto handle_failure; } if (field_id == MZ_ZIP64_EXTENDED_INFORMATION_FIELD_HEADER_ID) { const mz_uint8 *pSrc_field_data = pExtra_data + sizeof(mz_uint32); if (field_data_size < sizeof(mz_uint64) * 2) { mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); goto handle_failure; } local_header_uncomp_size = MZ_READ_LE64(pSrc_field_data); local_header_comp_size = MZ_READ_LE64(pSrc_field_data + sizeof(mz_uint64)); found_zip64_ext_data_in_ldir = MZ_TRUE; break; } pExtra_data += field_total_size; extra_size_remaining -= field_total_size; } while (extra_size_remaining); } /* TODO: parse local header extra data when local_header_comp_size is 0xFFFFFFFF! (big_descriptor.zip) */ /* I've seen zips in the wild with the data descriptor bit set, but proper local header values and bogus data descriptors */ if ((has_data_descriptor) && (!local_header_comp_size) && (!local_header_crc32)) { mz_uint8 descriptor_buf[32]; mz_bool has_id; const mz_uint8 *pSrc; mz_uint32 file_crc32; mz_uint64 comp_size = 0, uncomp_size = 0; mz_uint32 num_descriptor_uint32s = ((pState->m_zip64) || (found_zip64_ext_data_in_ldir)) ? 6 : 4; if (pZip->m_pRead(pZip->m_pIO_opaque, local_header_ofs + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + local_header_filename_len + local_header_extra_len + file_stat.m_comp_size, descriptor_buf, sizeof(mz_uint32) * num_descriptor_uint32s) != (sizeof(mz_uint32) * num_descriptor_uint32s)) { mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); goto handle_failure; } has_id = (MZ_READ_LE32(descriptor_buf) == MZ_ZIP_DATA_DESCRIPTOR_ID); pSrc = has_id ? (descriptor_buf + sizeof(mz_uint32)) : descriptor_buf; file_crc32 = MZ_READ_LE32(pSrc); if ((pState->m_zip64) || (found_zip64_ext_data_in_ldir)) { comp_size = MZ_READ_LE64(pSrc + sizeof(mz_uint32)); uncomp_size = MZ_READ_LE64(pSrc + sizeof(mz_uint32) + sizeof(mz_uint64)); } else { comp_size = MZ_READ_LE32(pSrc + sizeof(mz_uint32)); uncomp_size = MZ_READ_LE32(pSrc + sizeof(mz_uint32) + sizeof(mz_uint32)); } if ((file_crc32 != file_stat.m_crc32) || (comp_size != file_stat.m_comp_size) || (uncomp_size != file_stat.m_uncomp_size)) { mz_zip_set_error(pZip, MZ_ZIP_VALIDATION_FAILED); goto handle_failure; } } else { if ((local_header_crc32 != file_stat.m_crc32) || (local_header_comp_size != file_stat.m_comp_size) || (local_header_uncomp_size != file_stat.m_uncomp_size)) { mz_zip_set_error(pZip, MZ_ZIP_VALIDATION_FAILED); goto handle_failure; } } mz_zip_array_clear(pZip, &file_data_array); if ((flags & MZ_ZIP_FLAG_VALIDATE_HEADERS_ONLY) == 0) { if (!mz_zip_reader_extract_to_callback(pZip, file_index, mz_zip_compute_crc32_callback, &uncomp_crc32, 0)) return MZ_FALSE; /* 1 more check to be sure, although the extract checks too. */ if (uncomp_crc32 != file_stat.m_crc32) { mz_zip_set_error(pZip, MZ_ZIP_VALIDATION_FAILED); return MZ_FALSE; } } return MZ_TRUE; handle_failure: mz_zip_array_clear(pZip, &file_data_array); return MZ_FALSE; } mz_bool mz_zip_validate_archive(mz_zip_archive *pZip, mz_uint flags) { mz_zip_internal_state *pState; mz_uint32 i; if ((!pZip) || (!pZip->m_pState) || (!pZip->m_pAlloc) || (!pZip->m_pFree) || (!pZip->m_pRead)) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); pState = pZip->m_pState; /* Basic sanity checks */ if (!pState->m_zip64) { if (pZip->m_total_files > MZ_UINT16_MAX) return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); if (pZip->m_archive_size > MZ_UINT32_MAX) return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); } else { if (pState->m_central_dir.m_size >= MZ_UINT32_MAX) return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); } for (i = 0; i < pZip->m_total_files; i++) { if (MZ_ZIP_FLAG_VALIDATE_LOCATE_FILE_FLAG & flags) { mz_uint32 found_index; mz_zip_archive_file_stat stat; if (!mz_zip_reader_file_stat(pZip, i, &stat)) return MZ_FALSE; if (!mz_zip_reader_locate_file_v2(pZip, stat.m_filename, NULL, 0, &found_index)) return MZ_FALSE; /* This check can fail if there are duplicate filenames in the archive (which we don't check for when writing - that's up to the user) */ if (found_index != i) return mz_zip_set_error(pZip, MZ_ZIP_VALIDATION_FAILED); } if (!mz_zip_validate_file(pZip, i, flags)) return MZ_FALSE; } return MZ_TRUE; } mz_bool mz_zip_validate_mem_archive(const void *pMem, size_t size, mz_uint flags, mz_zip_error *pErr) { mz_bool success = MZ_TRUE; mz_zip_archive zip; mz_zip_error actual_err = MZ_ZIP_NO_ERROR; if ((!pMem) || (!size)) { if (pErr) *pErr = MZ_ZIP_INVALID_PARAMETER; return MZ_FALSE; } mz_zip_zero_struct(&zip); if (!mz_zip_reader_init_mem(&zip, pMem, size, flags)) { if (pErr) *pErr = zip.m_last_error; return MZ_FALSE; } if (!mz_zip_validate_archive(&zip, flags)) { actual_err = zip.m_last_error; success = MZ_FALSE; } if (!mz_zip_reader_end_internal(&zip, success)) { if (!actual_err) actual_err = zip.m_last_error; success = MZ_FALSE; } if (pErr) *pErr = actual_err; return success; } #ifndef MINIZ_NO_STDIO mz_bool mz_zip_validate_file_archive(const char *pFilename, mz_uint flags, mz_zip_error *pErr) { mz_bool success = MZ_TRUE; mz_zip_archive zip; mz_zip_error actual_err = MZ_ZIP_NO_ERROR; if (!pFilename) { if (pErr) *pErr = MZ_ZIP_INVALID_PARAMETER; return MZ_FALSE; } mz_zip_zero_struct(&zip); if (!mz_zip_reader_init_file_v2(&zip, pFilename, flags, 0, 0)) { if (pErr) *pErr = zip.m_last_error; return MZ_FALSE; } if (!mz_zip_validate_archive(&zip, flags)) { actual_err = zip.m_last_error; success = MZ_FALSE; } if (!mz_zip_reader_end_internal(&zip, success)) { if (!actual_err) actual_err = zip.m_last_error; success = MZ_FALSE; } if (pErr) *pErr = actual_err; return success; } #endif /* #ifndef MINIZ_NO_STDIO */ /* ------------------- .ZIP archive writing */ #ifndef MINIZ_NO_ARCHIVE_WRITING_APIS static MZ_FORCEINLINE void mz_write_le16(mz_uint8 *p, mz_uint16 v) { p[0] = (mz_uint8)v; p[1] = (mz_uint8)(v >> 8); } static MZ_FORCEINLINE void mz_write_le32(mz_uint8 *p, mz_uint32 v) { p[0] = (mz_uint8)v; p[1] = (mz_uint8)(v >> 8); p[2] = (mz_uint8)(v >> 16); p[3] = (mz_uint8)(v >> 24); } static MZ_FORCEINLINE void mz_write_le64(mz_uint8 *p, mz_uint64 v) { mz_write_le32(p, (mz_uint32)v); mz_write_le32(p + sizeof(mz_uint32), (mz_uint32)(v >> 32)); } #define MZ_WRITE_LE16(p, v) mz_write_le16((mz_uint8 *)(p), (mz_uint16)(v)) #define MZ_WRITE_LE32(p, v) mz_write_le32((mz_uint8 *)(p), (mz_uint32)(v)) #define MZ_WRITE_LE64(p, v) mz_write_le64((mz_uint8 *)(p), (mz_uint64)(v)) static size_t mz_zip_heap_write_func(void *pOpaque, mz_uint64 file_ofs, const void *pBuf, size_t n) { mz_zip_archive *pZip = (mz_zip_archive *)pOpaque; mz_zip_internal_state *pState = pZip->m_pState; mz_uint64 new_size = MZ_MAX(file_ofs + n, pState->m_mem_size); if (!n) return 0; /* An allocation this big is likely to just fail on 32-bit systems, so don't even go there. */ if ((sizeof(size_t) == sizeof(mz_uint32)) && (new_size > 0x7FFFFFFF)) { mz_zip_set_error(pZip, MZ_ZIP_FILE_TOO_LARGE); return 0; } if (new_size > pState->m_mem_capacity) { void *pNew_block; size_t new_capacity = MZ_MAX(64, pState->m_mem_capacity); while (new_capacity < new_size) new_capacity *= 2; if (NULL == (pNew_block = pZip->m_pRealloc(pZip->m_pAlloc_opaque, pState->m_pMem, 1, new_capacity))) { mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); return 0; } pState->m_pMem = pNew_block; pState->m_mem_capacity = new_capacity; } memcpy((mz_uint8 *)pState->m_pMem + file_ofs, pBuf, n); pState->m_mem_size = (size_t)new_size; return n; } static mz_bool mz_zip_writer_end_internal(mz_zip_archive *pZip, mz_bool set_last_error) { mz_zip_internal_state *pState; mz_bool status = MZ_TRUE; if ((!pZip) || (!pZip->m_pState) || (!pZip->m_pAlloc) || (!pZip->m_pFree) || ((pZip->m_zip_mode != MZ_ZIP_MODE_WRITING) && (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING_HAS_BEEN_FINALIZED))) { if (set_last_error) mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); return MZ_FALSE; } pState = pZip->m_pState; pZip->m_pState = NULL; mz_zip_array_clear(pZip, &pState->m_central_dir); mz_zip_array_clear(pZip, &pState->m_central_dir_offsets); mz_zip_array_clear(pZip, &pState->m_sorted_central_dir_offsets); #ifndef MINIZ_NO_STDIO if (pState->m_pFile) { if (pZip->m_zip_type == MZ_ZIP_TYPE_FILE) { if (MZ_FCLOSE(pState->m_pFile) == EOF) { if (set_last_error) mz_zip_set_error(pZip, MZ_ZIP_FILE_CLOSE_FAILED); status = MZ_FALSE; } } pState->m_pFile = NULL; } #endif /* #ifndef MINIZ_NO_STDIO */ if ((pZip->m_pWrite == mz_zip_heap_write_func) && (pState->m_pMem)) { pZip->m_pFree(pZip->m_pAlloc_opaque, pState->m_pMem); pState->m_pMem = NULL; } pZip->m_pFree(pZip->m_pAlloc_opaque, pState); pZip->m_zip_mode = MZ_ZIP_MODE_INVALID; return status; } mz_bool mz_zip_writer_init_v2(mz_zip_archive *pZip, mz_uint64 existing_size, mz_uint flags) { mz_bool zip64 = (flags & MZ_ZIP_FLAG_WRITE_ZIP64) != 0; if ((!pZip) || (pZip->m_pState) || (!pZip->m_pWrite) || (pZip->m_zip_mode != MZ_ZIP_MODE_INVALID)) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); if (flags & MZ_ZIP_FLAG_WRITE_ALLOW_READING) { if (!pZip->m_pRead) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); } if (pZip->m_file_offset_alignment) { /* Ensure user specified file offset alignment is a power of 2. */ if (pZip->m_file_offset_alignment & (pZip->m_file_offset_alignment - 1)) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); } if (!pZip->m_pAlloc) pZip->m_pAlloc = miniz_def_alloc_func; if (!pZip->m_pFree) pZip->m_pFree = miniz_def_free_func; if (!pZip->m_pRealloc) pZip->m_pRealloc = miniz_def_realloc_func; pZip->m_archive_size = existing_size; pZip->m_central_directory_file_ofs = 0; pZip->m_total_files = 0; if (NULL == (pZip->m_pState = (mz_zip_internal_state *)pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, sizeof(mz_zip_internal_state)))) return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); memset(pZip->m_pState, 0, sizeof(mz_zip_internal_state)); MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_central_dir, sizeof(mz_uint8)); MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_central_dir_offsets, sizeof(mz_uint32)); MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_sorted_central_dir_offsets, sizeof(mz_uint32)); pZip->m_pState->m_zip64 = zip64; pZip->m_pState->m_zip64_has_extended_info_fields = zip64; pZip->m_zip_type = MZ_ZIP_TYPE_USER; pZip->m_zip_mode = MZ_ZIP_MODE_WRITING; return MZ_TRUE; } mz_bool mz_zip_writer_init(mz_zip_archive *pZip, mz_uint64 existing_size) { return mz_zip_writer_init_v2(pZip, existing_size, 0); } mz_bool mz_zip_writer_init_heap_v2(mz_zip_archive *pZip, size_t size_to_reserve_at_beginning, size_t initial_allocation_size, mz_uint flags) { pZip->m_pWrite = mz_zip_heap_write_func; pZip->m_pNeeds_keepalive = NULL; if (flags & MZ_ZIP_FLAG_WRITE_ALLOW_READING) pZip->m_pRead = mz_zip_mem_read_func; pZip->m_pIO_opaque = pZip; if (!mz_zip_writer_init_v2(pZip, size_to_reserve_at_beginning, flags)) return MZ_FALSE; pZip->m_zip_type = MZ_ZIP_TYPE_HEAP; if (0 != (initial_allocation_size = MZ_MAX(initial_allocation_size, size_to_reserve_at_beginning))) { if (NULL == (pZip->m_pState->m_pMem = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, initial_allocation_size))) { mz_zip_writer_end_internal(pZip, MZ_FALSE); return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); } pZip->m_pState->m_mem_capacity = initial_allocation_size; } return MZ_TRUE; } mz_bool mz_zip_writer_init_heap(mz_zip_archive *pZip, size_t size_to_reserve_at_beginning, size_t initial_allocation_size) { return mz_zip_writer_init_heap_v2(pZip, size_to_reserve_at_beginning, initial_allocation_size, 0); } #ifndef MINIZ_NO_STDIO static size_t mz_zip_file_write_func(void *pOpaque, mz_uint64 file_ofs, const void *pBuf, size_t n) { mz_zip_archive *pZip = (mz_zip_archive *)pOpaque; mz_int64 cur_ofs = MZ_FTELL64(pZip->m_pState->m_pFile); file_ofs += pZip->m_pState->m_file_archive_start_ofs; if (((mz_int64)file_ofs < 0) || (((cur_ofs != (mz_int64)file_ofs)) && (MZ_FSEEK64(pZip->m_pState->m_pFile, (mz_int64)file_ofs, SEEK_SET)))) { mz_zip_set_error(pZip, MZ_ZIP_FILE_SEEK_FAILED); return 0; } return MZ_FWRITE(pBuf, 1, n, pZip->m_pState->m_pFile); } mz_bool mz_zip_writer_init_file(mz_zip_archive *pZip, const char *pFilename, mz_uint64 size_to_reserve_at_beginning) { return mz_zip_writer_init_file_v2(pZip, pFilename, size_to_reserve_at_beginning, 0); } mz_bool mz_zip_writer_init_file_v2(mz_zip_archive *pZip, const char *pFilename, mz_uint64 size_to_reserve_at_beginning, mz_uint flags) { MZ_FILE *pFile; pZip->m_pWrite = mz_zip_file_write_func; pZip->m_pNeeds_keepalive = NULL; if (flags & MZ_ZIP_FLAG_WRITE_ALLOW_READING) pZip->m_pRead = mz_zip_file_read_func; pZip->m_pIO_opaque = pZip; if (!mz_zip_writer_init_v2(pZip, size_to_reserve_at_beginning, flags)) return MZ_FALSE; if (NULL == (pFile = MZ_FOPEN(pFilename, (flags & MZ_ZIP_FLAG_WRITE_ALLOW_READING) ? "w+b" : "wb"))) { mz_zip_writer_end(pZip); return mz_zip_set_error(pZip, MZ_ZIP_FILE_OPEN_FAILED); } pZip->m_pState->m_pFile = pFile; pZip->m_zip_type = MZ_ZIP_TYPE_FILE; if (size_to_reserve_at_beginning) { mz_uint64 cur_ofs = 0; char buf[4096]; MZ_CLEAR_ARR(buf); do { size_t n = (size_t)MZ_MIN(sizeof(buf), size_to_reserve_at_beginning); if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_ofs, buf, n) != n) { mz_zip_writer_end(pZip); return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); } cur_ofs += n; size_to_reserve_at_beginning -= n; } while (size_to_reserve_at_beginning); } return MZ_TRUE; } mz_bool mz_zip_writer_init_cfile(mz_zip_archive *pZip, MZ_FILE *pFile, mz_uint flags) { pZip->m_pWrite = mz_zip_file_write_func; pZip->m_pNeeds_keepalive = NULL; if (flags & MZ_ZIP_FLAG_WRITE_ALLOW_READING) pZip->m_pRead = mz_zip_file_read_func; pZip->m_pIO_opaque = pZip; if (!mz_zip_writer_init_v2(pZip, 0, flags)) return MZ_FALSE; pZip->m_pState->m_pFile = pFile; pZip->m_pState->m_file_archive_start_ofs = MZ_FTELL64(pZip->m_pState->m_pFile); pZip->m_zip_type = MZ_ZIP_TYPE_CFILE; return MZ_TRUE; } #endif /* #ifndef MINIZ_NO_STDIO */ mz_bool mz_zip_writer_init_from_reader_v2(mz_zip_archive *pZip, const char *pFilename, mz_uint flags) { mz_zip_internal_state *pState; if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_READING)) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); if (flags & MZ_ZIP_FLAG_WRITE_ZIP64) { /* We don't support converting a non-zip64 file to zip64 - this seems like more trouble than it's worth. (What about the existing 32-bit data descriptors that could follow the compressed data?) */ if (!pZip->m_pState->m_zip64) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); } /* No sense in trying to write to an archive that's already at the support max size */ if (pZip->m_pState->m_zip64) { if (pZip->m_total_files == MZ_UINT32_MAX) return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); } else { if (pZip->m_total_files == MZ_UINT16_MAX) return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); if ((pZip->m_archive_size + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + MZ_ZIP_LOCAL_DIR_HEADER_SIZE) > MZ_UINT32_MAX) return mz_zip_set_error(pZip, MZ_ZIP_FILE_TOO_LARGE); } pState = pZip->m_pState; if (pState->m_pFile) { #ifdef MINIZ_NO_STDIO (void)pFilename; return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); #else if (pZip->m_pIO_opaque != pZip) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); if (pZip->m_zip_type == MZ_ZIP_TYPE_FILE) { if (!pFilename) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); /* Archive is being read from stdio and was originally opened only for reading. Try to reopen as writable. */ if (NULL == (pState->m_pFile = MZ_FREOPEN(pFilename, "r+b", pState->m_pFile))) { /* The mz_zip_archive is now in a bogus state because pState->m_pFile is NULL, so just close it. */ mz_zip_reader_end_internal(pZip, MZ_FALSE); return mz_zip_set_error(pZip, MZ_ZIP_FILE_OPEN_FAILED); } } pZip->m_pWrite = mz_zip_file_write_func; pZip->m_pNeeds_keepalive = NULL; #endif /* #ifdef MINIZ_NO_STDIO */ } else if (pState->m_pMem) { /* Archive lives in a memory block. Assume it's from the heap that we can resize using the realloc callback. */ if (pZip->m_pIO_opaque != pZip) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); pState->m_mem_capacity = pState->m_mem_size; pZip->m_pWrite = mz_zip_heap_write_func; pZip->m_pNeeds_keepalive = NULL; } /* Archive is being read via a user provided read function - make sure the user has specified a write function too. */ else if (!pZip->m_pWrite) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); /* Start writing new files at the archive's current central directory location. */ /* TODO: We could add a flag that lets the user start writing immediately AFTER the existing central dir - this would be safer. */ pZip->m_archive_size = pZip->m_central_directory_file_ofs; pZip->m_central_directory_file_ofs = 0; /* Clear the sorted central dir offsets, they aren't useful or maintained now. */ /* Even though we're now in write mode, files can still be extracted and verified, but file locates will be slow. */ /* TODO: We could easily maintain the sorted central directory offsets. */ mz_zip_array_clear(pZip, &pZip->m_pState->m_sorted_central_dir_offsets); pZip->m_zip_mode = MZ_ZIP_MODE_WRITING; return MZ_TRUE; } mz_bool mz_zip_writer_init_from_reader(mz_zip_archive *pZip, const char *pFilename) { return mz_zip_writer_init_from_reader_v2(pZip, pFilename, 0); } /* TODO: pArchive_name is a terrible name here! */ mz_bool mz_zip_writer_add_mem(mz_zip_archive *pZip, const char *pArchive_name, const void *pBuf, size_t buf_size, mz_uint level_and_flags) { return mz_zip_writer_add_mem_ex(pZip, pArchive_name, pBuf, buf_size, NULL, 0, level_and_flags, 0, 0); } typedef struct { mz_zip_archive *m_pZip; mz_uint64 m_cur_archive_file_ofs; mz_uint64 m_comp_size; } mz_zip_writer_add_state; static mz_bool mz_zip_writer_add_put_buf_callback(const void *pBuf, int len, void *pUser) { mz_zip_writer_add_state *pState = (mz_zip_writer_add_state *)pUser; if ((int)pState->m_pZip->m_pWrite(pState->m_pZip->m_pIO_opaque, pState->m_cur_archive_file_ofs, pBuf, len) != len) return MZ_FALSE; pState->m_cur_archive_file_ofs += len; pState->m_comp_size += len; return MZ_TRUE; } #define MZ_ZIP64_MAX_LOCAL_EXTRA_FIELD_SIZE (sizeof(mz_uint16) * 2 + sizeof(mz_uint64) * 2) #define MZ_ZIP64_MAX_CENTRAL_EXTRA_FIELD_SIZE (sizeof(mz_uint16) * 2 + sizeof(mz_uint64) * 3) static mz_uint32 mz_zip_writer_create_zip64_extra_data(mz_uint8 *pBuf, mz_uint64 *pUncomp_size, mz_uint64 *pComp_size, mz_uint64 *pLocal_header_ofs) { mz_uint8 *pDst = pBuf; mz_uint32 field_size = 0; MZ_WRITE_LE16(pDst + 0, MZ_ZIP64_EXTENDED_INFORMATION_FIELD_HEADER_ID); MZ_WRITE_LE16(pDst + 2, 0); pDst += sizeof(mz_uint16) * 2; if (pUncomp_size) { MZ_WRITE_LE64(pDst, *pUncomp_size); pDst += sizeof(mz_uint64); field_size += sizeof(mz_uint64); } if (pComp_size) { MZ_WRITE_LE64(pDst, *pComp_size); pDst += sizeof(mz_uint64); field_size += sizeof(mz_uint64); } if (pLocal_header_ofs) { MZ_WRITE_LE64(pDst, *pLocal_header_ofs); pDst += sizeof(mz_uint64); field_size += sizeof(mz_uint64); } MZ_WRITE_LE16(pBuf + 2, field_size); return (mz_uint32)(pDst - pBuf); } static mz_bool mz_zip_writer_create_local_dir_header(mz_zip_archive *pZip, mz_uint8 *pDst, mz_uint16 filename_size, mz_uint16 extra_size, mz_uint64 uncomp_size, mz_uint64 comp_size, mz_uint32 uncomp_crc32, mz_uint16 method, mz_uint16 bit_flags, mz_uint16 dos_time, mz_uint16 dos_date) { (void)pZip; memset(pDst, 0, MZ_ZIP_LOCAL_DIR_HEADER_SIZE); MZ_WRITE_LE32(pDst + MZ_ZIP_LDH_SIG_OFS, MZ_ZIP_LOCAL_DIR_HEADER_SIG); MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_VERSION_NEEDED_OFS, method ? 20 : 0); MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_BIT_FLAG_OFS, bit_flags); MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_METHOD_OFS, method); MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_FILE_TIME_OFS, dos_time); MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_FILE_DATE_OFS, dos_date); MZ_WRITE_LE32(pDst + MZ_ZIP_LDH_CRC32_OFS, uncomp_crc32); MZ_WRITE_LE32(pDst + MZ_ZIP_LDH_COMPRESSED_SIZE_OFS, MZ_MIN(comp_size, MZ_UINT32_MAX)); MZ_WRITE_LE32(pDst + MZ_ZIP_LDH_DECOMPRESSED_SIZE_OFS, MZ_MIN(uncomp_size, MZ_UINT32_MAX)); MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_FILENAME_LEN_OFS, filename_size); MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_EXTRA_LEN_OFS, extra_size); return MZ_TRUE; } static mz_bool mz_zip_writer_create_central_dir_header(mz_zip_archive *pZip, mz_uint8 *pDst, mz_uint16 filename_size, mz_uint16 extra_size, mz_uint16 comment_size, mz_uint64 uncomp_size, mz_uint64 comp_size, mz_uint32 uncomp_crc32, mz_uint16 method, mz_uint16 bit_flags, mz_uint16 dos_time, mz_uint16 dos_date, mz_uint64 local_header_ofs, mz_uint32 ext_attributes) { (void)pZip; memset(pDst, 0, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE); MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_SIG_OFS, MZ_ZIP_CENTRAL_DIR_HEADER_SIG); MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_VERSION_NEEDED_OFS, method ? 20 : 0); MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_BIT_FLAG_OFS, bit_flags); MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_METHOD_OFS, method); MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_FILE_TIME_OFS, dos_time); MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_FILE_DATE_OFS, dos_date); MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_CRC32_OFS, uncomp_crc32); MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS, MZ_MIN(comp_size, MZ_UINT32_MAX)); MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS, MZ_MIN(uncomp_size, MZ_UINT32_MAX)); MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_FILENAME_LEN_OFS, filename_size); MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_EXTRA_LEN_OFS, extra_size); MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_COMMENT_LEN_OFS, comment_size); MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_EXTERNAL_ATTR_OFS, ext_attributes); MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_LOCAL_HEADER_OFS, MZ_MIN(local_header_ofs, MZ_UINT32_MAX)); return MZ_TRUE; } static mz_bool mz_zip_writer_add_to_central_dir(mz_zip_archive *pZip, const char *pFilename, mz_uint16 filename_size, const void *pExtra, mz_uint16 extra_size, const void *pComment, mz_uint16 comment_size, mz_uint64 uncomp_size, mz_uint64 comp_size, mz_uint32 uncomp_crc32, mz_uint16 method, mz_uint16 bit_flags, mz_uint16 dos_time, mz_uint16 dos_date, mz_uint64 local_header_ofs, mz_uint32 ext_attributes, const char *user_extra_data, mz_uint user_extra_data_len) { mz_zip_internal_state *pState = pZip->m_pState; mz_uint32 central_dir_ofs = (mz_uint32)pState->m_central_dir.m_size; size_t orig_central_dir_size = pState->m_central_dir.m_size; mz_uint8 central_dir_header[MZ_ZIP_CENTRAL_DIR_HEADER_SIZE]; if (!pZip->m_pState->m_zip64) { if (local_header_ofs > 0xFFFFFFFF) return mz_zip_set_error(pZip, MZ_ZIP_FILE_TOO_LARGE); } /* miniz doesn't support central dirs >= MZ_UINT32_MAX bytes yet */ if (((mz_uint64)pState->m_central_dir.m_size + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + filename_size + extra_size + user_extra_data_len + comment_size) >= MZ_UINT32_MAX) return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_CDIR_SIZE); if (!mz_zip_writer_create_central_dir_header(pZip, central_dir_header, filename_size, (mz_uint16)(extra_size + user_extra_data_len), comment_size, uncomp_size, comp_size, uncomp_crc32, method, bit_flags, dos_time, dos_date, local_header_ofs, ext_attributes)) return mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR); if ((!mz_zip_array_push_back(pZip, &pState->m_central_dir, central_dir_header, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE)) || (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pFilename, filename_size)) || (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pExtra, extra_size)) || (!mz_zip_array_push_back(pZip, &pState->m_central_dir, user_extra_data, user_extra_data_len)) || (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pComment, comment_size)) || (!mz_zip_array_push_back(pZip, &pState->m_central_dir_offsets, ¢ral_dir_ofs, 1))) { /* Try to resize the central directory array back into its original state. */ mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, MZ_FALSE); return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); } return MZ_TRUE; } static mz_bool mz_zip_writer_validate_archive_name(const char *pArchive_name) { /* Basic ZIP archive filename validity checks: Valid filenames cannot start with a forward slash, cannot contain a drive letter, and cannot use DOS-style backward slashes. */ if (*pArchive_name == '/') return MZ_FALSE; /* Making sure the name does not contain drive letters or DOS style backward slashes is the responsibility of the program using miniz*/ return MZ_TRUE; } static mz_uint mz_zip_writer_compute_padding_needed_for_file_alignment(mz_zip_archive *pZip) { mz_uint32 n; if (!pZip->m_file_offset_alignment) return 0; n = (mz_uint32)(pZip->m_archive_size & (pZip->m_file_offset_alignment - 1)); return (mz_uint)((pZip->m_file_offset_alignment - n) & (pZip->m_file_offset_alignment - 1)); } static mz_bool mz_zip_writer_write_zeros(mz_zip_archive *pZip, mz_uint64 cur_file_ofs, mz_uint32 n) { char buf[4096]; memset(buf, 0, MZ_MIN(sizeof(buf), n)); while (n) { mz_uint32 s = MZ_MIN(sizeof(buf), n); if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_file_ofs, buf, s) != s) return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); cur_file_ofs += s; n -= s; } return MZ_TRUE; } mz_bool mz_zip_writer_add_mem_ex(mz_zip_archive *pZip, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags, mz_uint64 uncomp_size, mz_uint32 uncomp_crc32) { return mz_zip_writer_add_mem_ex_v2(pZip, pArchive_name, pBuf, buf_size, pComment, comment_size, level_and_flags, uncomp_size, uncomp_crc32, NULL, NULL, 0, NULL, 0); } mz_bool mz_zip_writer_add_mem_ex_v2(mz_zip_archive *pZip, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags, mz_uint64 uncomp_size, mz_uint32 uncomp_crc32, MZ_TIME_T *last_modified, const char *user_extra_data, mz_uint user_extra_data_len, const char *user_extra_data_central, mz_uint user_extra_data_central_len) { mz_uint16 method = 0, dos_time = 0, dos_date = 0; mz_uint level, ext_attributes = 0, num_alignment_padding_bytes; mz_uint64 local_dir_header_ofs = pZip->m_archive_size, cur_archive_file_ofs = pZip->m_archive_size, comp_size = 0; size_t archive_name_size; mz_uint8 local_dir_header[MZ_ZIP_LOCAL_DIR_HEADER_SIZE]; tdefl_compressor *pComp = NULL; mz_bool store_data_uncompressed; mz_zip_internal_state *pState; mz_uint8 *pExtra_data = NULL; mz_uint32 extra_size = 0; mz_uint8 extra_data[MZ_ZIP64_MAX_CENTRAL_EXTRA_FIELD_SIZE]; mz_uint16 bit_flags = 0; if ((int)level_and_flags < 0) level_and_flags = MZ_DEFAULT_LEVEL; if (uncomp_size || (buf_size && !(level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA))) bit_flags |= MZ_ZIP_LDH_BIT_FLAG_HAS_LOCATOR; if (!(level_and_flags & MZ_ZIP_FLAG_ASCII_FILENAME)) bit_flags |= MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_UTF8; level = level_and_flags & 0xF; store_data_uncompressed = ((!level) || (level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA)); if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING) || ((buf_size) && (!pBuf)) || (!pArchive_name) || ((comment_size) && (!pComment)) || (level > MZ_UBER_COMPRESSION)) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); pState = pZip->m_pState; if (pState->m_zip64) { if (pZip->m_total_files == MZ_UINT32_MAX) return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); } else { if (pZip->m_total_files == MZ_UINT16_MAX) { pState->m_zip64 = MZ_TRUE; /*return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); */ } if (((mz_uint64)buf_size > 0xFFFFFFFF) || (uncomp_size > 0xFFFFFFFF)) { pState->m_zip64 = MZ_TRUE; /*return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); */ } } if ((!(level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) && (uncomp_size)) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); if (!mz_zip_writer_validate_archive_name(pArchive_name)) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_FILENAME); #ifndef MINIZ_NO_TIME if (last_modified != NULL) { mz_zip_time_t_to_dos_time(*last_modified, &dos_time, &dos_date); } else { MZ_TIME_T cur_time; time(&cur_time); mz_zip_time_t_to_dos_time(cur_time, &dos_time, &dos_date); } #endif /* #ifndef MINIZ_NO_TIME */ if (!(level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) { uncomp_crc32 = (mz_uint32)mz_crc32(MZ_CRC32_INIT, (const mz_uint8 *)pBuf, buf_size); uncomp_size = buf_size; if (uncomp_size <= 3) { level = 0; store_data_uncompressed = MZ_TRUE; } } archive_name_size = strlen(pArchive_name); if (archive_name_size > MZ_UINT16_MAX) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_FILENAME); num_alignment_padding_bytes = mz_zip_writer_compute_padding_needed_for_file_alignment(pZip); /* miniz doesn't support central dirs >= MZ_UINT32_MAX bytes yet */ if (((mz_uint64)pState->m_central_dir.m_size + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + archive_name_size + MZ_ZIP64_MAX_CENTRAL_EXTRA_FIELD_SIZE + comment_size) >= MZ_UINT32_MAX) return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_CDIR_SIZE); if (!pState->m_zip64) { /* Bail early if the archive would obviously become too large */ if ((pZip->m_archive_size + num_alignment_padding_bytes + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + archive_name_size + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + archive_name_size + comment_size + user_extra_data_len + pState->m_central_dir.m_size + MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE + user_extra_data_central_len + MZ_ZIP_DATA_DESCRIPTER_SIZE32) > 0xFFFFFFFF) { pState->m_zip64 = MZ_TRUE; /*return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); */ } } if ((archive_name_size) && (pArchive_name[archive_name_size - 1] == '/')) { /* Set DOS Subdirectory attribute bit. */ ext_attributes |= MZ_ZIP_DOS_DIR_ATTRIBUTE_BITFLAG; /* Subdirectories cannot contain data. */ if ((buf_size) || (uncomp_size)) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); } /* Try to do any allocations before writing to the archive, so if an allocation fails the file remains unmodified. (A good idea if we're doing an in-place modification.) */ if ((!mz_zip_array_ensure_room(pZip, &pState->m_central_dir, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + archive_name_size + comment_size + (pState->m_zip64 ? MZ_ZIP64_MAX_CENTRAL_EXTRA_FIELD_SIZE : 0))) || (!mz_zip_array_ensure_room(pZip, &pState->m_central_dir_offsets, 1))) return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); if ((!store_data_uncompressed) && (buf_size)) { if (NULL == (pComp = (tdefl_compressor *)pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, sizeof(tdefl_compressor)))) return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); } if (!mz_zip_writer_write_zeros(pZip, cur_archive_file_ofs, num_alignment_padding_bytes)) { pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); return MZ_FALSE; } local_dir_header_ofs += num_alignment_padding_bytes; if (pZip->m_file_offset_alignment) { MZ_ASSERT((local_dir_header_ofs & (pZip->m_file_offset_alignment - 1)) == 0); } cur_archive_file_ofs += num_alignment_padding_bytes; MZ_CLEAR_ARR(local_dir_header); if (!store_data_uncompressed || (level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) { method = MZ_DEFLATED; } if (pState->m_zip64) { if (uncomp_size >= MZ_UINT32_MAX || local_dir_header_ofs >= MZ_UINT32_MAX) { pExtra_data = extra_data; extra_size = mz_zip_writer_create_zip64_extra_data(extra_data, (uncomp_size >= MZ_UINT32_MAX) ? &uncomp_size : NULL, (uncomp_size >= MZ_UINT32_MAX) ? &comp_size : NULL, (local_dir_header_ofs >= MZ_UINT32_MAX) ? &local_dir_header_ofs : NULL); } if (!mz_zip_writer_create_local_dir_header(pZip, local_dir_header, (mz_uint16)archive_name_size, (mz_uint16)(extra_size + user_extra_data_len), 0, 0, 0, method, bit_flags, dos_time, dos_date)) return mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR); if (pZip->m_pWrite(pZip->m_pIO_opaque, local_dir_header_ofs, local_dir_header, sizeof(local_dir_header)) != sizeof(local_dir_header)) return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); cur_archive_file_ofs += sizeof(local_dir_header); if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pArchive_name, archive_name_size) != archive_name_size) { pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); } cur_archive_file_ofs += archive_name_size; if (pExtra_data != NULL) { if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, extra_data, extra_size) != extra_size) return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); cur_archive_file_ofs += extra_size; } } else { if ((comp_size > MZ_UINT32_MAX) || (cur_archive_file_ofs > MZ_UINT32_MAX)) return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); if (!mz_zip_writer_create_local_dir_header(pZip, local_dir_header, (mz_uint16)archive_name_size, (mz_uint16)user_extra_data_len, 0, 0, 0, method, bit_flags, dos_time, dos_date)) return mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR); if (pZip->m_pWrite(pZip->m_pIO_opaque, local_dir_header_ofs, local_dir_header, sizeof(local_dir_header)) != sizeof(local_dir_header)) return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); cur_archive_file_ofs += sizeof(local_dir_header); if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pArchive_name, archive_name_size) != archive_name_size) { pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); } cur_archive_file_ofs += archive_name_size; } if (user_extra_data_len > 0) { if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, user_extra_data, user_extra_data_len) != user_extra_data_len) return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); cur_archive_file_ofs += user_extra_data_len; } if (store_data_uncompressed) { if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pBuf, buf_size) != buf_size) { pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); } cur_archive_file_ofs += buf_size; comp_size = buf_size; } else if (buf_size) { mz_zip_writer_add_state state; state.m_pZip = pZip; state.m_cur_archive_file_ofs = cur_archive_file_ofs; state.m_comp_size = 0; if ((tdefl_init(pComp, mz_zip_writer_add_put_buf_callback, &state, tdefl_create_comp_flags_from_zip_params(level, -15, MZ_DEFAULT_STRATEGY)) != TDEFL_STATUS_OKAY) || (tdefl_compress_buffer(pComp, pBuf, buf_size, TDEFL_FINISH) != TDEFL_STATUS_DONE)) { pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); return mz_zip_set_error(pZip, MZ_ZIP_COMPRESSION_FAILED); } comp_size = state.m_comp_size; cur_archive_file_ofs = state.m_cur_archive_file_ofs; } pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); pComp = NULL; if (uncomp_size) { mz_uint8 local_dir_footer[MZ_ZIP_DATA_DESCRIPTER_SIZE64]; mz_uint32 local_dir_footer_size = MZ_ZIP_DATA_DESCRIPTER_SIZE32; MZ_ASSERT(bit_flags & MZ_ZIP_LDH_BIT_FLAG_HAS_LOCATOR); MZ_WRITE_LE32(local_dir_footer + 0, MZ_ZIP_DATA_DESCRIPTOR_ID); MZ_WRITE_LE32(local_dir_footer + 4, uncomp_crc32); if (pExtra_data == NULL) { if (comp_size > MZ_UINT32_MAX) return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); MZ_WRITE_LE32(local_dir_footer + 8, comp_size); MZ_WRITE_LE32(local_dir_footer + 12, uncomp_size); } else { MZ_WRITE_LE64(local_dir_footer + 8, comp_size); MZ_WRITE_LE64(local_dir_footer + 16, uncomp_size); local_dir_footer_size = MZ_ZIP_DATA_DESCRIPTER_SIZE64; } if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, local_dir_footer, local_dir_footer_size) != local_dir_footer_size) return MZ_FALSE; cur_archive_file_ofs += local_dir_footer_size; } if (pExtra_data != NULL) { extra_size = mz_zip_writer_create_zip64_extra_data(extra_data, (uncomp_size >= MZ_UINT32_MAX) ? &uncomp_size : NULL, (uncomp_size >= MZ_UINT32_MAX) ? &comp_size : NULL, (local_dir_header_ofs >= MZ_UINT32_MAX) ? &local_dir_header_ofs : NULL); } if (!mz_zip_writer_add_to_central_dir(pZip, pArchive_name, (mz_uint16)archive_name_size, pExtra_data, (mz_uint16)extra_size, pComment, comment_size, uncomp_size, comp_size, uncomp_crc32, method, bit_flags, dos_time, dos_date, local_dir_header_ofs, ext_attributes, user_extra_data_central, user_extra_data_central_len)) return MZ_FALSE; pZip->m_total_files++; pZip->m_archive_size = cur_archive_file_ofs; return MZ_TRUE; } mz_bool mz_zip_writer_add_read_buf_callback(mz_zip_archive *pZip, const char *pArchive_name, mz_file_read_func read_callback, void* callback_opaque, mz_uint64 max_size, const MZ_TIME_T *pFile_time, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags, const char *user_extra_data, mz_uint user_extra_data_len, const char *user_extra_data_central, mz_uint user_extra_data_central_len) { mz_uint16 gen_flags; mz_uint uncomp_crc32 = MZ_CRC32_INIT, level, num_alignment_padding_bytes; mz_uint16 method = 0, dos_time = 0, dos_date = 0, ext_attributes = 0; mz_uint64 local_dir_header_ofs, cur_archive_file_ofs = pZip->m_archive_size, uncomp_size = 0, comp_size = 0; size_t archive_name_size; mz_uint8 local_dir_header[MZ_ZIP_LOCAL_DIR_HEADER_SIZE]; mz_uint8 *pExtra_data = NULL; mz_uint32 extra_size = 0; mz_uint8 extra_data[MZ_ZIP64_MAX_CENTRAL_EXTRA_FIELD_SIZE]; mz_zip_internal_state *pState; mz_uint64 file_ofs = 0, cur_archive_header_file_ofs; if ((int)level_and_flags < 0) level_and_flags = MZ_DEFAULT_LEVEL; level = level_and_flags & 0xF; gen_flags = (level_and_flags & MZ_ZIP_FLAG_WRITE_HEADER_SET_SIZE) ? 0 : MZ_ZIP_LDH_BIT_FLAG_HAS_LOCATOR; if (!(level_and_flags & MZ_ZIP_FLAG_ASCII_FILENAME)) gen_flags |= MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_UTF8; /* Sanity checks */ if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING) || (!pArchive_name) || ((comment_size) && (!pComment)) || (level > MZ_UBER_COMPRESSION)) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); pState = pZip->m_pState; if ((!pState->m_zip64) && (max_size > MZ_UINT32_MAX)) { /* Source file is too large for non-zip64 */ /*return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); */ pState->m_zip64 = MZ_TRUE; } /* We could support this, but why? */ if (level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); if (!mz_zip_writer_validate_archive_name(pArchive_name)) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_FILENAME); if (pState->m_zip64) { if (pZip->m_total_files == MZ_UINT32_MAX) return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); } else { if (pZip->m_total_files == MZ_UINT16_MAX) { pState->m_zip64 = MZ_TRUE; /*return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); */ } } archive_name_size = strlen(pArchive_name); if (archive_name_size > MZ_UINT16_MAX) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_FILENAME); num_alignment_padding_bytes = mz_zip_writer_compute_padding_needed_for_file_alignment(pZip); /* miniz doesn't support central dirs >= MZ_UINT32_MAX bytes yet */ if (((mz_uint64)pState->m_central_dir.m_size + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + archive_name_size + MZ_ZIP64_MAX_CENTRAL_EXTRA_FIELD_SIZE + comment_size) >= MZ_UINT32_MAX) return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_CDIR_SIZE); if (!pState->m_zip64) { /* Bail early if the archive would obviously become too large */ if ((pZip->m_archive_size + num_alignment_padding_bytes + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + archive_name_size + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + archive_name_size + comment_size + user_extra_data_len + pState->m_central_dir.m_size + MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE + 1024 + MZ_ZIP_DATA_DESCRIPTER_SIZE32 + user_extra_data_central_len) > 0xFFFFFFFF) { pState->m_zip64 = MZ_TRUE; /*return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); */ } } #ifndef MINIZ_NO_TIME if (pFile_time) { mz_zip_time_t_to_dos_time(*pFile_time, &dos_time, &dos_date); } #endif if (max_size <= 3) level = 0; if (!mz_zip_writer_write_zeros(pZip, cur_archive_file_ofs, num_alignment_padding_bytes)) { return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); } cur_archive_file_ofs += num_alignment_padding_bytes; local_dir_header_ofs = cur_archive_file_ofs; if (pZip->m_file_offset_alignment) { MZ_ASSERT((cur_archive_file_ofs & (pZip->m_file_offset_alignment - 1)) == 0); } if (max_size && level) { method = MZ_DEFLATED; } MZ_CLEAR_ARR(local_dir_header); if (pState->m_zip64) { if (max_size >= MZ_UINT32_MAX || local_dir_header_ofs >= MZ_UINT32_MAX) { pExtra_data = extra_data; if (level_and_flags & MZ_ZIP_FLAG_WRITE_HEADER_SET_SIZE) extra_size = mz_zip_writer_create_zip64_extra_data(extra_data, (max_size >= MZ_UINT32_MAX) ? &uncomp_size : NULL, (max_size >= MZ_UINT32_MAX) ? &comp_size : NULL, (local_dir_header_ofs >= MZ_UINT32_MAX) ? &local_dir_header_ofs : NULL); else extra_size = mz_zip_writer_create_zip64_extra_data(extra_data, NULL, NULL, (local_dir_header_ofs >= MZ_UINT32_MAX) ? &local_dir_header_ofs : NULL); } if (!mz_zip_writer_create_local_dir_header(pZip, local_dir_header, (mz_uint16)archive_name_size, (mz_uint16)(extra_size + user_extra_data_len), 0, 0, 0, method, gen_flags, dos_time, dos_date)) return mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR); if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, local_dir_header, sizeof(local_dir_header)) != sizeof(local_dir_header)) return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); cur_archive_file_ofs += sizeof(local_dir_header); if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pArchive_name, archive_name_size) != archive_name_size) { return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); } cur_archive_file_ofs += archive_name_size; if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, extra_data, extra_size) != extra_size) return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); cur_archive_file_ofs += extra_size; } else { if ((comp_size > MZ_UINT32_MAX) || (cur_archive_file_ofs > MZ_UINT32_MAX)) return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); if (!mz_zip_writer_create_local_dir_header(pZip, local_dir_header, (mz_uint16)archive_name_size, (mz_uint16)user_extra_data_len, 0, 0, 0, method, gen_flags, dos_time, dos_date)) return mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR); if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, local_dir_header, sizeof(local_dir_header)) != sizeof(local_dir_header)) return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); cur_archive_file_ofs += sizeof(local_dir_header); if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pArchive_name, archive_name_size) != archive_name_size) { return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); } cur_archive_file_ofs += archive_name_size; } if (user_extra_data_len > 0) { if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, user_extra_data, user_extra_data_len) != user_extra_data_len) return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); cur_archive_file_ofs += user_extra_data_len; } if (max_size) { void *pRead_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, MZ_ZIP_MAX_IO_BUF_SIZE); if (!pRead_buf) { return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); } if (!level) { while (1) { size_t n = read_callback(callback_opaque, file_ofs, pRead_buf, MZ_ZIP_MAX_IO_BUF_SIZE); if (n == 0) break; if ((n > MZ_ZIP_MAX_IO_BUF_SIZE) || (file_ofs + n > max_size)) { pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); } if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pRead_buf, n) != n) { pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); } file_ofs += n; uncomp_crc32 = (mz_uint32)mz_crc32(uncomp_crc32, (const mz_uint8 *)pRead_buf, n); cur_archive_file_ofs += n; } uncomp_size = file_ofs; comp_size = uncomp_size; } else { mz_bool result = MZ_FALSE; mz_zip_writer_add_state state; tdefl_compressor *pComp = (tdefl_compressor *)pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, sizeof(tdefl_compressor)); if (!pComp) { pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); } state.m_pZip = pZip; state.m_cur_archive_file_ofs = cur_archive_file_ofs; state.m_comp_size = 0; if (tdefl_init(pComp, mz_zip_writer_add_put_buf_callback, &state, tdefl_create_comp_flags_from_zip_params(level, -15, MZ_DEFAULT_STRATEGY)) != TDEFL_STATUS_OKAY) { pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); return mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR); } for (;;) { tdefl_status status; tdefl_flush flush = TDEFL_NO_FLUSH; size_t n = read_callback(callback_opaque, file_ofs, pRead_buf, MZ_ZIP_MAX_IO_BUF_SIZE); if ((n > MZ_ZIP_MAX_IO_BUF_SIZE) || (file_ofs + n > max_size)) { mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); break; } file_ofs += n; uncomp_crc32 = (mz_uint32)mz_crc32(uncomp_crc32, (const mz_uint8 *)pRead_buf, n); if (pZip->m_pNeeds_keepalive != NULL && pZip->m_pNeeds_keepalive(pZip->m_pIO_opaque)) flush = TDEFL_FULL_FLUSH; if (n == 0) flush = TDEFL_FINISH; status = tdefl_compress_buffer(pComp, pRead_buf, n, flush); if (status == TDEFL_STATUS_DONE) { result = MZ_TRUE; break; } else if (status != TDEFL_STATUS_OKAY) { mz_zip_set_error(pZip, MZ_ZIP_COMPRESSION_FAILED); break; } } pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); if (!result) { pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); return MZ_FALSE; } uncomp_size = file_ofs; comp_size = state.m_comp_size; cur_archive_file_ofs = state.m_cur_archive_file_ofs; } pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); } if (!(level_and_flags & MZ_ZIP_FLAG_WRITE_HEADER_SET_SIZE)) { mz_uint8 local_dir_footer[MZ_ZIP_DATA_DESCRIPTER_SIZE64]; mz_uint32 local_dir_footer_size = MZ_ZIP_DATA_DESCRIPTER_SIZE32; MZ_WRITE_LE32(local_dir_footer + 0, MZ_ZIP_DATA_DESCRIPTOR_ID); MZ_WRITE_LE32(local_dir_footer + 4, uncomp_crc32); if (pExtra_data == NULL) { if (comp_size > MZ_UINT32_MAX) return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); MZ_WRITE_LE32(local_dir_footer + 8, comp_size); MZ_WRITE_LE32(local_dir_footer + 12, uncomp_size); } else { MZ_WRITE_LE64(local_dir_footer + 8, comp_size); MZ_WRITE_LE64(local_dir_footer + 16, uncomp_size); local_dir_footer_size = MZ_ZIP_DATA_DESCRIPTER_SIZE64; } if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, local_dir_footer, local_dir_footer_size) != local_dir_footer_size) return MZ_FALSE; cur_archive_file_ofs += local_dir_footer_size; } if (level_and_flags & MZ_ZIP_FLAG_WRITE_HEADER_SET_SIZE) { if (pExtra_data != NULL) { extra_size = mz_zip_writer_create_zip64_extra_data(extra_data, (max_size >= MZ_UINT32_MAX) ? &uncomp_size : NULL, (max_size >= MZ_UINT32_MAX) ? &comp_size : NULL, (local_dir_header_ofs >= MZ_UINT32_MAX) ? &local_dir_header_ofs : NULL); } if (!mz_zip_writer_create_local_dir_header(pZip, local_dir_header, (mz_uint16)archive_name_size, (mz_uint16)(extra_size + user_extra_data_len), (max_size >= MZ_UINT32_MAX) ? MZ_UINT32_MAX : uncomp_size, (max_size >= MZ_UINT32_MAX) ? MZ_UINT32_MAX : comp_size, uncomp_crc32, method, gen_flags, dos_time, dos_date)) return mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR); cur_archive_header_file_ofs = local_dir_header_ofs; if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_header_file_ofs, local_dir_header, sizeof(local_dir_header)) != sizeof(local_dir_header)) return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); if (pExtra_data != NULL) { cur_archive_header_file_ofs += sizeof(local_dir_header); if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_header_file_ofs, pArchive_name, archive_name_size) != archive_name_size) { return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); } cur_archive_header_file_ofs += archive_name_size; if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_header_file_ofs, extra_data, extra_size) != extra_size) return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); cur_archive_header_file_ofs += extra_size; } } if (pExtra_data != NULL) { extra_size = mz_zip_writer_create_zip64_extra_data(extra_data, (uncomp_size >= MZ_UINT32_MAX) ? &uncomp_size : NULL, (uncomp_size >= MZ_UINT32_MAX) ? &comp_size : NULL, (local_dir_header_ofs >= MZ_UINT32_MAX) ? &local_dir_header_ofs : NULL); } if (!mz_zip_writer_add_to_central_dir(pZip, pArchive_name, (mz_uint16)archive_name_size, pExtra_data, (mz_uint16)extra_size, pComment, comment_size, uncomp_size, comp_size, uncomp_crc32, method, gen_flags, dos_time, dos_date, local_dir_header_ofs, ext_attributes, user_extra_data_central, user_extra_data_central_len)) return MZ_FALSE; pZip->m_total_files++; pZip->m_archive_size = cur_archive_file_ofs; return MZ_TRUE; } #ifndef MINIZ_NO_STDIO static size_t mz_file_read_func_stdio(void *pOpaque, mz_uint64 file_ofs, void *pBuf, size_t n) { MZ_FILE *pSrc_file = (MZ_FILE *)pOpaque; mz_int64 cur_ofs = MZ_FTELL64(pSrc_file); if (((mz_int64)file_ofs < 0) || (((cur_ofs != (mz_int64)file_ofs)) && (MZ_FSEEK64(pSrc_file, (mz_int64)file_ofs, SEEK_SET)))) return 0; return MZ_FREAD(pBuf, 1, n, pSrc_file); } mz_bool mz_zip_writer_add_cfile(mz_zip_archive *pZip, const char *pArchive_name, MZ_FILE *pSrc_file, mz_uint64 max_size, const MZ_TIME_T *pFile_time, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags, const char *user_extra_data, mz_uint user_extra_data_len, const char *user_extra_data_central, mz_uint user_extra_data_central_len) { return mz_zip_writer_add_read_buf_callback(pZip, pArchive_name, mz_file_read_func_stdio, pSrc_file, max_size, pFile_time, pComment, comment_size, level_and_flags, user_extra_data, user_extra_data_len, user_extra_data_central, user_extra_data_central_len); } mz_bool mz_zip_writer_add_file(mz_zip_archive *pZip, const char *pArchive_name, const char *pSrc_filename, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags) { MZ_FILE *pSrc_file = NULL; mz_uint64 uncomp_size = 0; MZ_TIME_T file_modified_time; MZ_TIME_T *pFile_time = NULL; mz_bool status; memset(&file_modified_time, 0, sizeof(file_modified_time)); #if !defined(MINIZ_NO_TIME) && !defined(MINIZ_NO_STDIO) pFile_time = &file_modified_time; if (!mz_zip_get_file_modified_time(pSrc_filename, &file_modified_time)) return mz_zip_set_error(pZip, MZ_ZIP_FILE_STAT_FAILED); #endif pSrc_file = MZ_FOPEN(pSrc_filename, "rb"); if (!pSrc_file) return mz_zip_set_error(pZip, MZ_ZIP_FILE_OPEN_FAILED); MZ_FSEEK64(pSrc_file, 0, SEEK_END); uncomp_size = MZ_FTELL64(pSrc_file); MZ_FSEEK64(pSrc_file, 0, SEEK_SET); status = mz_zip_writer_add_cfile(pZip, pArchive_name, pSrc_file, uncomp_size, pFile_time, pComment, comment_size, level_and_flags, NULL, 0, NULL, 0); MZ_FCLOSE(pSrc_file); return status; } #endif /* #ifndef MINIZ_NO_STDIO */ static mz_bool mz_zip_writer_update_zip64_extension_block(mz_zip_array *pNew_ext, mz_zip_archive *pZip, const mz_uint8 *pExt, mz_uint32 ext_len, mz_uint64 *pComp_size, mz_uint64 *pUncomp_size, mz_uint64 *pLocal_header_ofs, mz_uint32 *pDisk_start) { /* + 64 should be enough for any new zip64 data */ if (!mz_zip_array_reserve(pZip, pNew_ext, ext_len + 64, MZ_FALSE)) return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); mz_zip_array_resize(pZip, pNew_ext, 0, MZ_FALSE); if ((pUncomp_size) || (pComp_size) || (pLocal_header_ofs) || (pDisk_start)) { mz_uint8 new_ext_block[64]; mz_uint8 *pDst = new_ext_block; mz_write_le16(pDst, MZ_ZIP64_EXTENDED_INFORMATION_FIELD_HEADER_ID); mz_write_le16(pDst + sizeof(mz_uint16), 0); pDst += sizeof(mz_uint16) * 2; if (pUncomp_size) { mz_write_le64(pDst, *pUncomp_size); pDst += sizeof(mz_uint64); } if (pComp_size) { mz_write_le64(pDst, *pComp_size); pDst += sizeof(mz_uint64); } if (pLocal_header_ofs) { mz_write_le64(pDst, *pLocal_header_ofs); pDst += sizeof(mz_uint64); } if (pDisk_start) { mz_write_le32(pDst, *pDisk_start); pDst += sizeof(mz_uint32); } mz_write_le16(new_ext_block + sizeof(mz_uint16), (mz_uint16)((pDst - new_ext_block) - sizeof(mz_uint16) * 2)); if (!mz_zip_array_push_back(pZip, pNew_ext, new_ext_block, pDst - new_ext_block)) return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); } if ((pExt) && (ext_len)) { mz_uint32 extra_size_remaining = ext_len; const mz_uint8 *pExtra_data = pExt; do { mz_uint32 field_id, field_data_size, field_total_size; if (extra_size_remaining < (sizeof(mz_uint16) * 2)) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); field_id = MZ_READ_LE16(pExtra_data); field_data_size = MZ_READ_LE16(pExtra_data + sizeof(mz_uint16)); field_total_size = field_data_size + sizeof(mz_uint16) * 2; if (field_total_size > extra_size_remaining) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); if (field_id != MZ_ZIP64_EXTENDED_INFORMATION_FIELD_HEADER_ID) { if (!mz_zip_array_push_back(pZip, pNew_ext, pExtra_data, field_total_size)) return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); } pExtra_data += field_total_size; extra_size_remaining -= field_total_size; } while (extra_size_remaining); } return MZ_TRUE; } /* TODO: This func is now pretty freakin complex due to zip64, split it up? */ mz_bool mz_zip_writer_add_from_zip_reader(mz_zip_archive *pZip, mz_zip_archive *pSource_zip, mz_uint src_file_index) { mz_uint n, bit_flags, num_alignment_padding_bytes, src_central_dir_following_data_size; mz_uint64 src_archive_bytes_remaining, local_dir_header_ofs; mz_uint64 cur_src_file_ofs, cur_dst_file_ofs; mz_uint32 local_header_u32[(MZ_ZIP_LOCAL_DIR_HEADER_SIZE + sizeof(mz_uint32) - 1) / sizeof(mz_uint32)]; mz_uint8 *pLocal_header = (mz_uint8 *)local_header_u32; mz_uint8 new_central_header[MZ_ZIP_CENTRAL_DIR_HEADER_SIZE]; size_t orig_central_dir_size; mz_zip_internal_state *pState; void *pBuf; const mz_uint8 *pSrc_central_header; mz_zip_archive_file_stat src_file_stat; mz_uint32 src_filename_len, src_comment_len, src_ext_len; mz_uint32 local_header_filename_size, local_header_extra_len; mz_uint64 local_header_comp_size, local_header_uncomp_size; mz_bool found_zip64_ext_data_in_ldir = MZ_FALSE; /* Sanity checks */ if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING) || (!pSource_zip->m_pRead)) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); pState = pZip->m_pState; /* Don't support copying files from zip64 archives to non-zip64, even though in some cases this is possible */ if ((pSource_zip->m_pState->m_zip64) && (!pZip->m_pState->m_zip64)) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); /* Get pointer to the source central dir header and crack it */ if (NULL == (pSrc_central_header = mz_zip_get_cdh(pSource_zip, src_file_index))) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); if (MZ_READ_LE32(pSrc_central_header + MZ_ZIP_CDH_SIG_OFS) != MZ_ZIP_CENTRAL_DIR_HEADER_SIG) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); src_filename_len = MZ_READ_LE16(pSrc_central_header + MZ_ZIP_CDH_FILENAME_LEN_OFS); src_comment_len = MZ_READ_LE16(pSrc_central_header + MZ_ZIP_CDH_COMMENT_LEN_OFS); src_ext_len = MZ_READ_LE16(pSrc_central_header + MZ_ZIP_CDH_EXTRA_LEN_OFS); src_central_dir_following_data_size = src_filename_len + src_ext_len + src_comment_len; /* TODO: We don't support central dir's >= MZ_UINT32_MAX bytes right now (+32 fudge factor in case we need to add more extra data) */ if ((pState->m_central_dir.m_size + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + src_central_dir_following_data_size + 32) >= MZ_UINT32_MAX) return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_CDIR_SIZE); num_alignment_padding_bytes = mz_zip_writer_compute_padding_needed_for_file_alignment(pZip); if (!pState->m_zip64) { if (pZip->m_total_files == MZ_UINT16_MAX) return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); } else { /* TODO: Our zip64 support still has some 32-bit limits that may not be worth fixing. */ if (pZip->m_total_files == MZ_UINT32_MAX) return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); } if (!mz_zip_file_stat_internal(pSource_zip, src_file_index, pSrc_central_header, &src_file_stat, NULL)) return MZ_FALSE; cur_src_file_ofs = src_file_stat.m_local_header_ofs; cur_dst_file_ofs = pZip->m_archive_size; /* Read the source archive's local dir header */ if (pSource_zip->m_pRead(pSource_zip->m_pIO_opaque, cur_src_file_ofs, pLocal_header, MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != MZ_ZIP_LOCAL_DIR_HEADER_SIZE) return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); if (MZ_READ_LE32(pLocal_header) != MZ_ZIP_LOCAL_DIR_HEADER_SIG) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); cur_src_file_ofs += MZ_ZIP_LOCAL_DIR_HEADER_SIZE; /* Compute the total size we need to copy (filename+extra data+compressed data) */ local_header_filename_size = MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_FILENAME_LEN_OFS); local_header_extra_len = MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_EXTRA_LEN_OFS); local_header_comp_size = MZ_READ_LE32(pLocal_header + MZ_ZIP_LDH_COMPRESSED_SIZE_OFS); local_header_uncomp_size = MZ_READ_LE32(pLocal_header + MZ_ZIP_LDH_DECOMPRESSED_SIZE_OFS); src_archive_bytes_remaining = local_header_filename_size + local_header_extra_len + src_file_stat.m_comp_size; /* Try to find a zip64 extended information field */ if ((local_header_extra_len) && ((local_header_comp_size == MZ_UINT32_MAX) || (local_header_uncomp_size == MZ_UINT32_MAX))) { mz_zip_array file_data_array; const mz_uint8 *pExtra_data; mz_uint32 extra_size_remaining = local_header_extra_len; mz_zip_array_init(&file_data_array, 1); if (!mz_zip_array_resize(pZip, &file_data_array, local_header_extra_len, MZ_FALSE)) { return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); } if (pSource_zip->m_pRead(pSource_zip->m_pIO_opaque, src_file_stat.m_local_header_ofs + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + local_header_filename_size, file_data_array.m_p, local_header_extra_len) != local_header_extra_len) { mz_zip_array_clear(pZip, &file_data_array); return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); } pExtra_data = (const mz_uint8 *)file_data_array.m_p; do { mz_uint32 field_id, field_data_size, field_total_size; if (extra_size_remaining < (sizeof(mz_uint16) * 2)) { mz_zip_array_clear(pZip, &file_data_array); return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); } field_id = MZ_READ_LE16(pExtra_data); field_data_size = MZ_READ_LE16(pExtra_data + sizeof(mz_uint16)); field_total_size = field_data_size + sizeof(mz_uint16) * 2; if (field_total_size > extra_size_remaining) { mz_zip_array_clear(pZip, &file_data_array); return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); } if (field_id == MZ_ZIP64_EXTENDED_INFORMATION_FIELD_HEADER_ID) { const mz_uint8 *pSrc_field_data = pExtra_data + sizeof(mz_uint32); if (field_data_size < sizeof(mz_uint64) * 2) { mz_zip_array_clear(pZip, &file_data_array); return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); } local_header_uncomp_size = MZ_READ_LE64(pSrc_field_data); local_header_comp_size = MZ_READ_LE64(pSrc_field_data + sizeof(mz_uint64)); /* may be 0 if there's a descriptor */ found_zip64_ext_data_in_ldir = MZ_TRUE; break; } pExtra_data += field_total_size; extra_size_remaining -= field_total_size; } while (extra_size_remaining); mz_zip_array_clear(pZip, &file_data_array); } if (!pState->m_zip64) { /* Try to detect if the new archive will most likely wind up too big and bail early (+(sizeof(mz_uint32) * 4) is for the optional descriptor which could be present, +64 is a fudge factor). */ /* We also check when the archive is finalized so this doesn't need to be perfect. */ mz_uint64 approx_new_archive_size = cur_dst_file_ofs + num_alignment_padding_bytes + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + src_archive_bytes_remaining + (sizeof(mz_uint32) * 4) + pState->m_central_dir.m_size + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + src_central_dir_following_data_size + MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE + 64; if (approx_new_archive_size >= MZ_UINT32_MAX) return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); } /* Write dest archive padding */ if (!mz_zip_writer_write_zeros(pZip, cur_dst_file_ofs, num_alignment_padding_bytes)) return MZ_FALSE; cur_dst_file_ofs += num_alignment_padding_bytes; local_dir_header_ofs = cur_dst_file_ofs; if (pZip->m_file_offset_alignment) { MZ_ASSERT((local_dir_header_ofs & (pZip->m_file_offset_alignment - 1)) == 0); } /* The original zip's local header+ext block doesn't change, even with zip64, so we can just copy it over to the dest zip */ if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_dst_file_ofs, pLocal_header, MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != MZ_ZIP_LOCAL_DIR_HEADER_SIZE) return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); cur_dst_file_ofs += MZ_ZIP_LOCAL_DIR_HEADER_SIZE; /* Copy over the source archive bytes to the dest archive, also ensure we have enough buf space to handle optional data descriptor */ if (NULL == (pBuf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, (size_t)MZ_MAX(32U, MZ_MIN((mz_uint64)MZ_ZIP_MAX_IO_BUF_SIZE, src_archive_bytes_remaining))))) return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); while (src_archive_bytes_remaining) { n = (mz_uint)MZ_MIN((mz_uint64)MZ_ZIP_MAX_IO_BUF_SIZE, src_archive_bytes_remaining); if (pSource_zip->m_pRead(pSource_zip->m_pIO_opaque, cur_src_file_ofs, pBuf, n) != n) { pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); } cur_src_file_ofs += n; if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_dst_file_ofs, pBuf, n) != n) { pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); } cur_dst_file_ofs += n; src_archive_bytes_remaining -= n; } /* Now deal with the optional data descriptor */ bit_flags = MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_BIT_FLAG_OFS); if (bit_flags & 8) { /* Copy data descriptor */ if ((pSource_zip->m_pState->m_zip64) || (found_zip64_ext_data_in_ldir)) { /* src is zip64, dest must be zip64 */ /* name uint32_t's */ /* id 1 (optional in zip64?) */ /* crc 1 */ /* comp_size 2 */ /* uncomp_size 2 */ if (pSource_zip->m_pRead(pSource_zip->m_pIO_opaque, cur_src_file_ofs, pBuf, (sizeof(mz_uint32) * 6)) != (sizeof(mz_uint32) * 6)) { pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); } n = sizeof(mz_uint32) * ((MZ_READ_LE32(pBuf) == MZ_ZIP_DATA_DESCRIPTOR_ID) ? 6 : 5); } else { /* src is NOT zip64 */ mz_bool has_id; if (pSource_zip->m_pRead(pSource_zip->m_pIO_opaque, cur_src_file_ofs, pBuf, sizeof(mz_uint32) * 4) != sizeof(mz_uint32) * 4) { pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); } has_id = (MZ_READ_LE32(pBuf) == MZ_ZIP_DATA_DESCRIPTOR_ID); if (pZip->m_pState->m_zip64) { /* dest is zip64, so upgrade the data descriptor */ const mz_uint8 *pSrc_descriptor = (const mz_uint8 *)pBuf + (has_id ? sizeof(mz_uint32) : 0); const mz_uint32 src_crc32 = MZ_READ_LE32(pSrc_descriptor); const mz_uint64 src_comp_size = MZ_READ_LE32(pSrc_descriptor + sizeof(mz_uint32)); const mz_uint64 src_uncomp_size = MZ_READ_LE32(pSrc_descriptor + 2*sizeof(mz_uint32)); mz_write_le32((mz_uint8 *)pBuf, MZ_ZIP_DATA_DESCRIPTOR_ID); mz_write_le32((mz_uint8 *)pBuf + sizeof(mz_uint32) * 1, src_crc32); mz_write_le64((mz_uint8 *)pBuf + sizeof(mz_uint32) * 2, src_comp_size); mz_write_le64((mz_uint8 *)pBuf + sizeof(mz_uint32) * 4, src_uncomp_size); n = sizeof(mz_uint32) * 6; } else { /* dest is NOT zip64, just copy it as-is */ n = sizeof(mz_uint32) * (has_id ? 4 : 3); } } if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_dst_file_ofs, pBuf, n) != n) { pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); } cur_src_file_ofs += n; cur_dst_file_ofs += n; } pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); /* Finally, add the new central dir header */ orig_central_dir_size = pState->m_central_dir.m_size; memcpy(new_central_header, pSrc_central_header, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE); if (pState->m_zip64) { /* This is the painful part: We need to write a new central dir header + ext block with updated zip64 fields, and ensure the old fields (if any) are not included. */ const mz_uint8 *pSrc_ext = pSrc_central_header + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + src_filename_len; mz_zip_array new_ext_block; mz_zip_array_init(&new_ext_block, sizeof(mz_uint8)); MZ_WRITE_LE32(new_central_header + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS, MZ_UINT32_MAX); MZ_WRITE_LE32(new_central_header + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS, MZ_UINT32_MAX); MZ_WRITE_LE32(new_central_header + MZ_ZIP_CDH_LOCAL_HEADER_OFS, MZ_UINT32_MAX); if (!mz_zip_writer_update_zip64_extension_block(&new_ext_block, pZip, pSrc_ext, src_ext_len, &src_file_stat.m_comp_size, &src_file_stat.m_uncomp_size, &local_dir_header_ofs, NULL)) { mz_zip_array_clear(pZip, &new_ext_block); return MZ_FALSE; } MZ_WRITE_LE16(new_central_header + MZ_ZIP_CDH_EXTRA_LEN_OFS, new_ext_block.m_size); if (!mz_zip_array_push_back(pZip, &pState->m_central_dir, new_central_header, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE)) { mz_zip_array_clear(pZip, &new_ext_block); return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); } if (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pSrc_central_header + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE, src_filename_len)) { mz_zip_array_clear(pZip, &new_ext_block); mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, MZ_FALSE); return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); } if (!mz_zip_array_push_back(pZip, &pState->m_central_dir, new_ext_block.m_p, new_ext_block.m_size)) { mz_zip_array_clear(pZip, &new_ext_block); mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, MZ_FALSE); return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); } if (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pSrc_central_header + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + src_filename_len + src_ext_len, src_comment_len)) { mz_zip_array_clear(pZip, &new_ext_block); mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, MZ_FALSE); return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); } mz_zip_array_clear(pZip, &new_ext_block); } else { /* sanity checks */ if (cur_dst_file_ofs > MZ_UINT32_MAX) return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); if (local_dir_header_ofs >= MZ_UINT32_MAX) return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); MZ_WRITE_LE32(new_central_header + MZ_ZIP_CDH_LOCAL_HEADER_OFS, local_dir_header_ofs); if (!mz_zip_array_push_back(pZip, &pState->m_central_dir, new_central_header, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE)) return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); if (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pSrc_central_header + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE, src_central_dir_following_data_size)) { mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, MZ_FALSE); return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); } } /* This shouldn't trigger unless we screwed up during the initial sanity checks */ if (pState->m_central_dir.m_size >= MZ_UINT32_MAX) { /* TODO: Support central dirs >= 32-bits in size */ mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, MZ_FALSE); return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_CDIR_SIZE); } n = (mz_uint32)orig_central_dir_size; if (!mz_zip_array_push_back(pZip, &pState->m_central_dir_offsets, &n, 1)) { mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, MZ_FALSE); return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); } pZip->m_total_files++; pZip->m_archive_size = cur_dst_file_ofs; return MZ_TRUE; } mz_bool mz_zip_writer_finalize_archive(mz_zip_archive *pZip) { mz_zip_internal_state *pState; mz_uint64 central_dir_ofs, central_dir_size; mz_uint8 hdr[256]; if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING)) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); pState = pZip->m_pState; if (pState->m_zip64) { if ((mz_uint64)pState->m_central_dir.m_size >= MZ_UINT32_MAX) return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); } else { if ((pZip->m_total_files > MZ_UINT16_MAX) || ((pZip->m_archive_size + pState->m_central_dir.m_size + MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) > MZ_UINT32_MAX)) return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); } central_dir_ofs = 0; central_dir_size = 0; if (pZip->m_total_files) { /* Write central directory */ central_dir_ofs = pZip->m_archive_size; central_dir_size = pState->m_central_dir.m_size; pZip->m_central_directory_file_ofs = central_dir_ofs; if (pZip->m_pWrite(pZip->m_pIO_opaque, central_dir_ofs, pState->m_central_dir.m_p, (size_t)central_dir_size) != central_dir_size) return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); pZip->m_archive_size += central_dir_size; } if (pState->m_zip64) { /* Write zip64 end of central directory header */ mz_uint64 rel_ofs_to_zip64_ecdr = pZip->m_archive_size; MZ_CLEAR_ARR(hdr); MZ_WRITE_LE32(hdr + MZ_ZIP64_ECDH_SIG_OFS, MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIG); MZ_WRITE_LE64(hdr + MZ_ZIP64_ECDH_SIZE_OF_RECORD_OFS, MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE - sizeof(mz_uint32) - sizeof(mz_uint64)); MZ_WRITE_LE16(hdr + MZ_ZIP64_ECDH_VERSION_MADE_BY_OFS, 0x031E); /* TODO: always Unix */ MZ_WRITE_LE16(hdr + MZ_ZIP64_ECDH_VERSION_NEEDED_OFS, 0x002D); MZ_WRITE_LE64(hdr + MZ_ZIP64_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS, pZip->m_total_files); MZ_WRITE_LE64(hdr + MZ_ZIP64_ECDH_CDIR_TOTAL_ENTRIES_OFS, pZip->m_total_files); MZ_WRITE_LE64(hdr + MZ_ZIP64_ECDH_CDIR_SIZE_OFS, central_dir_size); MZ_WRITE_LE64(hdr + MZ_ZIP64_ECDH_CDIR_OFS_OFS, central_dir_ofs); if (pZip->m_pWrite(pZip->m_pIO_opaque, pZip->m_archive_size, hdr, MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE) != MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE) return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); pZip->m_archive_size += MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE; /* Write zip64 end of central directory locator */ MZ_CLEAR_ARR(hdr); MZ_WRITE_LE32(hdr + MZ_ZIP64_ECDL_SIG_OFS, MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIG); MZ_WRITE_LE64(hdr + MZ_ZIP64_ECDL_REL_OFS_TO_ZIP64_ECDR_OFS, rel_ofs_to_zip64_ecdr); MZ_WRITE_LE32(hdr + MZ_ZIP64_ECDL_TOTAL_NUMBER_OF_DISKS_OFS, 1); if (pZip->m_pWrite(pZip->m_pIO_opaque, pZip->m_archive_size, hdr, MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE) != MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE) return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); pZip->m_archive_size += MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE; } /* Write end of central directory record */ MZ_CLEAR_ARR(hdr); MZ_WRITE_LE32(hdr + MZ_ZIP_ECDH_SIG_OFS, MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG); MZ_WRITE_LE16(hdr + MZ_ZIP_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS, MZ_MIN(MZ_UINT16_MAX, pZip->m_total_files)); MZ_WRITE_LE16(hdr + MZ_ZIP_ECDH_CDIR_TOTAL_ENTRIES_OFS, MZ_MIN(MZ_UINT16_MAX, pZip->m_total_files)); MZ_WRITE_LE32(hdr + MZ_ZIP_ECDH_CDIR_SIZE_OFS, MZ_MIN(MZ_UINT32_MAX, central_dir_size)); MZ_WRITE_LE32(hdr + MZ_ZIP_ECDH_CDIR_OFS_OFS, MZ_MIN(MZ_UINT32_MAX, central_dir_ofs)); if (pZip->m_pWrite(pZip->m_pIO_opaque, pZip->m_archive_size, hdr, MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) != MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); #ifndef MINIZ_NO_STDIO if ((pState->m_pFile) && (MZ_FFLUSH(pState->m_pFile) == EOF)) return mz_zip_set_error(pZip, MZ_ZIP_FILE_CLOSE_FAILED); #endif /* #ifndef MINIZ_NO_STDIO */ pZip->m_archive_size += MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE; pZip->m_zip_mode = MZ_ZIP_MODE_WRITING_HAS_BEEN_FINALIZED; return MZ_TRUE; } mz_bool mz_zip_writer_finalize_heap_archive(mz_zip_archive *pZip, void **ppBuf, size_t *pSize) { if ((!ppBuf) || (!pSize)) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); *ppBuf = NULL; *pSize = 0; if ((!pZip) || (!pZip->m_pState)) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); if (pZip->m_pWrite != mz_zip_heap_write_func) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); if (!mz_zip_writer_finalize_archive(pZip)) return MZ_FALSE; *ppBuf = pZip->m_pState->m_pMem; *pSize = pZip->m_pState->m_mem_size; pZip->m_pState->m_pMem = NULL; pZip->m_pState->m_mem_size = pZip->m_pState->m_mem_capacity = 0; return MZ_TRUE; } mz_bool mz_zip_writer_end(mz_zip_archive *pZip) { return mz_zip_writer_end_internal(pZip, MZ_TRUE); } #ifndef MINIZ_NO_STDIO mz_bool mz_zip_add_mem_to_archive_file_in_place(const char *pZip_filename, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags) { return mz_zip_add_mem_to_archive_file_in_place_v2(pZip_filename, pArchive_name, pBuf, buf_size, pComment, comment_size, level_and_flags, NULL); } mz_bool mz_zip_add_mem_to_archive_file_in_place_v2(const char *pZip_filename, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags, mz_zip_error *pErr) { mz_bool status, created_new_archive = MZ_FALSE; mz_zip_archive zip_archive; struct MZ_FILE_STAT_STRUCT file_stat; mz_zip_error actual_err = MZ_ZIP_NO_ERROR; mz_zip_zero_struct(&zip_archive); if ((int)level_and_flags < 0) level_and_flags = MZ_DEFAULT_LEVEL; if ((!pZip_filename) || (!pArchive_name) || ((buf_size) && (!pBuf)) || ((comment_size) && (!pComment)) || ((level_and_flags & 0xF) > MZ_UBER_COMPRESSION)) { if (pErr) *pErr = MZ_ZIP_INVALID_PARAMETER; return MZ_FALSE; } if (!mz_zip_writer_validate_archive_name(pArchive_name)) { if (pErr) *pErr = MZ_ZIP_INVALID_FILENAME; return MZ_FALSE; } /* Important: The regular non-64 bit version of stat() can fail here if the file is very large, which could cause the archive to be overwritten. */ /* So be sure to compile with _LARGEFILE64_SOURCE 1 */ if (MZ_FILE_STAT(pZip_filename, &file_stat) != 0) { /* Create a new archive. */ if (!mz_zip_writer_init_file_v2(&zip_archive, pZip_filename, 0, level_and_flags)) { if (pErr) *pErr = zip_archive.m_last_error; return MZ_FALSE; } created_new_archive = MZ_TRUE; } else { /* Append to an existing archive. */ if (!mz_zip_reader_init_file_v2(&zip_archive, pZip_filename, level_and_flags | MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY, 0, 0)) { if (pErr) *pErr = zip_archive.m_last_error; return MZ_FALSE; } if (!mz_zip_writer_init_from_reader_v2(&zip_archive, pZip_filename, level_and_flags)) { if (pErr) *pErr = zip_archive.m_last_error; mz_zip_reader_end_internal(&zip_archive, MZ_FALSE); return MZ_FALSE; } } status = mz_zip_writer_add_mem_ex(&zip_archive, pArchive_name, pBuf, buf_size, pComment, comment_size, level_and_flags, 0, 0); actual_err = zip_archive.m_last_error; /* Always finalize, even if adding failed for some reason, so we have a valid central directory. (This may not always succeed, but we can try.) */ if (!mz_zip_writer_finalize_archive(&zip_archive)) { if (!actual_err) actual_err = zip_archive.m_last_error; status = MZ_FALSE; } if (!mz_zip_writer_end_internal(&zip_archive, status)) { if (!actual_err) actual_err = zip_archive.m_last_error; status = MZ_FALSE; } if ((!status) && (created_new_archive)) { /* It's a new archive and something went wrong, so just delete it. */ int ignoredStatus = MZ_DELETE_FILE(pZip_filename); (void)ignoredStatus; } if (pErr) *pErr = actual_err; return status; } void *mz_zip_extract_archive_file_to_heap_v2(const char *pZip_filename, const char *pArchive_name, const char *pComment, size_t *pSize, mz_uint flags, mz_zip_error *pErr) { mz_uint32 file_index; mz_zip_archive zip_archive; void *p = NULL; if (pSize) *pSize = 0; if ((!pZip_filename) || (!pArchive_name)) { if (pErr) *pErr = MZ_ZIP_INVALID_PARAMETER; return NULL; } mz_zip_zero_struct(&zip_archive); if (!mz_zip_reader_init_file_v2(&zip_archive, pZip_filename, flags | MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY, 0, 0)) { if (pErr) *pErr = zip_archive.m_last_error; return NULL; } if (mz_zip_reader_locate_file_v2(&zip_archive, pArchive_name, pComment, flags, &file_index)) { p = mz_zip_reader_extract_to_heap(&zip_archive, file_index, pSize, flags); } mz_zip_reader_end_internal(&zip_archive, p != NULL); if (pErr) *pErr = zip_archive.m_last_error; return p; } void *mz_zip_extract_archive_file_to_heap(const char *pZip_filename, const char *pArchive_name, size_t *pSize, mz_uint flags) { return mz_zip_extract_archive_file_to_heap_v2(pZip_filename, pArchive_name, NULL, pSize, flags, NULL); } #endif /* #ifndef MINIZ_NO_STDIO */ #endif /* #ifndef MINIZ_NO_ARCHIVE_WRITING_APIS */ /* ------------------- Misc utils */ mz_zip_mode mz_zip_get_mode(mz_zip_archive *pZip) { return pZip ? pZip->m_zip_mode : MZ_ZIP_MODE_INVALID; } mz_zip_type mz_zip_get_type(mz_zip_archive *pZip) { return pZip ? pZip->m_zip_type : MZ_ZIP_TYPE_INVALID; } mz_zip_error mz_zip_set_last_error(mz_zip_archive *pZip, mz_zip_error err_num) { mz_zip_error prev_err; if (!pZip) return MZ_ZIP_INVALID_PARAMETER; prev_err = pZip->m_last_error; pZip->m_last_error = err_num; return prev_err; } mz_zip_error mz_zip_peek_last_error(mz_zip_archive *pZip) { if (!pZip) return MZ_ZIP_INVALID_PARAMETER; return pZip->m_last_error; } mz_zip_error mz_zip_clear_last_error(mz_zip_archive *pZip) { return mz_zip_set_last_error(pZip, MZ_ZIP_NO_ERROR); } mz_zip_error mz_zip_get_last_error(mz_zip_archive *pZip) { mz_zip_error prev_err; if (!pZip) return MZ_ZIP_INVALID_PARAMETER; prev_err = pZip->m_last_error; pZip->m_last_error = MZ_ZIP_NO_ERROR; return prev_err; } const char *mz_zip_get_error_string(mz_zip_error mz_err) { switch (mz_err) { case MZ_ZIP_NO_ERROR: return "no error"; case MZ_ZIP_UNDEFINED_ERROR: return "undefined error"; case MZ_ZIP_TOO_MANY_FILES: return "too many files"; case MZ_ZIP_FILE_TOO_LARGE: return "file too large"; case MZ_ZIP_UNSUPPORTED_METHOD: return "unsupported method"; case MZ_ZIP_UNSUPPORTED_ENCRYPTION: return "unsupported encryption"; case MZ_ZIP_UNSUPPORTED_FEATURE: return "unsupported feature"; case MZ_ZIP_FAILED_FINDING_CENTRAL_DIR: return "failed finding central directory"; case MZ_ZIP_NOT_AN_ARCHIVE: return "not a ZIP archive"; case MZ_ZIP_INVALID_HEADER_OR_CORRUPTED: return "invalid header or archive is corrupted"; case MZ_ZIP_UNSUPPORTED_MULTIDISK: return "unsupported multidisk archive"; case MZ_ZIP_DECOMPRESSION_FAILED: return "decompression failed or archive is corrupted"; case MZ_ZIP_COMPRESSION_FAILED: return "compression failed"; case MZ_ZIP_UNEXPECTED_DECOMPRESSED_SIZE: return "unexpected decompressed size"; case MZ_ZIP_CRC_CHECK_FAILED: return "CRC-32 check failed"; case MZ_ZIP_UNSUPPORTED_CDIR_SIZE: return "unsupported central directory size"; case MZ_ZIP_ALLOC_FAILED: return "allocation failed"; case MZ_ZIP_FILE_OPEN_FAILED: return "file open failed"; case MZ_ZIP_FILE_CREATE_FAILED: return "file create failed"; case MZ_ZIP_FILE_WRITE_FAILED: return "file write failed"; case MZ_ZIP_FILE_READ_FAILED: return "file read failed"; case MZ_ZIP_FILE_CLOSE_FAILED: return "file close failed"; case MZ_ZIP_FILE_SEEK_FAILED: return "file seek failed"; case MZ_ZIP_FILE_STAT_FAILED: return "file stat failed"; case MZ_ZIP_INVALID_PARAMETER: return "invalid parameter"; case MZ_ZIP_INVALID_FILENAME: return "invalid filename"; case MZ_ZIP_BUF_TOO_SMALL: return "buffer too small"; case MZ_ZIP_INTERNAL_ERROR: return "internal error"; case MZ_ZIP_FILE_NOT_FOUND: return "file not found"; case MZ_ZIP_ARCHIVE_TOO_LARGE: return "archive is too large"; case MZ_ZIP_VALIDATION_FAILED: return "validation failed"; case MZ_ZIP_WRITE_CALLBACK_FAILED: return "write callback failed"; case MZ_ZIP_TOTAL_ERRORS: return "total errors"; default: break; } return "unknown error"; } /* Note: Just because the archive is not zip64 doesn't necessarily mean it doesn't have Zip64 extended information extra field, argh. */ mz_bool mz_zip_is_zip64(mz_zip_archive *pZip) { if ((!pZip) || (!pZip->m_pState)) return MZ_FALSE; return pZip->m_pState->m_zip64; } size_t mz_zip_get_central_dir_size(mz_zip_archive *pZip) { if ((!pZip) || (!pZip->m_pState)) return 0; return pZip->m_pState->m_central_dir.m_size; } mz_uint mz_zip_reader_get_num_files(mz_zip_archive *pZip) { return pZip ? pZip->m_total_files : 0; } mz_uint64 mz_zip_get_archive_size(mz_zip_archive *pZip) { if (!pZip) return 0; return pZip->m_archive_size; } mz_uint64 mz_zip_get_archive_file_start_offset(mz_zip_archive *pZip) { if ((!pZip) || (!pZip->m_pState)) return 0; return pZip->m_pState->m_file_archive_start_ofs; } MZ_FILE *mz_zip_get_cfile(mz_zip_archive *pZip) { if ((!pZip) || (!pZip->m_pState)) return 0; return pZip->m_pState->m_pFile; } size_t mz_zip_read_archive_data(mz_zip_archive *pZip, mz_uint64 file_ofs, void *pBuf, size_t n) { if ((!pZip) || (!pZip->m_pState) || (!pBuf) || (!pZip->m_pRead)) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); return pZip->m_pRead(pZip->m_pIO_opaque, file_ofs, pBuf, n); } mz_uint mz_zip_reader_get_filename(mz_zip_archive *pZip, mz_uint file_index, char *pFilename, mz_uint filename_buf_size) { mz_uint n; const mz_uint8 *p = mz_zip_get_cdh(pZip, file_index); if (!p) { if (filename_buf_size) pFilename[0] = '\0'; mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); return 0; } n = MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS); if (filename_buf_size) { n = MZ_MIN(n, filename_buf_size - 1); memcpy(pFilename, p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE, n); pFilename[n] = '\0'; } return n + 1; } mz_bool mz_zip_reader_file_stat(mz_zip_archive *pZip, mz_uint file_index, mz_zip_archive_file_stat *pStat) { return mz_zip_file_stat_internal(pZip, file_index, mz_zip_get_cdh(pZip, file_index), pStat, NULL); } mz_bool mz_zip_end(mz_zip_archive *pZip) { if (!pZip) return MZ_FALSE; if (pZip->m_zip_mode == MZ_ZIP_MODE_READING) return mz_zip_reader_end(pZip); #ifndef MINIZ_NO_ARCHIVE_WRITING_APIS else if ((pZip->m_zip_mode == MZ_ZIP_MODE_WRITING) || (pZip->m_zip_mode == MZ_ZIP_MODE_WRITING_HAS_BEEN_FINALIZED)) return mz_zip_writer_end(pZip); #endif return MZ_FALSE; } #ifdef __cplusplus } #endif #endif /*#ifndef MINIZ_NO_ARCHIVE_APIS*/ pr0m1th3as-datatypes-9c9a8d3/src/ods/miniz.h000066400000000000000000002127201522766574100207340ustar00rootroot00000000000000#ifndef MINIZ_EXPORT #define MINIZ_EXPORT #endif /* miniz.c 3.0.0 - public domain deflate/inflate, zlib-subset, ZIP reading/writing/appending, PNG writing See "unlicense" statement at the end of this file. Rich Geldreich , last updated Oct. 13, 2013 Implements RFC 1950: http://www.ietf.org/rfc/rfc1950.txt and RFC 1951: http://www.ietf.org/rfc/rfc1951.txt Most API's defined in miniz.c are optional. For example, to disable the archive related functions just define MINIZ_NO_ARCHIVE_APIS, or to get rid of all stdio usage define MINIZ_NO_STDIO (see the list below for more macros). * Low-level Deflate/Inflate implementation notes: Compression: Use the "tdefl" API's. The compressor supports raw, static, and dynamic blocks, lazy or greedy parsing, match length filtering, RLE-only, and Huffman-only streams. It performs and compresses approximately as well as zlib. Decompression: Use the "tinfl" API's. The entire decompressor is implemented as a single function coroutine: see tinfl_decompress(). It supports decompression into a 32KB (or larger power of 2) wrapping buffer, or into a memory block large enough to hold the entire file. The low-level tdefl/tinfl API's do not make any use of dynamic memory allocation. * zlib-style API notes: miniz.c implements a fairly large subset of zlib. There's enough functionality present for it to be a drop-in zlib replacement in many apps: The z_stream struct, optional memory allocation callbacks deflateInit/deflateInit2/deflate/deflateReset/deflateEnd/deflateBound inflateInit/inflateInit2/inflate/inflateReset/inflateEnd compress, compress2, compressBound, uncompress CRC-32, Adler-32 - Using modern, minimal code size, CPU cache friendly routines. Supports raw deflate streams or standard zlib streams with adler-32 checking. Limitations: The callback API's are not implemented yet. No support for gzip headers or zlib static dictionaries. I've tried to closely emulate zlib's various flavors of stream flushing and return status codes, but there are no guarantees that miniz.c pulls this off perfectly. * PNG writing: See the tdefl_write_image_to_png_file_in_memory() function, originally written by Alex Evans. Supports 1-4 bytes/pixel images. * ZIP archive API notes: The ZIP archive API's where designed with simplicity and efficiency in mind, with just enough abstraction to get the job done with minimal fuss. There are simple API's to retrieve file information, read files from existing archives, create new archives, append new files to existing archives, or clone archive data from one archive to another. It supports archives located in memory or the heap, on disk (using stdio.h), or you can specify custom file read/write callbacks. - Archive reading: Just call this function to read a single file from a disk archive: void *mz_zip_extract_archive_file_to_heap(const char *pZip_filename, const char *pArchive_name, size_t *pSize, mz_uint zip_flags); For more complex cases, use the "mz_zip_reader" functions. Upon opening an archive, the entire central directory is located and read as-is into memory, and subsequent file access only occurs when reading individual files. - Archives file scanning: The simple way is to use this function to scan a loaded archive for a specific file: int mz_zip_reader_locate_file(mz_zip_archive *pZip, const char *pName, const char *pComment, mz_uint flags); The locate operation can optionally check file comments too, which (as one example) can be used to identify multiple versions of the same file in an archive. This function uses a simple linear search through the central directory, so it's not very fast. Alternately, you can iterate through all the files in an archive (using mz_zip_reader_get_num_files()) and retrieve detailed info on each file by calling mz_zip_reader_file_stat(). - Archive creation: Use the "mz_zip_writer" functions. The ZIP writer immediately writes compressed file data to disk and builds an exact image of the central directory in memory. The central directory image is written all at once at the end of the archive file when the archive is finalized. The archive writer can optionally align each file's local header and file data to any power of 2 alignment, which can be useful when the archive will be read from optical media. Also, the writer supports placing arbitrary data blobs at the very beginning of ZIP archives. Archives written using either feature are still readable by any ZIP tool. - Archive appending: The simple way to add a single file to an archive is to call this function: mz_bool mz_zip_add_mem_to_archive_file_in_place(const char *pZip_filename, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags); The archive will be created if it doesn't already exist, otherwise it'll be appended to. Note the appending is done in-place and is not an atomic operation, so if something goes wrong during the operation it's possible the archive could be left without a central directory (although the local file headers and file data will be fine, so the archive will be recoverable). For more complex archive modification scenarios: 1. The safest way is to use a mz_zip_reader to read the existing archive, cloning only those bits you want to preserve into a new archive using using the mz_zip_writer_add_from_zip_reader() function (which compiles the compressed file data as-is). When you're done, delete the old archive and rename the newly written archive, and you're done. This is safe but requires a bunch of temporary disk space or heap memory. 2. Or, you can convert an mz_zip_reader in-place to an mz_zip_writer using mz_zip_writer_init_from_reader(), append new files as needed, then finalize the archive which will write an updated central directory to the original archive. (This is basically what mz_zip_add_mem_to_archive_file_in_place() does.) There's a possibility that the archive's central directory could be lost with this method if anything goes wrong, though. - ZIP archive support limitations: No spanning support. Extraction functions can only handle unencrypted, stored or deflated files. Requires streams capable of seeking. * This is a header file library, like stb_image.c. To get only a header file, either cut and paste the below header, or create miniz.h, #define MINIZ_HEADER_FILE_ONLY, and then include miniz.c from it. * Important: For best perf. be sure to customize the below macros for your target platform: #define MINIZ_USE_UNALIGNED_LOADS_AND_STORES 1 #define MINIZ_LITTLE_ENDIAN 1 #define MINIZ_HAS_64BIT_REGISTERS 1 * On platforms using glibc, Be sure to "#define _LARGEFILE64_SOURCE 1" before including miniz.c to ensure miniz uses the 64-bit variants: fopen64(), stat64(), etc. Otherwise you won't be able to process large files (i.e. 32-bit stat() fails for me on files > 0x7FFFFFFF bytes). */ #pragma once /* Defines to completely disable specific portions of miniz.c: If all macros here are defined the only functionality remaining will be CRC-32 and adler-32. */ /* Define MINIZ_NO_STDIO to disable all usage and any functions which rely on stdio for file I/O. */ /*#define MINIZ_NO_STDIO */ /* If MINIZ_NO_TIME is specified then the ZIP archive functions will not be able to get the current time, or */ /* get/set file times, and the C run-time funcs that get/set times won't be called. */ /* The current downside is the times written to your archives will be from 1979. */ /*#define MINIZ_NO_TIME */ /* Define MINIZ_NO_DEFLATE_APIS to disable all compression API's. */ /*#define MINIZ_NO_DEFLATE_APIS */ /* Define MINIZ_NO_INFLATE_APIS to disable all decompression API's. */ /*#define MINIZ_NO_INFLATE_APIS */ /* Define MINIZ_NO_ARCHIVE_APIS to disable all ZIP archive API's. */ /*#define MINIZ_NO_ARCHIVE_APIS */ /* Define MINIZ_NO_ARCHIVE_WRITING_APIS to disable all writing related ZIP archive API's. */ /*#define MINIZ_NO_ARCHIVE_WRITING_APIS */ /* Define MINIZ_NO_ZLIB_APIS to remove all ZLIB-style compression/decompression API's. */ /*#define MINIZ_NO_ZLIB_APIS */ /* Define MINIZ_NO_ZLIB_COMPATIBLE_NAME to disable zlib names, to prevent conflicts against stock zlib. */ /*#define MINIZ_NO_ZLIB_COMPATIBLE_NAMES */ /* Define MINIZ_NO_MALLOC to disable all calls to malloc, free, and realloc. Note if MINIZ_NO_MALLOC is defined then the user must always provide custom user alloc/free/realloc callbacks to the zlib and archive API's, and a few stand-alone helper API's which don't provide custom user functions (such as tdefl_compress_mem_to_heap() and tinfl_decompress_mem_to_heap()) won't work. */ /*#define MINIZ_NO_MALLOC */ #ifdef MINIZ_NO_INFLATE_APIS #define MINIZ_NO_ARCHIVE_APIS #endif #ifdef MINIZ_NO_DEFLATE_APIS #define MINIZ_NO_ARCHIVE_WRITING_APIS #endif #if defined(__TINYC__) && (defined(__linux) || defined(__linux__)) /* TODO: Work around "error: include file 'sys\utime.h' when compiling with tcc on Linux */ #define MINIZ_NO_TIME #endif #include #if !defined(MINIZ_NO_TIME) && !defined(MINIZ_NO_ARCHIVE_APIS) #include #endif #if defined(_M_IX86) || defined(_M_X64) || defined(__i386__) || defined(__i386) || defined(__i486__) || defined(__i486) || defined(i386) || defined(__ia64__) || defined(__x86_64__) /* MINIZ_X86_OR_X64_CPU is only used to help set the below macros. */ #define MINIZ_X86_OR_X64_CPU 1 #else #define MINIZ_X86_OR_X64_CPU 0 #endif /* Set MINIZ_LITTLE_ENDIAN only if not set */ #if !defined(MINIZ_LITTLE_ENDIAN) #if defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) #if (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) /* Set MINIZ_LITTLE_ENDIAN to 1 if the processor is little endian. */ #define MINIZ_LITTLE_ENDIAN 1 #else #define MINIZ_LITTLE_ENDIAN 0 #endif #else #if MINIZ_X86_OR_X64_CPU #define MINIZ_LITTLE_ENDIAN 1 #else #define MINIZ_LITTLE_ENDIAN 0 #endif #endif #endif /* Using unaligned loads and stores causes errors when using UBSan */ #if defined(__has_feature) #if __has_feature(undefined_behavior_sanitizer) #define MINIZ_USE_UNALIGNED_LOADS_AND_STORES 0 #endif #endif /* Set MINIZ_USE_UNALIGNED_LOADS_AND_STORES only if not set */ #if !defined(MINIZ_USE_UNALIGNED_LOADS_AND_STORES) #if MINIZ_X86_OR_X64_CPU /* Set MINIZ_USE_UNALIGNED_LOADS_AND_STORES to 1 on CPU's that permit efficient integer loads and stores from unaligned addresses. */ #define MINIZ_USE_UNALIGNED_LOADS_AND_STORES 0 #define MINIZ_UNALIGNED_USE_MEMCPY #else #define MINIZ_USE_UNALIGNED_LOADS_AND_STORES 0 #endif #endif #if defined(_M_X64) || defined(_WIN64) || defined(__MINGW64__) || defined(_LP64) || defined(__LP64__) || defined(__ia64__) || defined(__x86_64__) /* Set MINIZ_HAS_64BIT_REGISTERS to 1 if operations on 64-bit integers are reasonably fast (and don't involve compiler generated calls to helper functions). */ #define MINIZ_HAS_64BIT_REGISTERS 1 #else #define MINIZ_HAS_64BIT_REGISTERS 0 #endif #ifdef __cplusplus extern "C" { #endif /* ------------------- zlib-style API Definitions. */ /* For more compatibility with zlib, miniz.c uses unsigned long for some parameters/struct members. Beware: mz_ulong can be either 32 or 64-bits! */ typedef unsigned long mz_ulong; /* mz_free() internally uses the MZ_FREE() macro (which by default calls free() unless you've modified the MZ_MALLOC macro) to release a block allocated from the heap. */ MINIZ_EXPORT void mz_free(void *p); #define MZ_ADLER32_INIT (1) /* mz_adler32() returns the initial adler-32 value to use when called with ptr==NULL. */ MINIZ_EXPORT mz_ulong mz_adler32(mz_ulong adler, const unsigned char *ptr, size_t buf_len); #define MZ_CRC32_INIT (0) /* mz_crc32() returns the initial CRC-32 value to use when called with ptr==NULL. */ MINIZ_EXPORT mz_ulong mz_crc32(mz_ulong crc, const unsigned char *ptr, size_t buf_len); /* Compression strategies. */ enum { MZ_DEFAULT_STRATEGY = 0, MZ_FILTERED = 1, MZ_HUFFMAN_ONLY = 2, MZ_RLE = 3, MZ_FIXED = 4 }; /* Method */ #define MZ_DEFLATED 8 /* Heap allocation callbacks. Note that mz_alloc_func parameter types purposely differ from zlib's: items/size is size_t, not unsigned long. */ typedef void *(*mz_alloc_func)(void *opaque, size_t items, size_t size); typedef void (*mz_free_func)(void *opaque, void *address); typedef void *(*mz_realloc_func)(void *opaque, void *address, size_t items, size_t size); /* Compression levels: 0-9 are the standard zlib-style levels, 10 is best possible compression (not zlib compatible, and may be very slow), MZ_DEFAULT_COMPRESSION=MZ_DEFAULT_LEVEL. */ enum { MZ_NO_COMPRESSION = 0, MZ_BEST_SPEED = 1, MZ_BEST_COMPRESSION = 9, MZ_UBER_COMPRESSION = 10, MZ_DEFAULT_LEVEL = 6, MZ_DEFAULT_COMPRESSION = -1 }; #define MZ_VERSION "11.0.2" #define MZ_VERNUM 0xB002 #define MZ_VER_MAJOR 11 #define MZ_VER_MINOR 2 #define MZ_VER_REVISION 0 #define MZ_VER_SUBREVISION 0 #ifndef MINIZ_NO_ZLIB_APIS /* Flush values. For typical usage you only need MZ_NO_FLUSH and MZ_FINISH. The other values are for advanced use (refer to the zlib docs). */ enum { MZ_NO_FLUSH = 0, MZ_PARTIAL_FLUSH = 1, MZ_SYNC_FLUSH = 2, MZ_FULL_FLUSH = 3, MZ_FINISH = 4, MZ_BLOCK = 5 }; /* Return status codes. MZ_PARAM_ERROR is non-standard. */ enum { MZ_OK = 0, MZ_STREAM_END = 1, MZ_NEED_DICT = 2, MZ_ERRNO = -1, MZ_STREAM_ERROR = -2, MZ_DATA_ERROR = -3, MZ_MEM_ERROR = -4, MZ_BUF_ERROR = -5, MZ_VERSION_ERROR = -6, MZ_PARAM_ERROR = -10000 }; /* Window bits */ #define MZ_DEFAULT_WINDOW_BITS 15 struct mz_internal_state; /* Compression/decompression stream struct. */ typedef struct mz_stream_s { const unsigned char *next_in; /* pointer to next byte to read */ unsigned int avail_in; /* number of bytes available at next_in */ mz_ulong total_in; /* total number of bytes consumed so far */ unsigned char *next_out; /* pointer to next byte to write */ unsigned int avail_out; /* number of bytes that can be written to next_out */ mz_ulong total_out; /* total number of bytes produced so far */ char *msg; /* error msg (unused) */ struct mz_internal_state *state; /* internal state, allocated by zalloc/zfree */ mz_alloc_func zalloc; /* optional heap allocation function (defaults to malloc) */ mz_free_func zfree; /* optional heap free function (defaults to free) */ void *opaque; /* heap alloc function user pointer */ int data_type; /* data_type (unused) */ mz_ulong adler; /* adler32 of the source or uncompressed data */ mz_ulong reserved; /* not used */ } mz_stream; typedef mz_stream *mz_streamp; /* Returns the version string of miniz.c. */ MINIZ_EXPORT const char *mz_version(void); #ifndef MINIZ_NO_DEFLATE_APIS /* mz_deflateInit() initializes a compressor with default options: */ /* Parameters: */ /* pStream must point to an initialized mz_stream struct. */ /* level must be between [MZ_NO_COMPRESSION, MZ_BEST_COMPRESSION]. */ /* level 1 enables a specially optimized compression function that's been optimized purely for performance, not ratio. */ /* (This special func. is currently only enabled when MINIZ_USE_UNALIGNED_LOADS_AND_STORES and MINIZ_LITTLE_ENDIAN are defined.) */ /* Return values: */ /* MZ_OK on success. */ /* MZ_STREAM_ERROR if the stream is bogus. */ /* MZ_PARAM_ERROR if the input parameters are bogus. */ /* MZ_MEM_ERROR on out of memory. */ MINIZ_EXPORT int mz_deflateInit(mz_streamp pStream, int level); /* mz_deflateInit2() is like mz_deflate(), except with more control: */ /* Additional parameters: */ /* method must be MZ_DEFLATED */ /* window_bits must be MZ_DEFAULT_WINDOW_BITS (to wrap the deflate stream with zlib header/adler-32 footer) or -MZ_DEFAULT_WINDOW_BITS (raw deflate/no header or footer) */ /* mem_level must be between [1, 9] (it's checked but ignored by miniz.c) */ MINIZ_EXPORT int mz_deflateInit2(mz_streamp pStream, int level, int method, int window_bits, int mem_level, int strategy); /* Quickly resets a compressor without having to reallocate anything. Same as calling mz_deflateEnd() followed by mz_deflateInit()/mz_deflateInit2(). */ MINIZ_EXPORT int mz_deflateReset(mz_streamp pStream); /* mz_deflate() compresses the input to output, consuming as much of the input and producing as much output as possible. */ /* Parameters: */ /* pStream is the stream to read from and write to. You must initialize/update the next_in, avail_in, next_out, and avail_out members. */ /* flush may be MZ_NO_FLUSH, MZ_PARTIAL_FLUSH/MZ_SYNC_FLUSH, MZ_FULL_FLUSH, or MZ_FINISH. */ /* Return values: */ /* MZ_OK on success (when flushing, or if more input is needed but not available, and/or there's more output to be written but the output buffer is full). */ /* MZ_STREAM_END if all input has been consumed and all output bytes have been written. Don't call mz_deflate() on the stream anymore. */ /* MZ_STREAM_ERROR if the stream is bogus. */ /* MZ_PARAM_ERROR if one of the parameters is invalid. */ /* MZ_BUF_ERROR if no forward progress is possible because the input and/or output buffers are empty. (Fill up the input buffer or free up some output space and try again.) */ MINIZ_EXPORT int mz_deflate(mz_streamp pStream, int flush); /* mz_deflateEnd() deinitializes a compressor: */ /* Return values: */ /* MZ_OK on success. */ /* MZ_STREAM_ERROR if the stream is bogus. */ MINIZ_EXPORT int mz_deflateEnd(mz_streamp pStream); /* mz_deflateBound() returns a (very) conservative upper bound on the amount of data that could be generated by deflate(), assuming flush is set to only MZ_NO_FLUSH or MZ_FINISH. */ MINIZ_EXPORT mz_ulong mz_deflateBound(mz_streamp pStream, mz_ulong source_len); /* Single-call compression functions mz_compress() and mz_compress2(): */ /* Returns MZ_OK on success, or one of the error codes from mz_deflate() on failure. */ MINIZ_EXPORT int mz_compress(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len); MINIZ_EXPORT int mz_compress2(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len, int level); /* mz_compressBound() returns a (very) conservative upper bound on the amount of data that could be generated by calling mz_compress(). */ MINIZ_EXPORT mz_ulong mz_compressBound(mz_ulong source_len); #endif /*#ifndef MINIZ_NO_DEFLATE_APIS*/ #ifndef MINIZ_NO_INFLATE_APIS /* Initializes a decompressor. */ MINIZ_EXPORT int mz_inflateInit(mz_streamp pStream); /* mz_inflateInit2() is like mz_inflateInit() with an additional option that controls the window size and whether or not the stream has been wrapped with a zlib header/footer: */ /* window_bits must be MZ_DEFAULT_WINDOW_BITS (to parse zlib header/footer) or -MZ_DEFAULT_WINDOW_BITS (raw deflate). */ MINIZ_EXPORT int mz_inflateInit2(mz_streamp pStream, int window_bits); /* Quickly resets a compressor without having to reallocate anything. Same as calling mz_inflateEnd() followed by mz_inflateInit()/mz_inflateInit2(). */ MINIZ_EXPORT int mz_inflateReset(mz_streamp pStream); /* Decompresses the input stream to the output, consuming only as much of the input as needed, and writing as much to the output as possible. */ /* Parameters: */ /* pStream is the stream to read from and write to. You must initialize/update the next_in, avail_in, next_out, and avail_out members. */ /* flush may be MZ_NO_FLUSH, MZ_SYNC_FLUSH, or MZ_FINISH. */ /* On the first call, if flush is MZ_FINISH it's assumed the input and output buffers are both sized large enough to decompress the entire stream in a single call (this is slightly faster). */ /* MZ_FINISH implies that there are no more source bytes available beside what's already in the input buffer, and that the output buffer is large enough to hold the rest of the decompressed data. */ /* Return values: */ /* MZ_OK on success. Either more input is needed but not available, and/or there's more output to be written but the output buffer is full. */ /* MZ_STREAM_END if all needed input has been consumed and all output bytes have been written. For zlib streams, the adler-32 of the decompressed data has also been verified. */ /* MZ_STREAM_ERROR if the stream is bogus. */ /* MZ_DATA_ERROR if the deflate stream is invalid. */ /* MZ_PARAM_ERROR if one of the parameters is invalid. */ /* MZ_BUF_ERROR if no forward progress is possible because the input buffer is empty but the inflater needs more input to continue, or if the output buffer is not large enough. Call mz_inflate() again */ /* with more input data, or with more room in the output buffer (except when using single call decompression, described above). */ MINIZ_EXPORT int mz_inflate(mz_streamp pStream, int flush); /* Deinitializes a decompressor. */ MINIZ_EXPORT int mz_inflateEnd(mz_streamp pStream); /* Single-call decompression. */ /* Returns MZ_OK on success, or one of the error codes from mz_inflate() on failure. */ MINIZ_EXPORT int mz_uncompress(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len); MINIZ_EXPORT int mz_uncompress2(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong *pSource_len); #endif /*#ifndef MINIZ_NO_INFLATE_APIS*/ /* Returns a string description of the specified error code, or NULL if the error code is invalid. */ MINIZ_EXPORT const char *mz_error(int err); /* Redefine zlib-compatible names to miniz equivalents, so miniz.c can be used as a drop-in replacement for the subset of zlib that miniz.c supports. */ /* Define MINIZ_NO_ZLIB_COMPATIBLE_NAMES to disable zlib-compatibility if you use zlib in the same project. */ #ifndef MINIZ_NO_ZLIB_COMPATIBLE_NAMES typedef unsigned char Byte; typedef unsigned int uInt; typedef mz_ulong uLong; typedef Byte Bytef; typedef uInt uIntf; typedef char charf; typedef int intf; typedef void *voidpf; typedef uLong uLongf; typedef void *voidp; typedef void *const voidpc; #define Z_NULL 0 #define Z_NO_FLUSH MZ_NO_FLUSH #define Z_PARTIAL_FLUSH MZ_PARTIAL_FLUSH #define Z_SYNC_FLUSH MZ_SYNC_FLUSH #define Z_FULL_FLUSH MZ_FULL_FLUSH #define Z_FINISH MZ_FINISH #define Z_BLOCK MZ_BLOCK #define Z_OK MZ_OK #define Z_STREAM_END MZ_STREAM_END #define Z_NEED_DICT MZ_NEED_DICT #define Z_ERRNO MZ_ERRNO #define Z_STREAM_ERROR MZ_STREAM_ERROR #define Z_DATA_ERROR MZ_DATA_ERROR #define Z_MEM_ERROR MZ_MEM_ERROR #define Z_BUF_ERROR MZ_BUF_ERROR #define Z_VERSION_ERROR MZ_VERSION_ERROR #define Z_PARAM_ERROR MZ_PARAM_ERROR #define Z_NO_COMPRESSION MZ_NO_COMPRESSION #define Z_BEST_SPEED MZ_BEST_SPEED #define Z_BEST_COMPRESSION MZ_BEST_COMPRESSION #define Z_DEFAULT_COMPRESSION MZ_DEFAULT_COMPRESSION #define Z_DEFAULT_STRATEGY MZ_DEFAULT_STRATEGY #define Z_FILTERED MZ_FILTERED #define Z_HUFFMAN_ONLY MZ_HUFFMAN_ONLY #define Z_RLE MZ_RLE #define Z_FIXED MZ_FIXED #define Z_DEFLATED MZ_DEFLATED #define Z_DEFAULT_WINDOW_BITS MZ_DEFAULT_WINDOW_BITS #define alloc_func mz_alloc_func #define free_func mz_free_func #define internal_state mz_internal_state #define z_stream mz_stream #ifndef MINIZ_NO_DEFLATE_APIS #define deflateInit mz_deflateInit #define deflateInit2 mz_deflateInit2 #define deflateReset mz_deflateReset #define deflate mz_deflate #define deflateEnd mz_deflateEnd #define deflateBound mz_deflateBound #define compress mz_compress #define compress2 mz_compress2 #define compressBound mz_compressBound #endif /*#ifndef MINIZ_NO_DEFLATE_APIS*/ #ifndef MINIZ_NO_INFLATE_APIS #define inflateInit mz_inflateInit #define inflateInit2 mz_inflateInit2 #define inflateReset mz_inflateReset #define inflate mz_inflate #define inflateEnd mz_inflateEnd #define uncompress mz_uncompress #define uncompress2 mz_uncompress2 #endif /*#ifndef MINIZ_NO_INFLATE_APIS*/ #define crc32 mz_crc32 #define adler32 mz_adler32 #define MAX_WBITS 15 #define MAX_MEM_LEVEL 9 #define zError mz_error #define ZLIB_VERSION MZ_VERSION #define ZLIB_VERNUM MZ_VERNUM #define ZLIB_VER_MAJOR MZ_VER_MAJOR #define ZLIB_VER_MINOR MZ_VER_MINOR #define ZLIB_VER_REVISION MZ_VER_REVISION #define ZLIB_VER_SUBREVISION MZ_VER_SUBREVISION #define zlibVersion mz_version #define zlib_version mz_version() #endif /* #ifndef MINIZ_NO_ZLIB_COMPATIBLE_NAMES */ #endif /* MINIZ_NO_ZLIB_APIS */ #ifdef __cplusplus } #endif #pragma once #include #include #include #include /* ------------------- Types and macros */ typedef unsigned char mz_uint8; typedef signed short mz_int16; typedef unsigned short mz_uint16; typedef unsigned int mz_uint32; typedef unsigned int mz_uint; typedef int64_t mz_int64; typedef uint64_t mz_uint64; typedef int mz_bool; #define MZ_FALSE (0) #define MZ_TRUE (1) /* Works around MSVC's spammy "warning C4127: conditional expression is constant" message. */ #ifdef _MSC_VER #define MZ_MACRO_END while (0, 0) #else #define MZ_MACRO_END while (0) #endif #ifdef MINIZ_NO_STDIO #define MZ_FILE void * #else #include #define MZ_FILE FILE #endif /* #ifdef MINIZ_NO_STDIO */ #ifdef MINIZ_NO_TIME typedef struct mz_dummy_time_t_tag { mz_uint32 m_dummy1; mz_uint32 m_dummy2; } mz_dummy_time_t; #define MZ_TIME_T mz_dummy_time_t #else #define MZ_TIME_T time_t #endif #define MZ_ASSERT(x) assert(x) #ifdef MINIZ_NO_MALLOC #define MZ_MALLOC(x) NULL #define MZ_FREE(x) (void)x, ((void)0) #define MZ_REALLOC(p, x) NULL #else #define MZ_MALLOC(x) malloc(x) #define MZ_FREE(x) free(x) #define MZ_REALLOC(p, x) realloc(p, x) #endif #define MZ_MAX(a, b) (((a) > (b)) ? (a) : (b)) #define MZ_MIN(a, b) (((a) < (b)) ? (a) : (b)) #define MZ_CLEAR_OBJ(obj) memset(&(obj), 0, sizeof(obj)) #define MZ_CLEAR_ARR(obj) memset((obj), 0, sizeof(obj)) #define MZ_CLEAR_PTR(obj) memset((obj), 0, sizeof(*obj)) #if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN #define MZ_READ_LE16(p) *((const mz_uint16 *)(p)) #define MZ_READ_LE32(p) *((const mz_uint32 *)(p)) #else #define MZ_READ_LE16(p) ((mz_uint32)(((const mz_uint8 *)(p))[0]) | ((mz_uint32)(((const mz_uint8 *)(p))[1]) << 8U)) #define MZ_READ_LE32(p) ((mz_uint32)(((const mz_uint8 *)(p))[0]) | ((mz_uint32)(((const mz_uint8 *)(p))[1]) << 8U) | ((mz_uint32)(((const mz_uint8 *)(p))[2]) << 16U) | ((mz_uint32)(((const mz_uint8 *)(p))[3]) << 24U)) #endif #define MZ_READ_LE64(p) (((mz_uint64)MZ_READ_LE32(p)) | (((mz_uint64)MZ_READ_LE32((const mz_uint8 *)(p) + sizeof(mz_uint32))) << 32U)) #ifdef _MSC_VER #define MZ_FORCEINLINE __forceinline #elif defined(__GNUC__) #define MZ_FORCEINLINE __inline__ __attribute__((__always_inline__)) #else #define MZ_FORCEINLINE inline #endif #ifdef __cplusplus extern "C" { #endif extern MINIZ_EXPORT void *miniz_def_alloc_func(void *opaque, size_t items, size_t size); extern MINIZ_EXPORT void miniz_def_free_func(void *opaque, void *address); extern MINIZ_EXPORT void *miniz_def_realloc_func(void *opaque, void *address, size_t items, size_t size); #define MZ_UINT16_MAX (0xFFFFU) #define MZ_UINT32_MAX (0xFFFFFFFFU) #ifdef __cplusplus } #endif #pragma once #ifndef MINIZ_NO_DEFLATE_APIS #ifdef __cplusplus extern "C" { #endif /* ------------------- Low-level Compression API Definitions */ /* Set TDEFL_LESS_MEMORY to 1 to use less memory (compression will be slightly slower, and raw/dynamic blocks will be output more frequently). */ #define TDEFL_LESS_MEMORY 0 /* tdefl_init() compression flags logically OR'd together (low 12 bits contain the max. number of probes per dictionary search): */ /* TDEFL_DEFAULT_MAX_PROBES: The compressor defaults to 128 dictionary probes per dictionary search. 0=Huffman only, 1=Huffman+LZ (fastest/crap compression), 4095=Huffman+LZ (slowest/best compression). */ enum { TDEFL_HUFFMAN_ONLY = 0, TDEFL_DEFAULT_MAX_PROBES = 128, TDEFL_MAX_PROBES_MASK = 0xFFF }; /* TDEFL_WRITE_ZLIB_HEADER: If set, the compressor outputs a zlib header before the deflate data, and the Adler-32 of the source data at the end. Otherwise, you'll get raw deflate data. */ /* TDEFL_COMPUTE_ADLER32: Always compute the adler-32 of the input data (even when not writing zlib headers). */ /* TDEFL_GREEDY_PARSING_FLAG: Set to use faster greedy parsing, instead of more efficient lazy parsing. */ /* TDEFL_NONDETERMINISTIC_PARSING_FLAG: Enable to decrease the compressor's initialization time to the minimum, but the output may vary from run to run given the same input (depending on the contents of memory). */ /* TDEFL_RLE_MATCHES: Only look for RLE matches (matches with a distance of 1) */ /* TDEFL_FILTER_MATCHES: Discards matches <= 5 chars if enabled. */ /* TDEFL_FORCE_ALL_STATIC_BLOCKS: Disable usage of optimized Huffman tables. */ /* TDEFL_FORCE_ALL_RAW_BLOCKS: Only use raw (uncompressed) deflate blocks. */ /* The low 12 bits are reserved to control the max # of hash probes per dictionary lookup (see TDEFL_MAX_PROBES_MASK). */ enum { TDEFL_WRITE_ZLIB_HEADER = 0x01000, TDEFL_COMPUTE_ADLER32 = 0x02000, TDEFL_GREEDY_PARSING_FLAG = 0x04000, TDEFL_NONDETERMINISTIC_PARSING_FLAG = 0x08000, TDEFL_RLE_MATCHES = 0x10000, TDEFL_FILTER_MATCHES = 0x20000, TDEFL_FORCE_ALL_STATIC_BLOCKS = 0x40000, TDEFL_FORCE_ALL_RAW_BLOCKS = 0x80000 }; /* High level compression functions: */ /* tdefl_compress_mem_to_heap() compresses a block in memory to a heap block allocated via malloc(). */ /* On entry: */ /* pSrc_buf, src_buf_len: Pointer and size of source block to compress. */ /* flags: The max match finder probes (default is 128) logically OR'd against the above flags. Higher probes are slower but improve compression. */ /* On return: */ /* Function returns a pointer to the compressed data, or NULL on failure. */ /* *pOut_len will be set to the compressed data's size, which could be larger than src_buf_len on uncompressible data. */ /* The caller must free() the returned block when it's no longer needed. */ MINIZ_EXPORT void *tdefl_compress_mem_to_heap(const void *pSrc_buf, size_t src_buf_len, size_t *pOut_len, int flags); /* tdefl_compress_mem_to_mem() compresses a block in memory to another block in memory. */ /* Returns 0 on failure. */ MINIZ_EXPORT size_t tdefl_compress_mem_to_mem(void *pOut_buf, size_t out_buf_len, const void *pSrc_buf, size_t src_buf_len, int flags); /* Compresses an image to a compressed PNG file in memory. */ /* On entry: */ /* pImage, w, h, and num_chans describe the image to compress. num_chans may be 1, 2, 3, or 4. */ /* The image pitch in bytes per scanline will be w*num_chans. The leftmost pixel on the top scanline is stored first in memory. */ /* level may range from [0,10], use MZ_NO_COMPRESSION, MZ_BEST_SPEED, MZ_BEST_COMPRESSION, etc. or a decent default is MZ_DEFAULT_LEVEL */ /* If flip is true, the image will be flipped on the Y axis (useful for OpenGL apps). */ /* On return: */ /* Function returns a pointer to the compressed data, or NULL on failure. */ /* *pLen_out will be set to the size of the PNG image file. */ /* The caller must mz_free() the returned heap block (which will typically be larger than *pLen_out) when it's no longer needed. */ MINIZ_EXPORT void *tdefl_write_image_to_png_file_in_memory_ex(const void *pImage, int w, int h, int num_chans, size_t *pLen_out, mz_uint level, mz_bool flip); MINIZ_EXPORT void *tdefl_write_image_to_png_file_in_memory(const void *pImage, int w, int h, int num_chans, size_t *pLen_out); /* Output stream interface. The compressor uses this interface to write compressed data. It'll typically be called TDEFL_OUT_BUF_SIZE at a time. */ typedef mz_bool (*tdefl_put_buf_func_ptr)(const void *pBuf, int len, void *pUser); /* tdefl_compress_mem_to_output() compresses a block to an output stream. The above helpers use this function internally. */ MINIZ_EXPORT mz_bool tdefl_compress_mem_to_output(const void *pBuf, size_t buf_len, tdefl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags); enum { TDEFL_MAX_HUFF_TABLES = 3, TDEFL_MAX_HUFF_SYMBOLS_0 = 288, TDEFL_MAX_HUFF_SYMBOLS_1 = 32, TDEFL_MAX_HUFF_SYMBOLS_2 = 19, TDEFL_LZ_DICT_SIZE = 32768, TDEFL_LZ_DICT_SIZE_MASK = TDEFL_LZ_DICT_SIZE - 1, TDEFL_MIN_MATCH_LEN = 3, TDEFL_MAX_MATCH_LEN = 258 }; /* TDEFL_OUT_BUF_SIZE MUST be large enough to hold a single entire compressed output block (using static/fixed Huffman codes). */ #if TDEFL_LESS_MEMORY enum { TDEFL_LZ_CODE_BUF_SIZE = 24 * 1024, TDEFL_OUT_BUF_SIZE = (TDEFL_LZ_CODE_BUF_SIZE * 13) / 10, TDEFL_MAX_HUFF_SYMBOLS = 288, TDEFL_LZ_HASH_BITS = 12, TDEFL_LEVEL1_HASH_SIZE_MASK = 4095, TDEFL_LZ_HASH_SHIFT = (TDEFL_LZ_HASH_BITS + 2) / 3, TDEFL_LZ_HASH_SIZE = 1 << TDEFL_LZ_HASH_BITS }; #else enum { TDEFL_LZ_CODE_BUF_SIZE = 64 * 1024, TDEFL_OUT_BUF_SIZE = (TDEFL_LZ_CODE_BUF_SIZE * 13) / 10, TDEFL_MAX_HUFF_SYMBOLS = 288, TDEFL_LZ_HASH_BITS = 15, TDEFL_LEVEL1_HASH_SIZE_MASK = 4095, TDEFL_LZ_HASH_SHIFT = (TDEFL_LZ_HASH_BITS + 2) / 3, TDEFL_LZ_HASH_SIZE = 1 << TDEFL_LZ_HASH_BITS }; #endif /* The low-level tdefl functions below may be used directly if the above helper functions aren't flexible enough. The low-level functions don't make any heap allocations, unlike the above helper functions. */ typedef enum { TDEFL_STATUS_BAD_PARAM = -2, TDEFL_STATUS_PUT_BUF_FAILED = -1, TDEFL_STATUS_OKAY = 0, TDEFL_STATUS_DONE = 1 } tdefl_status; /* Must map to MZ_NO_FLUSH, MZ_SYNC_FLUSH, etc. enums */ typedef enum { TDEFL_NO_FLUSH = 0, TDEFL_SYNC_FLUSH = 2, TDEFL_FULL_FLUSH = 3, TDEFL_FINISH = 4 } tdefl_flush; /* tdefl's compression state structure. */ typedef struct { tdefl_put_buf_func_ptr m_pPut_buf_func; void *m_pPut_buf_user; mz_uint m_flags, m_max_probes[2]; int m_greedy_parsing; mz_uint m_adler32, m_lookahead_pos, m_lookahead_size, m_dict_size; mz_uint8 *m_pLZ_code_buf, *m_pLZ_flags, *m_pOutput_buf, *m_pOutput_buf_end; mz_uint m_num_flags_left, m_total_lz_bytes, m_lz_code_buf_dict_pos, m_bits_in, m_bit_buffer; mz_uint m_saved_match_dist, m_saved_match_len, m_saved_lit, m_output_flush_ofs, m_output_flush_remaining, m_finished, m_block_index, m_wants_to_finish; tdefl_status m_prev_return_status; const void *m_pIn_buf; void *m_pOut_buf; size_t *m_pIn_buf_size, *m_pOut_buf_size; tdefl_flush m_flush; const mz_uint8 *m_pSrc; size_t m_src_buf_left, m_out_buf_ofs; mz_uint8 m_dict[TDEFL_LZ_DICT_SIZE + TDEFL_MAX_MATCH_LEN - 1]; mz_uint16 m_huff_count[TDEFL_MAX_HUFF_TABLES][TDEFL_MAX_HUFF_SYMBOLS]; mz_uint16 m_huff_codes[TDEFL_MAX_HUFF_TABLES][TDEFL_MAX_HUFF_SYMBOLS]; mz_uint8 m_huff_code_sizes[TDEFL_MAX_HUFF_TABLES][TDEFL_MAX_HUFF_SYMBOLS]; mz_uint8 m_lz_code_buf[TDEFL_LZ_CODE_BUF_SIZE]; mz_uint16 m_next[TDEFL_LZ_DICT_SIZE]; mz_uint16 m_hash[TDEFL_LZ_HASH_SIZE]; mz_uint8 m_output_buf[TDEFL_OUT_BUF_SIZE]; } tdefl_compressor; /* Initializes the compressor. */ /* There is no corresponding deinit() function because the tdefl API's do not dynamically allocate memory. */ /* pBut_buf_func: If NULL, output data will be supplied to the specified callback. In this case, the user should call the tdefl_compress_buffer() API for compression. */ /* If pBut_buf_func is NULL the user should always call the tdefl_compress() API. */ /* flags: See the above enums (TDEFL_HUFFMAN_ONLY, TDEFL_WRITE_ZLIB_HEADER, etc.) */ MINIZ_EXPORT tdefl_status tdefl_init(tdefl_compressor *d, tdefl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags); /* Compresses a block of data, consuming as much of the specified input buffer as possible, and writing as much compressed data to the specified output buffer as possible. */ MINIZ_EXPORT tdefl_status tdefl_compress(tdefl_compressor *d, const void *pIn_buf, size_t *pIn_buf_size, void *pOut_buf, size_t *pOut_buf_size, tdefl_flush flush); /* tdefl_compress_buffer() is only usable when the tdefl_init() is called with a non-NULL tdefl_put_buf_func_ptr. */ /* tdefl_compress_buffer() always consumes the entire input buffer. */ MINIZ_EXPORT tdefl_status tdefl_compress_buffer(tdefl_compressor *d, const void *pIn_buf, size_t in_buf_size, tdefl_flush flush); MINIZ_EXPORT tdefl_status tdefl_get_prev_return_status(tdefl_compressor *d); MINIZ_EXPORT mz_uint32 tdefl_get_adler32(tdefl_compressor *d); /* Create tdefl_compress() flags given zlib-style compression parameters. */ /* level may range from [0,10] (where 10 is absolute max compression, but may be much slower on some files) */ /* window_bits may be -15 (raw deflate) or 15 (zlib) */ /* strategy may be either MZ_DEFAULT_STRATEGY, MZ_FILTERED, MZ_HUFFMAN_ONLY, MZ_RLE, or MZ_FIXED */ MINIZ_EXPORT mz_uint tdefl_create_comp_flags_from_zip_params(int level, int window_bits, int strategy); #ifndef MINIZ_NO_MALLOC /* Allocate the tdefl_compressor structure in C so that */ /* non-C language bindings to tdefl_ API don't need to worry about */ /* structure size and allocation mechanism. */ MINIZ_EXPORT tdefl_compressor *tdefl_compressor_alloc(void); MINIZ_EXPORT void tdefl_compressor_free(tdefl_compressor *pComp); #endif #ifdef __cplusplus } #endif #endif /*#ifndef MINIZ_NO_DEFLATE_APIS*/ #pragma once /* ------------------- Low-level Decompression API Definitions */ #ifndef MINIZ_NO_INFLATE_APIS #ifdef __cplusplus extern "C" { #endif /* Decompression flags used by tinfl_decompress(). */ /* TINFL_FLAG_PARSE_ZLIB_HEADER: If set, the input has a valid zlib header and ends with an adler32 checksum (it's a valid zlib stream). Otherwise, the input is a raw deflate stream. */ /* TINFL_FLAG_HAS_MORE_INPUT: If set, there are more input bytes available beyond the end of the supplied input buffer. If clear, the input buffer contains all remaining input. */ /* TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF: If set, the output buffer is large enough to hold the entire decompressed stream. If clear, the output buffer is at least the size of the dictionary (typically 32KB). */ /* TINFL_FLAG_COMPUTE_ADLER32: Force adler-32 checksum computation of the decompressed bytes. */ enum { TINFL_FLAG_PARSE_ZLIB_HEADER = 1, TINFL_FLAG_HAS_MORE_INPUT = 2, TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF = 4, TINFL_FLAG_COMPUTE_ADLER32 = 8 }; /* High level decompression functions: */ /* tinfl_decompress_mem_to_heap() decompresses a block in memory to a heap block allocated via malloc(). */ /* On entry: */ /* pSrc_buf, src_buf_len: Pointer and size of the Deflate or zlib source data to decompress. */ /* On return: */ /* Function returns a pointer to the decompressed data, or NULL on failure. */ /* *pOut_len will be set to the decompressed data's size, which could be larger than src_buf_len on uncompressible data. */ /* The caller must call mz_free() on the returned block when it's no longer needed. */ MINIZ_EXPORT void *tinfl_decompress_mem_to_heap(const void *pSrc_buf, size_t src_buf_len, size_t *pOut_len, int flags); /* tinfl_decompress_mem_to_mem() decompresses a block in memory to another block in memory. */ /* Returns TINFL_DECOMPRESS_MEM_TO_MEM_FAILED on failure, or the number of bytes written on success. */ #define TINFL_DECOMPRESS_MEM_TO_MEM_FAILED ((size_t)(-1)) MINIZ_EXPORT size_t tinfl_decompress_mem_to_mem(void *pOut_buf, size_t out_buf_len, const void *pSrc_buf, size_t src_buf_len, int flags); /* tinfl_decompress_mem_to_callback() decompresses a block in memory to an internal 32KB buffer, and a user provided callback function will be called to flush the buffer. */ /* Returns 1 on success or 0 on failure. */ typedef int (*tinfl_put_buf_func_ptr)(const void *pBuf, int len, void *pUser); MINIZ_EXPORT int tinfl_decompress_mem_to_callback(const void *pIn_buf, size_t *pIn_buf_size, tinfl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags); struct tinfl_decompressor_tag; typedef struct tinfl_decompressor_tag tinfl_decompressor; #ifndef MINIZ_NO_MALLOC /* Allocate the tinfl_decompressor structure in C so that */ /* non-C language bindings to tinfl_ API don't need to worry about */ /* structure size and allocation mechanism. */ MINIZ_EXPORT tinfl_decompressor *tinfl_decompressor_alloc(void); MINIZ_EXPORT void tinfl_decompressor_free(tinfl_decompressor *pDecomp); #endif /* Max size of LZ dictionary. */ #define TINFL_LZ_DICT_SIZE 32768 /* Return status. */ typedef enum { /* This flags indicates the inflator needs 1 or more input bytes to make forward progress, but the caller is indicating that no more are available. The compressed data */ /* is probably corrupted. If you call the inflator again with more bytes it'll try to continue processing the input but this is a BAD sign (either the data is corrupted or you called it incorrectly). */ /* If you call it again with no input you'll just get TINFL_STATUS_FAILED_CANNOT_MAKE_PROGRESS again. */ TINFL_STATUS_FAILED_CANNOT_MAKE_PROGRESS = -4, /* This flag indicates that one or more of the input parameters was obviously bogus. (You can try calling it again, but if you get this error the calling code is wrong.) */ TINFL_STATUS_BAD_PARAM = -3, /* This flags indicate the inflator is finished but the adler32 check of the uncompressed data didn't match. If you call it again it'll return TINFL_STATUS_DONE. */ TINFL_STATUS_ADLER32_MISMATCH = -2, /* This flags indicate the inflator has somehow failed (bad code, corrupted input, etc.). If you call it again without resetting via tinfl_init() it it'll just keep on returning the same status failure code. */ TINFL_STATUS_FAILED = -1, /* Any status code less than TINFL_STATUS_DONE must indicate a failure. */ /* This flag indicates the inflator has returned every byte of uncompressed data that it can, has consumed every byte that it needed, has successfully reached the end of the deflate stream, and */ /* if zlib headers and adler32 checking enabled that it has successfully checked the uncompressed data's adler32. If you call it again you'll just get TINFL_STATUS_DONE over and over again. */ TINFL_STATUS_DONE = 0, /* This flag indicates the inflator MUST have more input data (even 1 byte) before it can make any more forward progress, or you need to clear the TINFL_FLAG_HAS_MORE_INPUT */ /* flag on the next call if you don't have any more source data. If the source data was somehow corrupted it's also possible (but unlikely) for the inflator to keep on demanding input to */ /* proceed, so be sure to properly set the TINFL_FLAG_HAS_MORE_INPUT flag. */ TINFL_STATUS_NEEDS_MORE_INPUT = 1, /* This flag indicates the inflator definitely has 1 or more bytes of uncompressed data available, but it cannot write this data into the output buffer. */ /* Note if the source compressed data was corrupted it's possible for the inflator to return a lot of uncompressed data to the caller. I've been assuming you know how much uncompressed data to expect */ /* (either exact or worst case) and will stop calling the inflator and fail after receiving too much. In pure streaming scenarios where you have no idea how many bytes to expect this may not be possible */ /* so I may need to add some code to address this. */ TINFL_STATUS_HAS_MORE_OUTPUT = 2 } tinfl_status; /* Initializes the decompressor to its initial state. */ #define tinfl_init(r) \ do \ { \ (r)->m_state = 0; \ } \ MZ_MACRO_END #define tinfl_get_adler32(r) (r)->m_check_adler32 /* Main low-level decompressor coroutine function. This is the only function actually needed for decompression. All the other functions are just high-level helpers for improved usability. */ /* This is a universal API, i.e. it can be used as a building block to build any desired higher level decompression API. In the limit case, it can be called once per every byte input or output. */ MINIZ_EXPORT tinfl_status tinfl_decompress(tinfl_decompressor *r, const mz_uint8 *pIn_buf_next, size_t *pIn_buf_size, mz_uint8 *pOut_buf_start, mz_uint8 *pOut_buf_next, size_t *pOut_buf_size, const mz_uint32 decomp_flags); /* Internal/private bits follow. */ enum { TINFL_MAX_HUFF_TABLES = 3, TINFL_MAX_HUFF_SYMBOLS_0 = 288, TINFL_MAX_HUFF_SYMBOLS_1 = 32, TINFL_MAX_HUFF_SYMBOLS_2 = 19, TINFL_FAST_LOOKUP_BITS = 10, TINFL_FAST_LOOKUP_SIZE = 1 << TINFL_FAST_LOOKUP_BITS }; #if MINIZ_HAS_64BIT_REGISTERS #define TINFL_USE_64BIT_BITBUF 1 #else #define TINFL_USE_64BIT_BITBUF 0 #endif #if TINFL_USE_64BIT_BITBUF typedef mz_uint64 tinfl_bit_buf_t; #define TINFL_BITBUF_SIZE (64) #else typedef mz_uint32 tinfl_bit_buf_t; #define TINFL_BITBUF_SIZE (32) #endif struct tinfl_decompressor_tag { mz_uint32 m_state, m_num_bits, m_zhdr0, m_zhdr1, m_z_adler32, m_final, m_type, m_check_adler32, m_dist, m_counter, m_num_extra, m_table_sizes[TINFL_MAX_HUFF_TABLES]; tinfl_bit_buf_t m_bit_buf; size_t m_dist_from_out_buf_start; mz_int16 m_look_up[TINFL_MAX_HUFF_TABLES][TINFL_FAST_LOOKUP_SIZE]; mz_int16 m_tree_0[TINFL_MAX_HUFF_SYMBOLS_0 * 2]; mz_int16 m_tree_1[TINFL_MAX_HUFF_SYMBOLS_1 * 2]; mz_int16 m_tree_2[TINFL_MAX_HUFF_SYMBOLS_2 * 2]; mz_uint8 m_code_size_0[TINFL_MAX_HUFF_SYMBOLS_0]; mz_uint8 m_code_size_1[TINFL_MAX_HUFF_SYMBOLS_1]; mz_uint8 m_code_size_2[TINFL_MAX_HUFF_SYMBOLS_2]; mz_uint8 m_raw_header[4], m_len_codes[TINFL_MAX_HUFF_SYMBOLS_0 + TINFL_MAX_HUFF_SYMBOLS_1 + 137]; }; #ifdef __cplusplus } #endif #endif /*#ifndef MINIZ_NO_INFLATE_APIS*/ #pragma once /* ------------------- ZIP archive reading/writing */ #ifndef MINIZ_NO_ARCHIVE_APIS #ifdef __cplusplus extern "C" { #endif enum { /* Note: These enums can be reduced as needed to save memory or stack space - they are pretty conservative. */ MZ_ZIP_MAX_IO_BUF_SIZE = 64 * 1024, MZ_ZIP_MAX_ARCHIVE_FILENAME_SIZE = 512, MZ_ZIP_MAX_ARCHIVE_FILE_COMMENT_SIZE = 512 }; typedef struct { /* Central directory file index. */ mz_uint32 m_file_index; /* Byte offset of this entry in the archive's central directory. Note we currently only support up to UINT_MAX or less bytes in the central dir. */ mz_uint64 m_central_dir_ofs; /* These fields are copied directly from the zip's central dir. */ mz_uint16 m_version_made_by; mz_uint16 m_version_needed; mz_uint16 m_bit_flag; mz_uint16 m_method; /* CRC-32 of uncompressed data. */ mz_uint32 m_crc32; /* File's compressed size. */ mz_uint64 m_comp_size; /* File's uncompressed size. Note, I've seen some old archives where directory entries had 512 bytes for their uncompressed sizes, but when you try to unpack them you actually get 0 bytes. */ mz_uint64 m_uncomp_size; /* Zip internal and external file attributes. */ mz_uint16 m_internal_attr; mz_uint32 m_external_attr; /* Entry's local header file offset in bytes. */ mz_uint64 m_local_header_ofs; /* Size of comment in bytes. */ mz_uint32 m_comment_size; /* MZ_TRUE if the entry appears to be a directory. */ mz_bool m_is_directory; /* MZ_TRUE if the entry uses encryption/strong encryption (which miniz_zip doesn't support) */ mz_bool m_is_encrypted; /* MZ_TRUE if the file is not encrypted, a patch file, and if it uses a compression method we support. */ mz_bool m_is_supported; /* Filename. If string ends in '/' it's a subdirectory entry. */ /* Guaranteed to be zero terminated, may be truncated to fit. */ char m_filename[MZ_ZIP_MAX_ARCHIVE_FILENAME_SIZE]; /* Comment field. */ /* Guaranteed to be zero terminated, may be truncated to fit. */ char m_comment[MZ_ZIP_MAX_ARCHIVE_FILE_COMMENT_SIZE]; #ifdef MINIZ_NO_TIME MZ_TIME_T m_padding; #else MZ_TIME_T m_time; #endif } mz_zip_archive_file_stat; typedef size_t (*mz_file_read_func)(void *pOpaque, mz_uint64 file_ofs, void *pBuf, size_t n); typedef size_t (*mz_file_write_func)(void *pOpaque, mz_uint64 file_ofs, const void *pBuf, size_t n); typedef mz_bool (*mz_file_needs_keepalive)(void *pOpaque); struct mz_zip_internal_state_tag; typedef struct mz_zip_internal_state_tag mz_zip_internal_state; typedef enum { MZ_ZIP_MODE_INVALID = 0, MZ_ZIP_MODE_READING = 1, MZ_ZIP_MODE_WRITING = 2, MZ_ZIP_MODE_WRITING_HAS_BEEN_FINALIZED = 3 } mz_zip_mode; typedef enum { MZ_ZIP_FLAG_CASE_SENSITIVE = 0x0100, MZ_ZIP_FLAG_IGNORE_PATH = 0x0200, MZ_ZIP_FLAG_COMPRESSED_DATA = 0x0400, MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY = 0x0800, MZ_ZIP_FLAG_VALIDATE_LOCATE_FILE_FLAG = 0x1000, /* if enabled, mz_zip_reader_locate_file() will be called on each file as its validated to ensure the func finds the file in the central dir (intended for testing) */ MZ_ZIP_FLAG_VALIDATE_HEADERS_ONLY = 0x2000, /* validate the local headers, but don't decompress the entire file and check the crc32 */ MZ_ZIP_FLAG_WRITE_ZIP64 = 0x4000, /* always use the zip64 file format, instead of the original zip file format with automatic switch to zip64. Use as flags parameter with mz_zip_writer_init*_v2 */ MZ_ZIP_FLAG_WRITE_ALLOW_READING = 0x8000, MZ_ZIP_FLAG_ASCII_FILENAME = 0x10000, /*After adding a compressed file, seek back to local file header and set the correct sizes*/ MZ_ZIP_FLAG_WRITE_HEADER_SET_SIZE = 0x20000 } mz_zip_flags; typedef enum { MZ_ZIP_TYPE_INVALID = 0, MZ_ZIP_TYPE_USER, MZ_ZIP_TYPE_MEMORY, MZ_ZIP_TYPE_HEAP, MZ_ZIP_TYPE_FILE, MZ_ZIP_TYPE_CFILE, MZ_ZIP_TOTAL_TYPES } mz_zip_type; /* miniz error codes. Be sure to update mz_zip_get_error_string() if you add or modify this enum. */ typedef enum { MZ_ZIP_NO_ERROR = 0, MZ_ZIP_UNDEFINED_ERROR, MZ_ZIP_TOO_MANY_FILES, MZ_ZIP_FILE_TOO_LARGE, MZ_ZIP_UNSUPPORTED_METHOD, MZ_ZIP_UNSUPPORTED_ENCRYPTION, MZ_ZIP_UNSUPPORTED_FEATURE, MZ_ZIP_FAILED_FINDING_CENTRAL_DIR, MZ_ZIP_NOT_AN_ARCHIVE, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED, MZ_ZIP_UNSUPPORTED_MULTIDISK, MZ_ZIP_DECOMPRESSION_FAILED, MZ_ZIP_COMPRESSION_FAILED, MZ_ZIP_UNEXPECTED_DECOMPRESSED_SIZE, MZ_ZIP_CRC_CHECK_FAILED, MZ_ZIP_UNSUPPORTED_CDIR_SIZE, MZ_ZIP_ALLOC_FAILED, MZ_ZIP_FILE_OPEN_FAILED, MZ_ZIP_FILE_CREATE_FAILED, MZ_ZIP_FILE_WRITE_FAILED, MZ_ZIP_FILE_READ_FAILED, MZ_ZIP_FILE_CLOSE_FAILED, MZ_ZIP_FILE_SEEK_FAILED, MZ_ZIP_FILE_STAT_FAILED, MZ_ZIP_INVALID_PARAMETER, MZ_ZIP_INVALID_FILENAME, MZ_ZIP_BUF_TOO_SMALL, MZ_ZIP_INTERNAL_ERROR, MZ_ZIP_FILE_NOT_FOUND, MZ_ZIP_ARCHIVE_TOO_LARGE, MZ_ZIP_VALIDATION_FAILED, MZ_ZIP_WRITE_CALLBACK_FAILED, MZ_ZIP_TOTAL_ERRORS } mz_zip_error; typedef struct { mz_uint64 m_archive_size; mz_uint64 m_central_directory_file_ofs; /* We only support up to UINT32_MAX files in zip64 mode. */ mz_uint32 m_total_files; mz_zip_mode m_zip_mode; mz_zip_type m_zip_type; mz_zip_error m_last_error; mz_uint64 m_file_offset_alignment; mz_alloc_func m_pAlloc; mz_free_func m_pFree; mz_realloc_func m_pRealloc; void *m_pAlloc_opaque; mz_file_read_func m_pRead; mz_file_write_func m_pWrite; mz_file_needs_keepalive m_pNeeds_keepalive; void *m_pIO_opaque; mz_zip_internal_state *m_pState; } mz_zip_archive; typedef struct { mz_zip_archive *pZip; mz_uint flags; int status; mz_uint64 read_buf_size, read_buf_ofs, read_buf_avail, comp_remaining, out_buf_ofs, cur_file_ofs; mz_zip_archive_file_stat file_stat; void *pRead_buf; void *pWrite_buf; size_t out_blk_remain; tinfl_decompressor inflator; #ifdef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS mz_uint padding; #else mz_uint file_crc32; #endif } mz_zip_reader_extract_iter_state; /* -------- ZIP reading */ /* Inits a ZIP archive reader. */ /* These functions read and validate the archive's central directory. */ MINIZ_EXPORT mz_bool mz_zip_reader_init(mz_zip_archive *pZip, mz_uint64 size, mz_uint flags); MINIZ_EXPORT mz_bool mz_zip_reader_init_mem(mz_zip_archive *pZip, const void *pMem, size_t size, mz_uint flags); #ifndef MINIZ_NO_STDIO /* Read a archive from a disk file. */ /* file_start_ofs is the file offset where the archive actually begins, or 0. */ /* actual_archive_size is the true total size of the archive, which may be smaller than the file's actual size on disk. If zero the entire file is treated as the archive. */ MINIZ_EXPORT mz_bool mz_zip_reader_init_file(mz_zip_archive *pZip, const char *pFilename, mz_uint32 flags); MINIZ_EXPORT mz_bool mz_zip_reader_init_file_v2(mz_zip_archive *pZip, const char *pFilename, mz_uint flags, mz_uint64 file_start_ofs, mz_uint64 archive_size); /* Read an archive from an already opened FILE, beginning at the current file position. */ /* The archive is assumed to be archive_size bytes long. If archive_size is 0, then the entire rest of the file is assumed to contain the archive. */ /* The FILE will NOT be closed when mz_zip_reader_end() is called. */ MINIZ_EXPORT mz_bool mz_zip_reader_init_cfile(mz_zip_archive *pZip, MZ_FILE *pFile, mz_uint64 archive_size, mz_uint flags); #endif /* Ends archive reading, freeing all allocations, and closing the input archive file if mz_zip_reader_init_file() was used. */ MINIZ_EXPORT mz_bool mz_zip_reader_end(mz_zip_archive *pZip); /* -------- ZIP reading or writing */ /* Clears a mz_zip_archive struct to all zeros. */ /* Important: This must be done before passing the struct to any mz_zip functions. */ MINIZ_EXPORT void mz_zip_zero_struct(mz_zip_archive *pZip); MINIZ_EXPORT mz_zip_mode mz_zip_get_mode(mz_zip_archive *pZip); MINIZ_EXPORT mz_zip_type mz_zip_get_type(mz_zip_archive *pZip); /* Returns the total number of files in the archive. */ MINIZ_EXPORT mz_uint mz_zip_reader_get_num_files(mz_zip_archive *pZip); MINIZ_EXPORT mz_uint64 mz_zip_get_archive_size(mz_zip_archive *pZip); MINIZ_EXPORT mz_uint64 mz_zip_get_archive_file_start_offset(mz_zip_archive *pZip); MINIZ_EXPORT MZ_FILE *mz_zip_get_cfile(mz_zip_archive *pZip); /* Reads n bytes of raw archive data, starting at file offset file_ofs, to pBuf. */ MINIZ_EXPORT size_t mz_zip_read_archive_data(mz_zip_archive *pZip, mz_uint64 file_ofs, void *pBuf, size_t n); /* All mz_zip funcs set the m_last_error field in the mz_zip_archive struct. These functions retrieve/manipulate this field. */ /* Note that the m_last_error functionality is not thread safe. */ MINIZ_EXPORT mz_zip_error mz_zip_set_last_error(mz_zip_archive *pZip, mz_zip_error err_num); MINIZ_EXPORT mz_zip_error mz_zip_peek_last_error(mz_zip_archive *pZip); MINIZ_EXPORT mz_zip_error mz_zip_clear_last_error(mz_zip_archive *pZip); MINIZ_EXPORT mz_zip_error mz_zip_get_last_error(mz_zip_archive *pZip); MINIZ_EXPORT const char *mz_zip_get_error_string(mz_zip_error mz_err); /* MZ_TRUE if the archive file entry is a directory entry. */ MINIZ_EXPORT mz_bool mz_zip_reader_is_file_a_directory(mz_zip_archive *pZip, mz_uint file_index); /* MZ_TRUE if the file is encrypted/strong encrypted. */ MINIZ_EXPORT mz_bool mz_zip_reader_is_file_encrypted(mz_zip_archive *pZip, mz_uint file_index); /* MZ_TRUE if the compression method is supported, and the file is not encrypted, and the file is not a compressed patch file. */ MINIZ_EXPORT mz_bool mz_zip_reader_is_file_supported(mz_zip_archive *pZip, mz_uint file_index); /* Retrieves the filename of an archive file entry. */ /* Returns the number of bytes written to pFilename, or if filename_buf_size is 0 this function returns the number of bytes needed to fully store the filename. */ MINIZ_EXPORT mz_uint mz_zip_reader_get_filename(mz_zip_archive *pZip, mz_uint file_index, char *pFilename, mz_uint filename_buf_size); /* Attempts to locates a file in the archive's central directory. */ /* Valid flags: MZ_ZIP_FLAG_CASE_SENSITIVE, MZ_ZIP_FLAG_IGNORE_PATH */ /* Returns -1 if the file cannot be found. */ MINIZ_EXPORT int mz_zip_reader_locate_file(mz_zip_archive *pZip, const char *pName, const char *pComment, mz_uint flags); MINIZ_EXPORT mz_bool mz_zip_reader_locate_file_v2(mz_zip_archive *pZip, const char *pName, const char *pComment, mz_uint flags, mz_uint32 *file_index); /* Returns detailed information about an archive file entry. */ MINIZ_EXPORT mz_bool mz_zip_reader_file_stat(mz_zip_archive *pZip, mz_uint file_index, mz_zip_archive_file_stat *pStat); /* MZ_TRUE if the file is in zip64 format. */ /* A file is considered zip64 if it contained a zip64 end of central directory marker, or if it contained any zip64 extended file information fields in the central directory. */ MINIZ_EXPORT mz_bool mz_zip_is_zip64(mz_zip_archive *pZip); /* Returns the total central directory size in bytes. */ /* The current max supported size is <= MZ_UINT32_MAX. */ MINIZ_EXPORT size_t mz_zip_get_central_dir_size(mz_zip_archive *pZip); /* Extracts a archive file to a memory buffer using no memory allocation. */ /* There must be at least enough room on the stack to store the inflator's state (~34KB or so). */ MINIZ_EXPORT mz_bool mz_zip_reader_extract_to_mem_no_alloc(mz_zip_archive *pZip, mz_uint file_index, void *pBuf, size_t buf_size, mz_uint flags, void *pUser_read_buf, size_t user_read_buf_size); MINIZ_EXPORT mz_bool mz_zip_reader_extract_file_to_mem_no_alloc(mz_zip_archive *pZip, const char *pFilename, void *pBuf, size_t buf_size, mz_uint flags, void *pUser_read_buf, size_t user_read_buf_size); /* Extracts a archive file to a memory buffer. */ MINIZ_EXPORT mz_bool mz_zip_reader_extract_to_mem(mz_zip_archive *pZip, mz_uint file_index, void *pBuf, size_t buf_size, mz_uint flags); MINIZ_EXPORT mz_bool mz_zip_reader_extract_file_to_mem(mz_zip_archive *pZip, const char *pFilename, void *pBuf, size_t buf_size, mz_uint flags); /* Extracts a archive file to a dynamically allocated heap buffer. */ /* The memory will be allocated via the mz_zip_archive's alloc/realloc functions. */ /* Returns NULL and sets the last error on failure. */ MINIZ_EXPORT void *mz_zip_reader_extract_to_heap(mz_zip_archive *pZip, mz_uint file_index, size_t *pSize, mz_uint flags); MINIZ_EXPORT void *mz_zip_reader_extract_file_to_heap(mz_zip_archive *pZip, const char *pFilename, size_t *pSize, mz_uint flags); /* Extracts a archive file using a callback function to output the file's data. */ MINIZ_EXPORT mz_bool mz_zip_reader_extract_to_callback(mz_zip_archive *pZip, mz_uint file_index, mz_file_write_func pCallback, void *pOpaque, mz_uint flags); MINIZ_EXPORT mz_bool mz_zip_reader_extract_file_to_callback(mz_zip_archive *pZip, const char *pFilename, mz_file_write_func pCallback, void *pOpaque, mz_uint flags); /* Extract a file iteratively */ MINIZ_EXPORT mz_zip_reader_extract_iter_state* mz_zip_reader_extract_iter_new(mz_zip_archive *pZip, mz_uint file_index, mz_uint flags); MINIZ_EXPORT mz_zip_reader_extract_iter_state* mz_zip_reader_extract_file_iter_new(mz_zip_archive *pZip, const char *pFilename, mz_uint flags); MINIZ_EXPORT size_t mz_zip_reader_extract_iter_read(mz_zip_reader_extract_iter_state* pState, void* pvBuf, size_t buf_size); MINIZ_EXPORT mz_bool mz_zip_reader_extract_iter_free(mz_zip_reader_extract_iter_state* pState); #ifndef MINIZ_NO_STDIO /* Extracts a archive file to a disk file and sets its last accessed and modified times. */ /* This function only extracts files, not archive directory records. */ MINIZ_EXPORT mz_bool mz_zip_reader_extract_to_file(mz_zip_archive *pZip, mz_uint file_index, const char *pDst_filename, mz_uint flags); MINIZ_EXPORT mz_bool mz_zip_reader_extract_file_to_file(mz_zip_archive *pZip, const char *pArchive_filename, const char *pDst_filename, mz_uint flags); /* Extracts a archive file starting at the current position in the destination FILE stream. */ MINIZ_EXPORT mz_bool mz_zip_reader_extract_to_cfile(mz_zip_archive *pZip, mz_uint file_index, MZ_FILE *File, mz_uint flags); MINIZ_EXPORT mz_bool mz_zip_reader_extract_file_to_cfile(mz_zip_archive *pZip, const char *pArchive_filename, MZ_FILE *pFile, mz_uint flags); #endif #if 0 /* TODO */ typedef void *mz_zip_streaming_extract_state_ptr; mz_zip_streaming_extract_state_ptr mz_zip_streaming_extract_begin(mz_zip_archive *pZip, mz_uint file_index, mz_uint flags); mz_uint64 mz_zip_streaming_extract_get_size(mz_zip_archive *pZip, mz_zip_streaming_extract_state_ptr pState); mz_uint64 mz_zip_streaming_extract_get_cur_ofs(mz_zip_archive *pZip, mz_zip_streaming_extract_state_ptr pState); mz_bool mz_zip_streaming_extract_seek(mz_zip_archive *pZip, mz_zip_streaming_extract_state_ptr pState, mz_uint64 new_ofs); size_t mz_zip_streaming_extract_read(mz_zip_archive *pZip, mz_zip_streaming_extract_state_ptr pState, void *pBuf, size_t buf_size); mz_bool mz_zip_streaming_extract_end(mz_zip_archive *pZip, mz_zip_streaming_extract_state_ptr pState); #endif /* This function compares the archive's local headers, the optional local zip64 extended information block, and the optional descriptor following the compressed data vs. the data in the central directory. */ /* It also validates that each file can be successfully uncompressed unless the MZ_ZIP_FLAG_VALIDATE_HEADERS_ONLY is specified. */ MINIZ_EXPORT mz_bool mz_zip_validate_file(mz_zip_archive *pZip, mz_uint file_index, mz_uint flags); /* Validates an entire archive by calling mz_zip_validate_file() on each file. */ MINIZ_EXPORT mz_bool mz_zip_validate_archive(mz_zip_archive *pZip, mz_uint flags); /* Misc utils/helpers, valid for ZIP reading or writing */ MINIZ_EXPORT mz_bool mz_zip_validate_mem_archive(const void *pMem, size_t size, mz_uint flags, mz_zip_error *pErr); #ifndef MINIZ_NO_STDIO MINIZ_EXPORT mz_bool mz_zip_validate_file_archive(const char *pFilename, mz_uint flags, mz_zip_error *pErr); #endif /* Universal end function - calls either mz_zip_reader_end() or mz_zip_writer_end(). */ MINIZ_EXPORT mz_bool mz_zip_end(mz_zip_archive *pZip); /* -------- ZIP writing */ #ifndef MINIZ_NO_ARCHIVE_WRITING_APIS /* Inits a ZIP archive writer. */ /*Set pZip->m_pWrite (and pZip->m_pIO_opaque) before calling mz_zip_writer_init or mz_zip_writer_init_v2*/ /*The output is streamable, i.e. file_ofs in mz_file_write_func always increases only by n*/ MINIZ_EXPORT mz_bool mz_zip_writer_init(mz_zip_archive *pZip, mz_uint64 existing_size); MINIZ_EXPORT mz_bool mz_zip_writer_init_v2(mz_zip_archive *pZip, mz_uint64 existing_size, mz_uint flags); MINIZ_EXPORT mz_bool mz_zip_writer_init_heap(mz_zip_archive *pZip, size_t size_to_reserve_at_beginning, size_t initial_allocation_size); MINIZ_EXPORT mz_bool mz_zip_writer_init_heap_v2(mz_zip_archive *pZip, size_t size_to_reserve_at_beginning, size_t initial_allocation_size, mz_uint flags); #ifndef MINIZ_NO_STDIO MINIZ_EXPORT mz_bool mz_zip_writer_init_file(mz_zip_archive *pZip, const char *pFilename, mz_uint64 size_to_reserve_at_beginning); MINIZ_EXPORT mz_bool mz_zip_writer_init_file_v2(mz_zip_archive *pZip, const char *pFilename, mz_uint64 size_to_reserve_at_beginning, mz_uint flags); MINIZ_EXPORT mz_bool mz_zip_writer_init_cfile(mz_zip_archive *pZip, MZ_FILE *pFile, mz_uint flags); #endif /* Converts a ZIP archive reader object into a writer object, to allow efficient in-place file appends to occur on an existing archive. */ /* For archives opened using mz_zip_reader_init_file, pFilename must be the archive's filename so it can be reopened for writing. If the file can't be reopened, mz_zip_reader_end() will be called. */ /* For archives opened using mz_zip_reader_init_mem, the memory block must be growable using the realloc callback (which defaults to realloc unless you've overridden it). */ /* Finally, for archives opened using mz_zip_reader_init, the mz_zip_archive's user provided m_pWrite function cannot be NULL. */ /* Note: In-place archive modification is not recommended unless you know what you're doing, because if execution stops or something goes wrong before */ /* the archive is finalized the file's central directory will be hosed. */ MINIZ_EXPORT mz_bool mz_zip_writer_init_from_reader(mz_zip_archive *pZip, const char *pFilename); MINIZ_EXPORT mz_bool mz_zip_writer_init_from_reader_v2(mz_zip_archive *pZip, const char *pFilename, mz_uint flags); /* Adds the contents of a memory buffer to an archive. These functions record the current local time into the archive. */ /* To add a directory entry, call this method with an archive name ending in a forwardslash with an empty buffer. */ /* level_and_flags - compression level (0-10, see MZ_BEST_SPEED, MZ_BEST_COMPRESSION, etc.) logically OR'd with zero or more mz_zip_flags, or just set to MZ_DEFAULT_COMPRESSION. */ MINIZ_EXPORT mz_bool mz_zip_writer_add_mem(mz_zip_archive *pZip, const char *pArchive_name, const void *pBuf, size_t buf_size, mz_uint level_and_flags); /* Like mz_zip_writer_add_mem(), except you can specify a file comment field, and optionally supply the function with already compressed data. */ /* uncomp_size/uncomp_crc32 are only used if the MZ_ZIP_FLAG_COMPRESSED_DATA flag is specified. */ MINIZ_EXPORT mz_bool mz_zip_writer_add_mem_ex(mz_zip_archive *pZip, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags, mz_uint64 uncomp_size, mz_uint32 uncomp_crc32); MINIZ_EXPORT mz_bool mz_zip_writer_add_mem_ex_v2(mz_zip_archive *pZip, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags, mz_uint64 uncomp_size, mz_uint32 uncomp_crc32, MZ_TIME_T *last_modified, const char *user_extra_data_local, mz_uint user_extra_data_local_len, const char *user_extra_data_central, mz_uint user_extra_data_central_len); /* Adds the contents of a file to an archive. This function also records the disk file's modified time into the archive. */ /* File data is supplied via a read callback function. User mz_zip_writer_add_(c)file to add a file directly.*/ MINIZ_EXPORT mz_bool mz_zip_writer_add_read_buf_callback(mz_zip_archive *pZip, const char *pArchive_name, mz_file_read_func read_callback, void* callback_opaque, mz_uint64 max_size, const MZ_TIME_T *pFile_time, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags, const char *user_extra_data_local, mz_uint user_extra_data_local_len, const char *user_extra_data_central, mz_uint user_extra_data_central_len); #ifndef MINIZ_NO_STDIO /* Adds the contents of a disk file to an archive. This function also records the disk file's modified time into the archive. */ /* level_and_flags - compression level (0-10, see MZ_BEST_SPEED, MZ_BEST_COMPRESSION, etc.) logically OR'd with zero or more mz_zip_flags, or just set to MZ_DEFAULT_COMPRESSION. */ MINIZ_EXPORT mz_bool mz_zip_writer_add_file(mz_zip_archive *pZip, const char *pArchive_name, const char *pSrc_filename, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags); /* Like mz_zip_writer_add_file(), except the file data is read from the specified FILE stream. */ MINIZ_EXPORT mz_bool mz_zip_writer_add_cfile(mz_zip_archive *pZip, const char *pArchive_name, MZ_FILE *pSrc_file, mz_uint64 max_size, const MZ_TIME_T *pFile_time, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags, const char *user_extra_data_local, mz_uint user_extra_data_local_len, const char *user_extra_data_central, mz_uint user_extra_data_central_len); #endif /* Adds a file to an archive by fully cloning the data from another archive. */ /* This function fully clones the source file's compressed data (no recompression), along with its full filename, extra data (it may add or modify the zip64 local header extra data field), and the optional descriptor following the compressed data. */ MINIZ_EXPORT mz_bool mz_zip_writer_add_from_zip_reader(mz_zip_archive *pZip, mz_zip_archive *pSource_zip, mz_uint src_file_index); /* Finalizes the archive by writing the central directory records followed by the end of central directory record. */ /* After an archive is finalized, the only valid call on the mz_zip_archive struct is mz_zip_writer_end(). */ /* An archive must be manually finalized by calling this function for it to be valid. */ MINIZ_EXPORT mz_bool mz_zip_writer_finalize_archive(mz_zip_archive *pZip); /* Finalizes a heap archive, returning a pointer to the heap block and its size. */ /* The heap block will be allocated using the mz_zip_archive's alloc/realloc callbacks. */ MINIZ_EXPORT mz_bool mz_zip_writer_finalize_heap_archive(mz_zip_archive *pZip, void **ppBuf, size_t *pSize); /* Ends archive writing, freeing all allocations, and closing the output file if mz_zip_writer_init_file() was used. */ /* Note for the archive to be valid, it *must* have been finalized before ending (this function will not do it for you). */ MINIZ_EXPORT mz_bool mz_zip_writer_end(mz_zip_archive *pZip); /* -------- Misc. high-level helper functions: */ /* mz_zip_add_mem_to_archive_file_in_place() efficiently (but not atomically) appends a memory blob to a ZIP archive. */ /* Note this is NOT a fully safe operation. If it crashes or dies in some way your archive can be left in a screwed up state (without a central directory). */ /* level_and_flags - compression level (0-10, see MZ_BEST_SPEED, MZ_BEST_COMPRESSION, etc.) logically OR'd with zero or more mz_zip_flags, or just set to MZ_DEFAULT_COMPRESSION. */ /* TODO: Perhaps add an option to leave the existing central dir in place in case the add dies? We could then truncate the file (so the old central dir would be at the end) if something goes wrong. */ MINIZ_EXPORT mz_bool mz_zip_add_mem_to_archive_file_in_place(const char *pZip_filename, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags); MINIZ_EXPORT mz_bool mz_zip_add_mem_to_archive_file_in_place_v2(const char *pZip_filename, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags, mz_zip_error *pErr); #ifndef MINIZ_NO_STDIO /* Reads a single file from an archive into a heap block. */ /* If pComment is not NULL, only the file with the specified comment will be extracted. */ /* Returns NULL on failure. */ MINIZ_EXPORT void *mz_zip_extract_archive_file_to_heap(const char *pZip_filename, const char *pArchive_name, size_t *pSize, mz_uint flags); MINIZ_EXPORT void *mz_zip_extract_archive_file_to_heap_v2(const char *pZip_filename, const char *pArchive_name, const char *pComment, size_t *pSize, mz_uint flags, mz_zip_error *pErr); #endif #endif /* #ifndef MINIZ_NO_ARCHIVE_WRITING_APIS */ #ifdef __cplusplus } #endif #endif /* MINIZ_NO_ARCHIVE_APIS */ pr0m1th3as-datatypes-9c9a8d3/src/ods/miniz_LICENSE000066400000000000000000000022401522766574100216420ustar00rootroot00000000000000Copyright 2013-2014 RAD Game Tools and Valve Software Copyright 2010-2014 Rich Geldreich and Tenacious Software LLC All Rights Reserved. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. pr0m1th3as-datatypes-9c9a8d3/src/ods/pugiconfig.hpp000066400000000000000000000055201522766574100222760ustar00rootroot00000000000000/** * pugixml parser - version 1.14 * -------------------------------------------------------- * Copyright (C) 2006-2023, by Arseny Kapoulkine (arseny.kapoulkine@gmail.com) * Report bugs and download new versions at https://pugixml.org/ * * This library is distributed under the MIT License. See notice at the end * of this file. * * This work is based on the pugxml parser, which is: * Copyright (C) 2003, by Kristen Wegner (kristen@tima.net) */ #ifndef HEADER_PUGICONFIG_HPP #define HEADER_PUGICONFIG_HPP // Uncomment this to enable wchar_t mode // #define PUGIXML_WCHAR_MODE // Uncomment this to enable compact mode // #define PUGIXML_COMPACT // Uncomment this to disable XPath // #define PUGIXML_NO_XPATH // Uncomment this to disable STL // #define PUGIXML_NO_STL // Uncomment this to disable exceptions // #define PUGIXML_NO_EXCEPTIONS // Set this to control attributes for public classes/functions, i.e.: // #define PUGIXML_API __declspec(dllexport) // to export all public symbols from DLL // #define PUGIXML_CLASS __declspec(dllimport) // to import all classes from DLL // #define PUGIXML_FUNCTION __fastcall // to set calling conventions to all public functions to fastcall // In absence of PUGIXML_CLASS/PUGIXML_FUNCTION definitions PUGIXML_API is used instead // Tune these constants to adjust memory-related behavior // #define PUGIXML_MEMORY_PAGE_SIZE 32768 // #define PUGIXML_MEMORY_OUTPUT_STACK 10240 // #define PUGIXML_MEMORY_XPATH_PAGE_SIZE 4096 // Tune this constant to adjust max nesting for XPath queries // #define PUGIXML_XPATH_DEPTH_LIMIT 1024 // Uncomment this to switch to header-only version // #define PUGIXML_HEADER_ONLY // Uncomment this to enable long long support // #define PUGIXML_HAS_LONG_LONG #endif /** * Copyright (c) 2006-2023 Arseny Kapoulkine * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ pr0m1th3as-datatypes-9c9a8d3/src/ods/pugixml.cpp000066400000000000000000012453131522766574100216330ustar00rootroot00000000000000/** * pugixml parser - version 1.14 * -------------------------------------------------------- * Copyright (C) 2006-2023, by Arseny Kapoulkine (arseny.kapoulkine@gmail.com) * Report bugs and download new versions at https://pugixml.org/ * * This library is distributed under the MIT License. See notice at the end * of this file. * * This work is based on the pugxml parser, which is: * Copyright (C) 2003, by Kristen Wegner (kristen@tima.net) */ #ifndef SOURCE_PUGIXML_CPP #define SOURCE_PUGIXML_CPP #include "pugixml.hpp" #include #include #include #include #include #ifdef PUGIXML_WCHAR_MODE # include #endif #ifndef PUGIXML_NO_XPATH # include # include #endif #ifndef PUGIXML_NO_STL # include # include # include #endif // For placement new #include // For load_file #if defined(__linux__) || defined(__APPLE__) #include #endif #ifdef _MSC_VER # pragma warning(push) # pragma warning(disable: 4127) // conditional expression is constant # pragma warning(disable: 4324) // structure was padded due to __declspec(align()) # pragma warning(disable: 4702) // unreachable code # pragma warning(disable: 4996) // this function or variable may be unsafe #endif #if defined(_MSC_VER) && defined(__c2__) # pragma clang diagnostic push # pragma clang diagnostic ignored "-Wdeprecated" // this function or variable may be unsafe #endif #ifdef __INTEL_COMPILER # pragma warning(disable: 177) // function was declared but never referenced # pragma warning(disable: 279) // controlling expression is constant # pragma warning(disable: 1478 1786) // function was declared "deprecated" # pragma warning(disable: 1684) // conversion from pointer to same-sized integral type #endif #if defined(__BORLANDC__) && defined(PUGIXML_HEADER_ONLY) # pragma warn -8080 // symbol is declared but never used; disabling this inside push/pop bracket does not make the warning go away #endif #ifdef __BORLANDC__ # pragma option push # pragma warn -8008 // condition is always false # pragma warn -8066 // unreachable code #endif #ifdef __SNC__ // Using diag_push/diag_pop does not disable the warnings inside templates due to a compiler bug # pragma diag_suppress=178 // function was declared but never referenced # pragma diag_suppress=237 // controlling expression is constant #endif #ifdef __TI_COMPILER_VERSION__ # pragma diag_suppress 179 // function was declared but never referenced #endif // Inlining controls #if defined(_MSC_VER) && _MSC_VER >= 1300 # define PUGI_IMPL_NO_INLINE __declspec(noinline) #elif defined(__GNUC__) # define PUGI_IMPL_NO_INLINE __attribute__((noinline)) #else # define PUGI_IMPL_NO_INLINE #endif // Branch weight controls #if defined(__GNUC__) && !defined(__c2__) # define PUGI_IMPL_UNLIKELY(cond) __builtin_expect(cond, 0) #else # define PUGI_IMPL_UNLIKELY(cond) (cond) #endif // Simple static assertion #define PUGI_IMPL_STATIC_ASSERT(cond) { static const char condition_failed[(cond) ? 1 : -1] = {0}; (void)condition_failed[0]; } // Digital Mars C++ bug workaround for passing char loaded from memory via stack #ifdef __DMC__ # define PUGI_IMPL_DMC_VOLATILE volatile #else # define PUGI_IMPL_DMC_VOLATILE #endif // Integer sanitizer workaround; we only apply this for clang since gcc8 has no_sanitize but not unsigned-integer-overflow and produces "attribute directive ignored" warnings #if defined(__clang__) && defined(__has_attribute) # if __has_attribute(no_sanitize) # define PUGI_IMPL_UNSIGNED_OVERFLOW __attribute__((no_sanitize("unsigned-integer-overflow"))) # else # define PUGI_IMPL_UNSIGNED_OVERFLOW # endif #else # define PUGI_IMPL_UNSIGNED_OVERFLOW #endif // Borland C++ bug workaround for not defining ::memcpy depending on header include order (can't always use std::memcpy because some compilers don't have it at all) #if defined(__BORLANDC__) && !defined(__MEM_H_USING_LIST) using std::memcpy; using std::memmove; using std::memset; #endif // Old versions of GCC do not define ::malloc and ::free depending on header include order #if defined(__GNUC__) && (__GNUC__ < 3 || (__GNUC__ == 3 && __GNUC_MINOR__ < 4)) using std::malloc; using std::free; #endif // Some MinGW/GCC versions have headers that erroneously omit LLONG_MIN/LLONG_MAX/ULLONG_MAX definitions from limits.h in some configurations #if defined(PUGIXML_HAS_LONG_LONG) && defined(__GNUC__) && !defined(LLONG_MAX) && !defined(LLONG_MIN) && !defined(ULLONG_MAX) # define LLONG_MIN (-LLONG_MAX - 1LL) # define LLONG_MAX __LONG_LONG_MAX__ # define ULLONG_MAX (LLONG_MAX * 2ULL + 1ULL) #endif // In some environments MSVC is a compiler but the CRT lacks certain MSVC-specific features #if defined(_MSC_VER) && !defined(__S3E__) && !defined(_WIN32_WCE) # define PUGI_IMPL_MSVC_CRT_VERSION _MSC_VER #elif defined(_WIN32_WCE) # define PUGI_IMPL_MSVC_CRT_VERSION 1310 // MSVC7.1 #endif // Not all platforms have snprintf; we define a wrapper that uses snprintf if possible. This only works with buffers with a known size. #if __cplusplus >= 201103 # define PUGI_IMPL_SNPRINTF(buf, ...) snprintf(buf, sizeof(buf), __VA_ARGS__) #elif defined(PUGI_IMPL_MSVC_CRT_VERSION) && PUGI_IMPL_MSVC_CRT_VERSION >= 1400 # define PUGI_IMPL_SNPRINTF(buf, ...) _snprintf_s(buf, _countof(buf), _TRUNCATE, __VA_ARGS__) #elif defined(__APPLE__) && __clang_major__ >= 14 // Xcode 14 marks sprintf as deprecated while still using C++98 by default # define PUGI_IMPL_SNPRINTF(buf, fmt, arg1, arg2) snprintf(buf, sizeof(buf), fmt, arg1, arg2) #else # define PUGI_IMPL_SNPRINTF sprintf #endif // We put implementation details into an anonymous namespace in source mode, but have to keep it in non-anonymous namespace in header-only mode to prevent binary bloat. #ifdef PUGIXML_HEADER_ONLY # define PUGI_IMPL_NS_BEGIN namespace pugi { namespace impl { # define PUGI_IMPL_NS_END } } # define PUGI_IMPL_FN inline # define PUGI_IMPL_FN_NO_INLINE inline #else # if defined(_MSC_VER) && _MSC_VER < 1300 // MSVC6 seems to have an amusing bug with anonymous namespaces inside namespaces # define PUGI_IMPL_NS_BEGIN namespace pugi { namespace impl { # define PUGI_IMPL_NS_END } } # else # define PUGI_IMPL_NS_BEGIN namespace pugi { namespace impl { namespace { # define PUGI_IMPL_NS_END } } } # endif # define PUGI_IMPL_FN # define PUGI_IMPL_FN_NO_INLINE PUGI_IMPL_NO_INLINE #endif // uintptr_t #if (defined(_MSC_VER) && _MSC_VER < 1600) || (defined(__BORLANDC__) && __BORLANDC__ < 0x561) namespace pugi { # ifndef _UINTPTR_T_DEFINED typedef size_t uintptr_t; # endif typedef unsigned __int8 uint8_t; typedef unsigned __int16 uint16_t; typedef unsigned __int32 uint32_t; } #else # include #endif // Memory allocation PUGI_IMPL_NS_BEGIN PUGI_IMPL_FN void* default_allocate(size_t size) { return malloc(size); } PUGI_IMPL_FN void default_deallocate(void* ptr) { free(ptr); } template struct xml_memory_management_function_storage { static allocation_function allocate; static deallocation_function deallocate; }; // Global allocation functions are stored in class statics so that in header mode linker deduplicates them // Without a template<> we'll get multiple definitions of the same static template allocation_function xml_memory_management_function_storage::allocate = default_allocate; template deallocation_function xml_memory_management_function_storage::deallocate = default_deallocate; typedef xml_memory_management_function_storage xml_memory; PUGI_IMPL_NS_END // String utilities PUGI_IMPL_NS_BEGIN // Get string length PUGI_IMPL_FN size_t strlength(const char_t* s) { assert(s); #ifdef PUGIXML_WCHAR_MODE return wcslen(s); #else return strlen(s); #endif } // Compare two strings PUGI_IMPL_FN bool strequal(const char_t* src, const char_t* dst) { assert(src && dst); #ifdef PUGIXML_WCHAR_MODE return wcscmp(src, dst) == 0; #else return strcmp(src, dst) == 0; #endif } // Compare lhs with [rhs_begin, rhs_end) PUGI_IMPL_FN bool strequalrange(const char_t* lhs, const char_t* rhs, size_t count) { for (size_t i = 0; i < count; ++i) if (lhs[i] != rhs[i]) return false; return lhs[count] == 0; } // Get length of wide string, even if CRT lacks wide character support PUGI_IMPL_FN size_t strlength_wide(const wchar_t* s) { assert(s); #ifdef PUGIXML_WCHAR_MODE return wcslen(s); #else const wchar_t* end = s; while (*end) end++; return static_cast(end - s); #endif } PUGI_IMPL_NS_END // auto_ptr-like object for exception recovery PUGI_IMPL_NS_BEGIN template struct auto_deleter { typedef void (*D)(T*); T* data; D deleter; auto_deleter(T* data_, D deleter_): data(data_), deleter(deleter_) { } ~auto_deleter() { if (data) deleter(data); } T* release() { T* result = data; data = 0; return result; } }; PUGI_IMPL_NS_END #ifdef PUGIXML_COMPACT PUGI_IMPL_NS_BEGIN class compact_hash_table { public: compact_hash_table(): _items(0), _capacity(0), _count(0) { } void clear() { if (_items) { xml_memory::deallocate(_items); _items = 0; _capacity = 0; _count = 0; } } void* find(const void* key) { if (_capacity == 0) return 0; item_t* item = get_item(key); assert(item); assert(item->key == key || (item->key == 0 && item->value == 0)); return item->value; } void insert(const void* key, void* value) { assert(_capacity != 0 && _count < _capacity - _capacity / 4); item_t* item = get_item(key); assert(item); if (item->key == 0) { _count++; item->key = key; } item->value = value; } bool reserve(size_t extra = 16) { if (_count + extra >= _capacity - _capacity / 4) return rehash(_count + extra); return true; } private: struct item_t { const void* key; void* value; }; item_t* _items; size_t _capacity; size_t _count; bool rehash(size_t count); item_t* get_item(const void* key) { assert(key); assert(_capacity > 0); size_t hashmod = _capacity - 1; size_t bucket = hash(key) & hashmod; for (size_t probe = 0; probe <= hashmod; ++probe) { item_t& probe_item = _items[bucket]; if (probe_item.key == key || probe_item.key == 0) return &probe_item; // hash collision, quadratic probing bucket = (bucket + probe + 1) & hashmod; } assert(false && "Hash table is full"); // unreachable return 0; } static PUGI_IMPL_UNSIGNED_OVERFLOW unsigned int hash(const void* key) { unsigned int h = static_cast(reinterpret_cast(key) & 0xffffffff); // MurmurHash3 32-bit finalizer h ^= h >> 16; h *= 0x85ebca6bu; h ^= h >> 13; h *= 0xc2b2ae35u; h ^= h >> 16; return h; } }; PUGI_IMPL_FN_NO_INLINE bool compact_hash_table::rehash(size_t count) { size_t capacity = 32; while (count >= capacity - capacity / 4) capacity *= 2; compact_hash_table rt; rt._capacity = capacity; rt._items = static_cast(xml_memory::allocate(sizeof(item_t) * capacity)); if (!rt._items) return false; memset(rt._items, 0, sizeof(item_t) * capacity); for (size_t i = 0; i < _capacity; ++i) if (_items[i].key) rt.insert(_items[i].key, _items[i].value); if (_items) xml_memory::deallocate(_items); _capacity = capacity; _items = rt._items; assert(_count == rt._count); return true; } PUGI_IMPL_NS_END #endif PUGI_IMPL_NS_BEGIN #ifdef PUGIXML_COMPACT static const uintptr_t xml_memory_block_alignment = 4; #else static const uintptr_t xml_memory_block_alignment = sizeof(void*); #endif // extra metadata bits static const uintptr_t xml_memory_page_contents_shared_mask = 64; static const uintptr_t xml_memory_page_name_allocated_mask = 32; static const uintptr_t xml_memory_page_value_allocated_mask = 16; static const uintptr_t xml_memory_page_type_mask = 15; // combined masks for string uniqueness static const uintptr_t xml_memory_page_name_allocated_or_shared_mask = xml_memory_page_name_allocated_mask | xml_memory_page_contents_shared_mask; static const uintptr_t xml_memory_page_value_allocated_or_shared_mask = xml_memory_page_value_allocated_mask | xml_memory_page_contents_shared_mask; #ifdef PUGIXML_COMPACT #define PUGI_IMPL_GETHEADER_IMPL(object, page, flags) // unused #define PUGI_IMPL_GETPAGE_IMPL(header) (header).get_page() #else #define PUGI_IMPL_GETHEADER_IMPL(object, page, flags) (((reinterpret_cast(object) - reinterpret_cast(page)) << 8) | (flags)) // this macro casts pointers through void* to avoid 'cast increases required alignment of target type' warnings #define PUGI_IMPL_GETPAGE_IMPL(header) static_cast(const_cast(static_cast(reinterpret_cast(&header) - (header >> 8)))) #endif #define PUGI_IMPL_GETPAGE(n) PUGI_IMPL_GETPAGE_IMPL((n)->header) #define PUGI_IMPL_NODETYPE(n) static_cast((n)->header & impl::xml_memory_page_type_mask) struct xml_allocator; struct xml_memory_page { static xml_memory_page* construct(void* memory) { xml_memory_page* result = static_cast(memory); result->allocator = 0; result->prev = 0; result->next = 0; result->busy_size = 0; result->freed_size = 0; #ifdef PUGIXML_COMPACT result->compact_string_base = 0; result->compact_shared_parent = 0; result->compact_page_marker = 0; #endif return result; } xml_allocator* allocator; xml_memory_page* prev; xml_memory_page* next; size_t busy_size; size_t freed_size; #ifdef PUGIXML_COMPACT char_t* compact_string_base; void* compact_shared_parent; uint32_t* compact_page_marker; #endif }; static const size_t xml_memory_page_size = #ifdef PUGIXML_MEMORY_PAGE_SIZE (PUGIXML_MEMORY_PAGE_SIZE) #else 32768 #endif - sizeof(xml_memory_page); struct xml_memory_string_header { uint16_t page_offset; // offset from page->data uint16_t full_size; // 0 if string occupies whole page }; struct xml_allocator { xml_allocator(xml_memory_page* root): _root(root), _busy_size(root->busy_size) { #ifdef PUGIXML_COMPACT _hash = 0; #endif } xml_memory_page* allocate_page(size_t data_size) { size_t size = sizeof(xml_memory_page) + data_size; // allocate block with some alignment, leaving memory for worst-case padding void* memory = xml_memory::allocate(size); if (!memory) return 0; // prepare page structure xml_memory_page* page = xml_memory_page::construct(memory); assert(page); assert(this == _root->allocator); page->allocator = this; return page; } static void deallocate_page(xml_memory_page* page) { xml_memory::deallocate(page); } void* allocate_memory_oob(size_t size, xml_memory_page*& out_page); void* allocate_memory(size_t size, xml_memory_page*& out_page) { if (PUGI_IMPL_UNLIKELY(_busy_size + size > xml_memory_page_size)) return allocate_memory_oob(size, out_page); void* buf = reinterpret_cast(_root) + sizeof(xml_memory_page) + _busy_size; _busy_size += size; out_page = _root; return buf; } #ifdef PUGIXML_COMPACT void* allocate_object(size_t size, xml_memory_page*& out_page) { void* result = allocate_memory(size + sizeof(uint32_t), out_page); if (!result) return 0; // adjust for marker ptrdiff_t offset = static_cast(result) - reinterpret_cast(out_page->compact_page_marker); if (PUGI_IMPL_UNLIKELY(static_cast(offset) >= 256 * xml_memory_block_alignment)) { // insert new marker uint32_t* marker = static_cast(result); *marker = static_cast(reinterpret_cast(marker) - reinterpret_cast(out_page)); out_page->compact_page_marker = marker; // since we don't reuse the page space until we reallocate it, we can just pretend that we freed the marker block // this will make sure deallocate_memory correctly tracks the size out_page->freed_size += sizeof(uint32_t); return marker + 1; } else { // roll back uint32_t part _busy_size -= sizeof(uint32_t); return result; } } #else void* allocate_object(size_t size, xml_memory_page*& out_page) { return allocate_memory(size, out_page); } #endif void deallocate_memory(void* ptr, size_t size, xml_memory_page* page) { if (page == _root) page->busy_size = _busy_size; assert(ptr >= reinterpret_cast(page) + sizeof(xml_memory_page) && ptr < reinterpret_cast(page) + sizeof(xml_memory_page) + page->busy_size); (void)!ptr; page->freed_size += size; assert(page->freed_size <= page->busy_size); if (page->freed_size == page->busy_size) { if (page->next == 0) { assert(_root == page); // top page freed, just reset sizes page->busy_size = 0; page->freed_size = 0; #ifdef PUGIXML_COMPACT // reset compact state to maximize efficiency page->compact_string_base = 0; page->compact_shared_parent = 0; page->compact_page_marker = 0; #endif _busy_size = 0; } else { assert(_root != page); assert(page->prev); // remove from the list page->prev->next = page->next; page->next->prev = page->prev; // deallocate deallocate_page(page); } } } char_t* allocate_string(size_t length) { static const size_t max_encoded_offset = (1 << 16) * xml_memory_block_alignment; PUGI_IMPL_STATIC_ASSERT(xml_memory_page_size <= max_encoded_offset); // allocate memory for string and header block size_t size = sizeof(xml_memory_string_header) + length * sizeof(char_t); // round size up to block alignment boundary size_t full_size = (size + (xml_memory_block_alignment - 1)) & ~(xml_memory_block_alignment - 1); xml_memory_page* page; xml_memory_string_header* header = static_cast(allocate_memory(full_size, page)); if (!header) return 0; // setup header ptrdiff_t page_offset = reinterpret_cast(header) - reinterpret_cast(page) - sizeof(xml_memory_page); assert(page_offset % xml_memory_block_alignment == 0); assert(page_offset >= 0 && static_cast(page_offset) < max_encoded_offset); header->page_offset = static_cast(static_cast(page_offset) / xml_memory_block_alignment); // full_size == 0 for large strings that occupy the whole page assert(full_size % xml_memory_block_alignment == 0); assert(full_size < max_encoded_offset || (page->busy_size == full_size && page_offset == 0)); header->full_size = static_cast(full_size < max_encoded_offset ? full_size / xml_memory_block_alignment : 0); // round-trip through void* to avoid 'cast increases required alignment of target type' warning // header is guaranteed a pointer-sized alignment, which should be enough for char_t return static_cast(static_cast(header + 1)); } void deallocate_string(char_t* string) { // this function casts pointers through void* to avoid 'cast increases required alignment of target type' warnings // we're guaranteed the proper (pointer-sized) alignment on the input string if it was allocated via allocate_string // get header xml_memory_string_header* header = static_cast(static_cast(string)) - 1; assert(header); // deallocate size_t page_offset = sizeof(xml_memory_page) + header->page_offset * xml_memory_block_alignment; xml_memory_page* page = reinterpret_cast(static_cast(reinterpret_cast(header) - page_offset)); // if full_size == 0 then this string occupies the whole page size_t full_size = header->full_size == 0 ? page->busy_size : header->full_size * xml_memory_block_alignment; deallocate_memory(header, full_size, page); } bool reserve() { #ifdef PUGIXML_COMPACT return _hash->reserve(); #else return true; #endif } xml_memory_page* _root; size_t _busy_size; #ifdef PUGIXML_COMPACT compact_hash_table* _hash; #endif }; PUGI_IMPL_FN_NO_INLINE void* xml_allocator::allocate_memory_oob(size_t size, xml_memory_page*& out_page) { const size_t large_allocation_threshold = xml_memory_page_size / 4; xml_memory_page* page = allocate_page(size <= large_allocation_threshold ? xml_memory_page_size : size); out_page = page; if (!page) return 0; if (size <= large_allocation_threshold) { _root->busy_size = _busy_size; // insert page at the end of linked list page->prev = _root; _root->next = page; _root = page; _busy_size = size; } else { // insert page before the end of linked list, so that it is deleted as soon as possible // the last page is not deleted even if it's empty (see deallocate_memory) assert(_root->prev); page->prev = _root->prev; page->next = _root; _root->prev->next = page; _root->prev = page; page->busy_size = size; } return reinterpret_cast(page) + sizeof(xml_memory_page); } PUGI_IMPL_NS_END #ifdef PUGIXML_COMPACT PUGI_IMPL_NS_BEGIN static const uintptr_t compact_alignment_log2 = 2; static const uintptr_t compact_alignment = 1 << compact_alignment_log2; class compact_header { public: compact_header(xml_memory_page* page, unsigned int flags) { PUGI_IMPL_STATIC_ASSERT(xml_memory_block_alignment == compact_alignment); ptrdiff_t offset = (reinterpret_cast(this) - reinterpret_cast(page->compact_page_marker)); assert(offset % compact_alignment == 0 && static_cast(offset) < 256 * compact_alignment); _page = static_cast(offset >> compact_alignment_log2); _flags = static_cast(flags); } void operator&=(uintptr_t mod) { _flags &= static_cast(mod); } void operator|=(uintptr_t mod) { _flags |= static_cast(mod); } uintptr_t operator&(uintptr_t mod) const { return _flags & mod; } xml_memory_page* get_page() const { // round-trip through void* to silence 'cast increases required alignment of target type' warnings const char* page_marker = reinterpret_cast(this) - (_page << compact_alignment_log2); const char* page = page_marker - *reinterpret_cast(static_cast(page_marker)); return const_cast(reinterpret_cast(static_cast(page))); } private: unsigned char _page; unsigned char _flags; }; PUGI_IMPL_FN xml_memory_page* compact_get_page(const void* object, int header_offset) { const compact_header* header = reinterpret_cast(static_cast(object) - header_offset); return header->get_page(); } template PUGI_IMPL_FN_NO_INLINE T* compact_get_value(const void* object) { return static_cast(compact_get_page(object, header_offset)->allocator->_hash->find(object)); } template PUGI_IMPL_FN_NO_INLINE void compact_set_value(const void* object, T* value) { compact_get_page(object, header_offset)->allocator->_hash->insert(object, value); } template class compact_pointer { public: compact_pointer(): _data(0) { } void operator=(const compact_pointer& rhs) { *this = rhs + 0; } void operator=(T* value) { if (value) { // value is guaranteed to be compact-aligned; 'this' is not // our decoding is based on 'this' aligned to compact alignment downwards (see operator T*) // so for negative offsets (e.g. -3) we need to adjust the diff by compact_alignment - 1 to // compensate for arithmetic shift rounding for negative values ptrdiff_t diff = reinterpret_cast(value) - reinterpret_cast(this); ptrdiff_t offset = ((diff + int(compact_alignment - 1)) >> compact_alignment_log2) - start; if (static_cast(offset) <= 253) _data = static_cast(offset + 1); else { compact_set_value(this, value); _data = 255; } } else _data = 0; } operator T*() const { if (_data) { if (_data < 255) { uintptr_t base = reinterpret_cast(this) & ~(compact_alignment - 1); return reinterpret_cast(base + (_data - 1 + start) * compact_alignment); } else return compact_get_value(this); } else return 0; } T* operator->() const { return *this; } private: unsigned char _data; }; template class compact_pointer_parent { public: compact_pointer_parent(): _data(0) { } void operator=(const compact_pointer_parent& rhs) { *this = rhs + 0; } void operator=(T* value) { if (value) { // value is guaranteed to be compact-aligned; 'this' is not // our decoding is based on 'this' aligned to compact alignment downwards (see operator T*) // so for negative offsets (e.g. -3) we need to adjust the diff by compact_alignment - 1 to // compensate for arithmetic shift behavior for negative values ptrdiff_t diff = reinterpret_cast(value) - reinterpret_cast(this); ptrdiff_t offset = ((diff + int(compact_alignment - 1)) >> compact_alignment_log2) + 65533; if (static_cast(offset) <= 65533) { _data = static_cast(offset + 1); } else { xml_memory_page* page = compact_get_page(this, header_offset); if (PUGI_IMPL_UNLIKELY(page->compact_shared_parent == 0)) page->compact_shared_parent = value; if (page->compact_shared_parent == value) { _data = 65534; } else { compact_set_value(this, value); _data = 65535; } } } else { _data = 0; } } operator T*() const { if (_data) { if (_data < 65534) { uintptr_t base = reinterpret_cast(this) & ~(compact_alignment - 1); return reinterpret_cast(base + (_data - 1 - 65533) * compact_alignment); } else if (_data == 65534) return static_cast(compact_get_page(this, header_offset)->compact_shared_parent); else return compact_get_value(this); } else return 0; } T* operator->() const { return *this; } private: uint16_t _data; }; template class compact_string { public: compact_string(): _data(0) { } void operator=(const compact_string& rhs) { *this = rhs + 0; } void operator=(char_t* value) { if (value) { xml_memory_page* page = compact_get_page(this, header_offset); if (PUGI_IMPL_UNLIKELY(page->compact_string_base == 0)) page->compact_string_base = value; ptrdiff_t offset = value - page->compact_string_base; if (static_cast(offset) < (65535 << 7)) { // round-trip through void* to silence 'cast increases required alignment of target type' warnings uint16_t* base = reinterpret_cast(static_cast(reinterpret_cast(this) - base_offset)); if (*base == 0) { *base = static_cast((offset >> 7) + 1); _data = static_cast((offset & 127) + 1); } else { ptrdiff_t remainder = offset - ((*base - 1) << 7); if (static_cast(remainder) <= 253) { _data = static_cast(remainder + 1); } else { compact_set_value(this, value); _data = 255; } } } else { compact_set_value(this, value); _data = 255; } } else { _data = 0; } } operator char_t*() const { if (_data) { if (_data < 255) { xml_memory_page* page = compact_get_page(this, header_offset); // round-trip through void* to silence 'cast increases required alignment of target type' warnings const uint16_t* base = reinterpret_cast(static_cast(reinterpret_cast(this) - base_offset)); assert(*base); ptrdiff_t offset = ((*base - 1) << 7) + (_data - 1); return page->compact_string_base + offset; } else { return compact_get_value(this); } } else return 0; } private: unsigned char _data; }; PUGI_IMPL_NS_END #endif #ifdef PUGIXML_COMPACT namespace pugi { struct xml_attribute_struct { xml_attribute_struct(impl::xml_memory_page* page): header(page, 0), namevalue_base(0) { PUGI_IMPL_STATIC_ASSERT(sizeof(xml_attribute_struct) == 8); } impl::compact_header header; uint16_t namevalue_base; impl::compact_string<4, 2> name; impl::compact_string<5, 3> value; impl::compact_pointer prev_attribute_c; impl::compact_pointer next_attribute; }; struct xml_node_struct { xml_node_struct(impl::xml_memory_page* page, xml_node_type type): header(page, type), namevalue_base(0) { PUGI_IMPL_STATIC_ASSERT(sizeof(xml_node_struct) == 12); } impl::compact_header header; uint16_t namevalue_base; impl::compact_string<4, 2> name; impl::compact_string<5, 3> value; impl::compact_pointer_parent parent; impl::compact_pointer first_child; impl::compact_pointer prev_sibling_c; impl::compact_pointer next_sibling; impl::compact_pointer first_attribute; }; } #else namespace pugi { struct xml_attribute_struct { xml_attribute_struct(impl::xml_memory_page* page): name(0), value(0), prev_attribute_c(0), next_attribute(0) { header = PUGI_IMPL_GETHEADER_IMPL(this, page, 0); } uintptr_t header; char_t* name; char_t* value; xml_attribute_struct* prev_attribute_c; xml_attribute_struct* next_attribute; }; struct xml_node_struct { xml_node_struct(impl::xml_memory_page* page, xml_node_type type): name(0), value(0), parent(0), first_child(0), prev_sibling_c(0), next_sibling(0), first_attribute(0) { header = PUGI_IMPL_GETHEADER_IMPL(this, page, type); } uintptr_t header; char_t* name; char_t* value; xml_node_struct* parent; xml_node_struct* first_child; xml_node_struct* prev_sibling_c; xml_node_struct* next_sibling; xml_attribute_struct* first_attribute; }; } #endif PUGI_IMPL_NS_BEGIN struct xml_extra_buffer { char_t* buffer; xml_extra_buffer* next; }; struct xml_document_struct: public xml_node_struct, public xml_allocator { xml_document_struct(xml_memory_page* page): xml_node_struct(page, node_document), xml_allocator(page), buffer(0), extra_buffers(0) { } const char_t* buffer; xml_extra_buffer* extra_buffers; #ifdef PUGIXML_COMPACT compact_hash_table hash; #endif }; template inline xml_allocator& get_allocator(const Object* object) { assert(object); return *PUGI_IMPL_GETPAGE(object)->allocator; } template inline xml_document_struct& get_document(const Object* object) { assert(object); return *static_cast(PUGI_IMPL_GETPAGE(object)->allocator); } PUGI_IMPL_NS_END // Low-level DOM operations PUGI_IMPL_NS_BEGIN inline xml_attribute_struct* allocate_attribute(xml_allocator& alloc) { xml_memory_page* page; void* memory = alloc.allocate_object(sizeof(xml_attribute_struct), page); if (!memory) return 0; return new (memory) xml_attribute_struct(page); } inline xml_node_struct* allocate_node(xml_allocator& alloc, xml_node_type type) { xml_memory_page* page; void* memory = alloc.allocate_object(sizeof(xml_node_struct), page); if (!memory) return 0; return new (memory) xml_node_struct(page, type); } inline void destroy_attribute(xml_attribute_struct* a, xml_allocator& alloc) { if (a->header & impl::xml_memory_page_name_allocated_mask) alloc.deallocate_string(a->name); if (a->header & impl::xml_memory_page_value_allocated_mask) alloc.deallocate_string(a->value); alloc.deallocate_memory(a, sizeof(xml_attribute_struct), PUGI_IMPL_GETPAGE(a)); } inline void destroy_node(xml_node_struct* n, xml_allocator& alloc) { if (n->header & impl::xml_memory_page_name_allocated_mask) alloc.deallocate_string(n->name); if (n->header & impl::xml_memory_page_value_allocated_mask) alloc.deallocate_string(n->value); for (xml_attribute_struct* attr = n->first_attribute; attr; ) { xml_attribute_struct* next = attr->next_attribute; destroy_attribute(attr, alloc); attr = next; } for (xml_node_struct* child = n->first_child; child; ) { xml_node_struct* next = child->next_sibling; destroy_node(child, alloc); child = next; } alloc.deallocate_memory(n, sizeof(xml_node_struct), PUGI_IMPL_GETPAGE(n)); } inline void append_node(xml_node_struct* child, xml_node_struct* node) { child->parent = node; xml_node_struct* head = node->first_child; if (head) { xml_node_struct* tail = head->prev_sibling_c; tail->next_sibling = child; child->prev_sibling_c = tail; head->prev_sibling_c = child; } else { node->first_child = child; child->prev_sibling_c = child; } } inline void prepend_node(xml_node_struct* child, xml_node_struct* node) { child->parent = node; xml_node_struct* head = node->first_child; if (head) { child->prev_sibling_c = head->prev_sibling_c; head->prev_sibling_c = child; } else child->prev_sibling_c = child; child->next_sibling = head; node->first_child = child; } inline void insert_node_after(xml_node_struct* child, xml_node_struct* node) { xml_node_struct* parent = node->parent; child->parent = parent; xml_node_struct* next = node->next_sibling; if (next) next->prev_sibling_c = child; else parent->first_child->prev_sibling_c = child; child->next_sibling = next; child->prev_sibling_c = node; node->next_sibling = child; } inline void insert_node_before(xml_node_struct* child, xml_node_struct* node) { xml_node_struct* parent = node->parent; child->parent = parent; xml_node_struct* prev = node->prev_sibling_c; if (prev->next_sibling) prev->next_sibling = child; else parent->first_child = child; child->prev_sibling_c = prev; child->next_sibling = node; node->prev_sibling_c = child; } inline void remove_node(xml_node_struct* node) { xml_node_struct* parent = node->parent; xml_node_struct* next = node->next_sibling; xml_node_struct* prev = node->prev_sibling_c; if (next) next->prev_sibling_c = prev; else parent->first_child->prev_sibling_c = prev; if (prev->next_sibling) prev->next_sibling = next; else parent->first_child = next; node->parent = 0; node->prev_sibling_c = 0; node->next_sibling = 0; } inline void append_attribute(xml_attribute_struct* attr, xml_node_struct* node) { xml_attribute_struct* head = node->first_attribute; if (head) { xml_attribute_struct* tail = head->prev_attribute_c; tail->next_attribute = attr; attr->prev_attribute_c = tail; head->prev_attribute_c = attr; } else { node->first_attribute = attr; attr->prev_attribute_c = attr; } } inline void prepend_attribute(xml_attribute_struct* attr, xml_node_struct* node) { xml_attribute_struct* head = node->first_attribute; if (head) { attr->prev_attribute_c = head->prev_attribute_c; head->prev_attribute_c = attr; } else attr->prev_attribute_c = attr; attr->next_attribute = head; node->first_attribute = attr; } inline void insert_attribute_after(xml_attribute_struct* attr, xml_attribute_struct* place, xml_node_struct* node) { xml_attribute_struct* next = place->next_attribute; if (next) next->prev_attribute_c = attr; else node->first_attribute->prev_attribute_c = attr; attr->next_attribute = next; attr->prev_attribute_c = place; place->next_attribute = attr; } inline void insert_attribute_before(xml_attribute_struct* attr, xml_attribute_struct* place, xml_node_struct* node) { xml_attribute_struct* prev = place->prev_attribute_c; if (prev->next_attribute) prev->next_attribute = attr; else node->first_attribute = attr; attr->prev_attribute_c = prev; attr->next_attribute = place; place->prev_attribute_c = attr; } inline void remove_attribute(xml_attribute_struct* attr, xml_node_struct* node) { xml_attribute_struct* next = attr->next_attribute; xml_attribute_struct* prev = attr->prev_attribute_c; if (next) next->prev_attribute_c = prev; else node->first_attribute->prev_attribute_c = prev; if (prev->next_attribute) prev->next_attribute = next; else node->first_attribute = next; attr->prev_attribute_c = 0; attr->next_attribute = 0; } PUGI_IMPL_FN_NO_INLINE xml_node_struct* append_new_node(xml_node_struct* node, xml_allocator& alloc, xml_node_type type = node_element) { if (!alloc.reserve()) return 0; xml_node_struct* child = allocate_node(alloc, type); if (!child) return 0; append_node(child, node); return child; } PUGI_IMPL_FN_NO_INLINE xml_attribute_struct* append_new_attribute(xml_node_struct* node, xml_allocator& alloc) { if (!alloc.reserve()) return 0; xml_attribute_struct* attr = allocate_attribute(alloc); if (!attr) return 0; append_attribute(attr, node); return attr; } PUGI_IMPL_NS_END // Helper classes for code generation PUGI_IMPL_NS_BEGIN struct opt_false { enum { value = 0 }; }; struct opt_true { enum { value = 1 }; }; PUGI_IMPL_NS_END // Unicode utilities PUGI_IMPL_NS_BEGIN inline uint16_t endian_swap(uint16_t value) { return static_cast(((value & 0xff) << 8) | (value >> 8)); } inline uint32_t endian_swap(uint32_t value) { return ((value & 0xff) << 24) | ((value & 0xff00) << 8) | ((value & 0xff0000) >> 8) | (value >> 24); } struct utf8_counter { typedef size_t value_type; static value_type low(value_type result, uint32_t ch) { // U+0000..U+007F if (ch < 0x80) return result + 1; // U+0080..U+07FF else if (ch < 0x800) return result + 2; // U+0800..U+FFFF else return result + 3; } static value_type high(value_type result, uint32_t) { // U+10000..U+10FFFF return result + 4; } }; struct utf8_writer { typedef uint8_t* value_type; static value_type low(value_type result, uint32_t ch) { // U+0000..U+007F if (ch < 0x80) { *result = static_cast(ch); return result + 1; } // U+0080..U+07FF else if (ch < 0x800) { result[0] = static_cast(0xC0 | (ch >> 6)); result[1] = static_cast(0x80 | (ch & 0x3F)); return result + 2; } // U+0800..U+FFFF else { result[0] = static_cast(0xE0 | (ch >> 12)); result[1] = static_cast(0x80 | ((ch >> 6) & 0x3F)); result[2] = static_cast(0x80 | (ch & 0x3F)); return result + 3; } } static value_type high(value_type result, uint32_t ch) { // U+10000..U+10FFFF result[0] = static_cast(0xF0 | (ch >> 18)); result[1] = static_cast(0x80 | ((ch >> 12) & 0x3F)); result[2] = static_cast(0x80 | ((ch >> 6) & 0x3F)); result[3] = static_cast(0x80 | (ch & 0x3F)); return result + 4; } static value_type any(value_type result, uint32_t ch) { return (ch < 0x10000) ? low(result, ch) : high(result, ch); } }; struct utf16_counter { typedef size_t value_type; static value_type low(value_type result, uint32_t) { return result + 1; } static value_type high(value_type result, uint32_t) { return result + 2; } }; struct utf16_writer { typedef uint16_t* value_type; static value_type low(value_type result, uint32_t ch) { *result = static_cast(ch); return result + 1; } static value_type high(value_type result, uint32_t ch) { uint32_t msh = static_cast(ch - 0x10000) >> 10; uint32_t lsh = static_cast(ch - 0x10000) & 0x3ff; result[0] = static_cast(0xD800 + msh); result[1] = static_cast(0xDC00 + lsh); return result + 2; } static value_type any(value_type result, uint32_t ch) { return (ch < 0x10000) ? low(result, ch) : high(result, ch); } }; struct utf32_counter { typedef size_t value_type; static value_type low(value_type result, uint32_t) { return result + 1; } static value_type high(value_type result, uint32_t) { return result + 1; } }; struct utf32_writer { typedef uint32_t* value_type; static value_type low(value_type result, uint32_t ch) { *result = ch; return result + 1; } static value_type high(value_type result, uint32_t ch) { *result = ch; return result + 1; } static value_type any(value_type result, uint32_t ch) { *result = ch; return result + 1; } }; struct latin1_writer { typedef uint8_t* value_type; static value_type low(value_type result, uint32_t ch) { *result = static_cast(ch > 255 ? '?' : ch); return result + 1; } static value_type high(value_type result, uint32_t ch) { (void)ch; *result = '?'; return result + 1; } }; struct utf8_decoder { typedef uint8_t type; template static inline typename Traits::value_type process(const uint8_t* data, size_t size, typename Traits::value_type result, Traits) { const uint8_t utf8_byte_mask = 0x3f; while (size) { uint8_t lead = *data; // 0xxxxxxx -> U+0000..U+007F if (lead < 0x80) { result = Traits::low(result, lead); data += 1; size -= 1; // process aligned single-byte (ascii) blocks if ((reinterpret_cast(data) & 3) == 0) { // round-trip through void* to silence 'cast increases required alignment of target type' warnings while (size >= 4 && (*static_cast(static_cast(data)) & 0x80808080) == 0) { result = Traits::low(result, data[0]); result = Traits::low(result, data[1]); result = Traits::low(result, data[2]); result = Traits::low(result, data[3]); data += 4; size -= 4; } } } // 110xxxxx -> U+0080..U+07FF else if (static_cast(lead - 0xC0) < 0x20 && size >= 2 && (data[1] & 0xc0) == 0x80) { result = Traits::low(result, ((lead & ~0xC0) << 6) | (data[1] & utf8_byte_mask)); data += 2; size -= 2; } // 1110xxxx -> U+0800-U+FFFF else if (static_cast(lead - 0xE0) < 0x10 && size >= 3 && (data[1] & 0xc0) == 0x80 && (data[2] & 0xc0) == 0x80) { result = Traits::low(result, ((lead & ~0xE0) << 12) | ((data[1] & utf8_byte_mask) << 6) | (data[2] & utf8_byte_mask)); data += 3; size -= 3; } // 11110xxx -> U+10000..U+10FFFF else if (static_cast(lead - 0xF0) < 0x08 && size >= 4 && (data[1] & 0xc0) == 0x80 && (data[2] & 0xc0) == 0x80 && (data[3] & 0xc0) == 0x80) { result = Traits::high(result, ((lead & ~0xF0) << 18) | ((data[1] & utf8_byte_mask) << 12) | ((data[2] & utf8_byte_mask) << 6) | (data[3] & utf8_byte_mask)); data += 4; size -= 4; } // 10xxxxxx or 11111xxx -> invalid else { data += 1; size -= 1; } } return result; } }; template struct utf16_decoder { typedef uint16_t type; template static inline typename Traits::value_type process(const uint16_t* data, size_t size, typename Traits::value_type result, Traits) { while (size) { uint16_t lead = opt_swap::value ? endian_swap(*data) : *data; // U+0000..U+D7FF if (lead < 0xD800) { result = Traits::low(result, lead); data += 1; size -= 1; } // U+E000..U+FFFF else if (static_cast(lead - 0xE000) < 0x2000) { result = Traits::low(result, lead); data += 1; size -= 1; } // surrogate pair lead else if (static_cast(lead - 0xD800) < 0x400 && size >= 2) { uint16_t next = opt_swap::value ? endian_swap(data[1]) : data[1]; if (static_cast(next - 0xDC00) < 0x400) { result = Traits::high(result, 0x10000 + ((lead & 0x3ff) << 10) + (next & 0x3ff)); data += 2; size -= 2; } else { data += 1; size -= 1; } } else { data += 1; size -= 1; } } return result; } }; template struct utf32_decoder { typedef uint32_t type; template static inline typename Traits::value_type process(const uint32_t* data, size_t size, typename Traits::value_type result, Traits) { while (size) { uint32_t lead = opt_swap::value ? endian_swap(*data) : *data; // U+0000..U+FFFF if (lead < 0x10000) { result = Traits::low(result, lead); data += 1; size -= 1; } // U+10000..U+10FFFF else { result = Traits::high(result, lead); data += 1; size -= 1; } } return result; } }; struct latin1_decoder { typedef uint8_t type; template static inline typename Traits::value_type process(const uint8_t* data, size_t size, typename Traits::value_type result, Traits) { while (size) { result = Traits::low(result, *data); data += 1; size -= 1; } return result; } }; template struct wchar_selector; template <> struct wchar_selector<2> { typedef uint16_t type; typedef utf16_counter counter; typedef utf16_writer writer; typedef utf16_decoder decoder; }; template <> struct wchar_selector<4> { typedef uint32_t type; typedef utf32_counter counter; typedef utf32_writer writer; typedef utf32_decoder decoder; }; typedef wchar_selector::counter wchar_counter; typedef wchar_selector::writer wchar_writer; struct wchar_decoder { typedef wchar_t type; template static inline typename Traits::value_type process(const wchar_t* data, size_t size, typename Traits::value_type result, Traits traits) { typedef wchar_selector::decoder decoder; return decoder::process(reinterpret_cast(data), size, result, traits); } }; #ifdef PUGIXML_WCHAR_MODE PUGI_IMPL_FN void convert_wchar_endian_swap(wchar_t* result, const wchar_t* data, size_t length) { for (size_t i = 0; i < length; ++i) result[i] = static_cast(endian_swap(static_cast::type>(data[i]))); } #endif PUGI_IMPL_NS_END PUGI_IMPL_NS_BEGIN enum chartype_t { ct_parse_pcdata = 1, // \0, &, \r, < ct_parse_attr = 2, // \0, &, \r, ', " ct_parse_attr_ws = 4, // \0, &, \r, ', ", \n, tab ct_space = 8, // \r, \n, space, tab ct_parse_cdata = 16, // \0, ], >, \r ct_parse_comment = 32, // \0, -, >, \r ct_symbol = 64, // Any symbol > 127, a-z, A-Z, 0-9, _, :, -, . ct_start_symbol = 128 // Any symbol > 127, a-z, A-Z, _, : }; static const unsigned char chartype_table[256] = { 55, 0, 0, 0, 0, 0, 0, 0, 0, 12, 12, 0, 0, 63, 0, 0, // 0-15 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 16-31 8, 0, 6, 0, 0, 0, 7, 6, 0, 0, 0, 0, 0, 96, 64, 0, // 32-47 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 192, 0, 1, 0, 48, 0, // 48-63 0, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, // 64-79 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 0, 0, 16, 0, 192, // 80-95 0, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, // 96-111 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 0, 0, 0, 0, 0, // 112-127 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, // 128+ 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192 }; enum chartypex_t { ctx_special_pcdata = 1, // Any symbol >= 0 and < 32 (except \t, \r, \n), &, <, > ctx_special_attr = 2, // Any symbol >= 0 and < 32, &, <, ", ' ctx_start_symbol = 4, // Any symbol > 127, a-z, A-Z, _ ctx_digit = 8, // 0-9 ctx_symbol = 16 // Any symbol > 127, a-z, A-Z, 0-9, _, -, . }; static const unsigned char chartypex_table[256] = { 3, 3, 3, 3, 3, 3, 3, 3, 3, 2, 2, 3, 3, 2, 3, 3, // 0-15 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, // 16-31 0, 0, 2, 0, 0, 0, 3, 2, 0, 0, 0, 0, 0, 16, 16, 0, // 32-47 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 0, 0, 3, 0, 1, 0, // 48-63 0, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, // 64-79 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 0, 0, 0, 0, 20, // 80-95 0, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, // 96-111 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 0, 0, 0, 0, 0, // 112-127 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, // 128+ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20 }; #ifdef PUGIXML_WCHAR_MODE #define PUGI_IMPL_IS_CHARTYPE_IMPL(c, ct, table) ((static_cast(c) < 128 ? table[static_cast(c)] : table[128]) & (ct)) #else #define PUGI_IMPL_IS_CHARTYPE_IMPL(c, ct, table) (table[static_cast(c)] & (ct)) #endif #define PUGI_IMPL_IS_CHARTYPE(c, ct) PUGI_IMPL_IS_CHARTYPE_IMPL(c, ct, chartype_table) #define PUGI_IMPL_IS_CHARTYPEX(c, ct) PUGI_IMPL_IS_CHARTYPE_IMPL(c, ct, chartypex_table) PUGI_IMPL_FN bool is_little_endian() { unsigned int ui = 1; return *reinterpret_cast(&ui) == 1; } PUGI_IMPL_FN xml_encoding get_wchar_encoding() { PUGI_IMPL_STATIC_ASSERT(sizeof(wchar_t) == 2 || sizeof(wchar_t) == 4); if (sizeof(wchar_t) == 2) return is_little_endian() ? encoding_utf16_le : encoding_utf16_be; else return is_little_endian() ? encoding_utf32_le : encoding_utf32_be; } PUGI_IMPL_FN bool parse_declaration_encoding(const uint8_t* data, size_t size, const uint8_t*& out_encoding, size_t& out_length) { #define PUGI_IMPL_SCANCHAR(ch) { if (offset >= size || data[offset] != ch) return false; offset++; } #define PUGI_IMPL_SCANCHARTYPE(ct) { while (offset < size && PUGI_IMPL_IS_CHARTYPE(data[offset], ct)) offset++; } // check if we have a non-empty XML declaration if (size < 6 || !((data[0] == '<') & (data[1] == '?') & (data[2] == 'x') & (data[3] == 'm') & (data[4] == 'l') && PUGI_IMPL_IS_CHARTYPE(data[5], ct_space))) return false; // scan XML declaration until the encoding field for (size_t i = 6; i + 1 < size; ++i) { // declaration can not contain ? in quoted values if (data[i] == '?') return false; if (data[i] == 'e' && data[i + 1] == 'n') { size_t offset = i; // encoding follows the version field which can't contain 'en' so this has to be the encoding if XML is well formed PUGI_IMPL_SCANCHAR('e'); PUGI_IMPL_SCANCHAR('n'); PUGI_IMPL_SCANCHAR('c'); PUGI_IMPL_SCANCHAR('o'); PUGI_IMPL_SCANCHAR('d'); PUGI_IMPL_SCANCHAR('i'); PUGI_IMPL_SCANCHAR('n'); PUGI_IMPL_SCANCHAR('g'); // S? = S? PUGI_IMPL_SCANCHARTYPE(ct_space); PUGI_IMPL_SCANCHAR('='); PUGI_IMPL_SCANCHARTYPE(ct_space); // the only two valid delimiters are ' and " uint8_t delimiter = (offset < size && data[offset] == '"') ? '"' : '\''; PUGI_IMPL_SCANCHAR(delimiter); size_t start = offset; out_encoding = data + offset; PUGI_IMPL_SCANCHARTYPE(ct_symbol); out_length = offset - start; PUGI_IMPL_SCANCHAR(delimiter); return true; } } return false; #undef PUGI_IMPL_SCANCHAR #undef PUGI_IMPL_SCANCHARTYPE } PUGI_IMPL_FN xml_encoding guess_buffer_encoding(const uint8_t* data, size_t size) { // skip encoding autodetection if input buffer is too small if (size < 4) return encoding_utf8; uint8_t d0 = data[0], d1 = data[1], d2 = data[2], d3 = data[3]; // look for BOM in first few bytes if (d0 == 0 && d1 == 0 && d2 == 0xfe && d3 == 0xff) return encoding_utf32_be; if (d0 == 0xff && d1 == 0xfe && d2 == 0 && d3 == 0) return encoding_utf32_le; if (d0 == 0xfe && d1 == 0xff) return encoding_utf16_be; if (d0 == 0xff && d1 == 0xfe) return encoding_utf16_le; if (d0 == 0xef && d1 == 0xbb && d2 == 0xbf) return encoding_utf8; // look for <, (contents); return guess_buffer_encoding(data, size); } PUGI_IMPL_FN bool get_mutable_buffer(char_t*& out_buffer, size_t& out_length, const void* contents, size_t size, bool is_mutable) { size_t length = size / sizeof(char_t); if (is_mutable) { out_buffer = static_cast(const_cast(contents)); out_length = length; } else { char_t* buffer = static_cast(xml_memory::allocate((length + 1) * sizeof(char_t))); if (!buffer) return false; if (contents) memcpy(buffer, contents, length * sizeof(char_t)); else assert(length == 0); buffer[length] = 0; out_buffer = buffer; out_length = length + 1; } return true; } #ifdef PUGIXML_WCHAR_MODE PUGI_IMPL_FN bool need_endian_swap_utf(xml_encoding le, xml_encoding re) { return (le == encoding_utf16_be && re == encoding_utf16_le) || (le == encoding_utf16_le && re == encoding_utf16_be) || (le == encoding_utf32_be && re == encoding_utf32_le) || (le == encoding_utf32_le && re == encoding_utf32_be); } PUGI_IMPL_FN bool convert_buffer_endian_swap(char_t*& out_buffer, size_t& out_length, const void* contents, size_t size, bool is_mutable) { const char_t* data = static_cast(contents); size_t length = size / sizeof(char_t); if (is_mutable) { char_t* buffer = const_cast(data); convert_wchar_endian_swap(buffer, data, length); out_buffer = buffer; out_length = length; } else { char_t* buffer = static_cast(xml_memory::allocate((length + 1) * sizeof(char_t))); if (!buffer) return false; convert_wchar_endian_swap(buffer, data, length); buffer[length] = 0; out_buffer = buffer; out_length = length + 1; } return true; } template PUGI_IMPL_FN bool convert_buffer_generic(char_t*& out_buffer, size_t& out_length, const void* contents, size_t size, D) { const typename D::type* data = static_cast(contents); size_t data_length = size / sizeof(typename D::type); // first pass: get length in wchar_t units size_t length = D::process(data, data_length, 0, wchar_counter()); // allocate buffer of suitable length char_t* buffer = static_cast(xml_memory::allocate((length + 1) * sizeof(char_t))); if (!buffer) return false; // second pass: convert utf16 input to wchar_t wchar_writer::value_type obegin = reinterpret_cast(buffer); wchar_writer::value_type oend = D::process(data, data_length, obegin, wchar_writer()); assert(oend == obegin + length); *oend = 0; out_buffer = buffer; out_length = length + 1; return true; } PUGI_IMPL_FN bool convert_buffer(char_t*& out_buffer, size_t& out_length, xml_encoding encoding, const void* contents, size_t size, bool is_mutable) { // get native encoding xml_encoding wchar_encoding = get_wchar_encoding(); // fast path: no conversion required if (encoding == wchar_encoding) return get_mutable_buffer(out_buffer, out_length, contents, size, is_mutable); // only endian-swapping is required if (need_endian_swap_utf(encoding, wchar_encoding)) return convert_buffer_endian_swap(out_buffer, out_length, contents, size, is_mutable); // source encoding is utf8 if (encoding == encoding_utf8) return convert_buffer_generic(out_buffer, out_length, contents, size, utf8_decoder()); // source encoding is utf16 if (encoding == encoding_utf16_be || encoding == encoding_utf16_le) { xml_encoding native_encoding = is_little_endian() ? encoding_utf16_le : encoding_utf16_be; return (native_encoding == encoding) ? convert_buffer_generic(out_buffer, out_length, contents, size, utf16_decoder()) : convert_buffer_generic(out_buffer, out_length, contents, size, utf16_decoder()); } // source encoding is utf32 if (encoding == encoding_utf32_be || encoding == encoding_utf32_le) { xml_encoding native_encoding = is_little_endian() ? encoding_utf32_le : encoding_utf32_be; return (native_encoding == encoding) ? convert_buffer_generic(out_buffer, out_length, contents, size, utf32_decoder()) : convert_buffer_generic(out_buffer, out_length, contents, size, utf32_decoder()); } // source encoding is latin1 if (encoding == encoding_latin1) return convert_buffer_generic(out_buffer, out_length, contents, size, latin1_decoder()); assert(false && "Invalid encoding"); // unreachable return false; } #else template PUGI_IMPL_FN bool convert_buffer_generic(char_t*& out_buffer, size_t& out_length, const void* contents, size_t size, D) { const typename D::type* data = static_cast(contents); size_t data_length = size / sizeof(typename D::type); // first pass: get length in utf8 units size_t length = D::process(data, data_length, 0, utf8_counter()); // allocate buffer of suitable length char_t* buffer = static_cast(xml_memory::allocate((length + 1) * sizeof(char_t))); if (!buffer) return false; // second pass: convert utf16 input to utf8 uint8_t* obegin = reinterpret_cast(buffer); uint8_t* oend = D::process(data, data_length, obegin, utf8_writer()); assert(oend == obegin + length); *oend = 0; out_buffer = buffer; out_length = length + 1; return true; } PUGI_IMPL_FN size_t get_latin1_7bit_prefix_length(const uint8_t* data, size_t size) { for (size_t i = 0; i < size; ++i) if (data[i] > 127) return i; return size; } PUGI_IMPL_FN bool convert_buffer_latin1(char_t*& out_buffer, size_t& out_length, const void* contents, size_t size, bool is_mutable) { const uint8_t* data = static_cast(contents); size_t data_length = size; // get size of prefix that does not need utf8 conversion size_t prefix_length = get_latin1_7bit_prefix_length(data, data_length); assert(prefix_length <= data_length); const uint8_t* postfix = data + prefix_length; size_t postfix_length = data_length - prefix_length; // if no conversion is needed, just return the original buffer if (postfix_length == 0) return get_mutable_buffer(out_buffer, out_length, contents, size, is_mutable); // first pass: get length in utf8 units size_t length = prefix_length + latin1_decoder::process(postfix, postfix_length, 0, utf8_counter()); // allocate buffer of suitable length char_t* buffer = static_cast(xml_memory::allocate((length + 1) * sizeof(char_t))); if (!buffer) return false; // second pass: convert latin1 input to utf8 memcpy(buffer, data, prefix_length); uint8_t* obegin = reinterpret_cast(buffer); uint8_t* oend = latin1_decoder::process(postfix, postfix_length, obegin + prefix_length, utf8_writer()); assert(oend == obegin + length); *oend = 0; out_buffer = buffer; out_length = length + 1; return true; } PUGI_IMPL_FN bool convert_buffer(char_t*& out_buffer, size_t& out_length, xml_encoding encoding, const void* contents, size_t size, bool is_mutable) { // fast path: no conversion required if (encoding == encoding_utf8) return get_mutable_buffer(out_buffer, out_length, contents, size, is_mutable); // source encoding is utf16 if (encoding == encoding_utf16_be || encoding == encoding_utf16_le) { xml_encoding native_encoding = is_little_endian() ? encoding_utf16_le : encoding_utf16_be; return (native_encoding == encoding) ? convert_buffer_generic(out_buffer, out_length, contents, size, utf16_decoder()) : convert_buffer_generic(out_buffer, out_length, contents, size, utf16_decoder()); } // source encoding is utf32 if (encoding == encoding_utf32_be || encoding == encoding_utf32_le) { xml_encoding native_encoding = is_little_endian() ? encoding_utf32_le : encoding_utf32_be; return (native_encoding == encoding) ? convert_buffer_generic(out_buffer, out_length, contents, size, utf32_decoder()) : convert_buffer_generic(out_buffer, out_length, contents, size, utf32_decoder()); } // source encoding is latin1 if (encoding == encoding_latin1) return convert_buffer_latin1(out_buffer, out_length, contents, size, is_mutable); assert(false && "Invalid encoding"); // unreachable return false; } #endif PUGI_IMPL_FN size_t as_utf8_begin(const wchar_t* str, size_t length) { // get length in utf8 characters return wchar_decoder::process(str, length, 0, utf8_counter()); } PUGI_IMPL_FN void as_utf8_end(char* buffer, size_t size, const wchar_t* str, size_t length) { // convert to utf8 uint8_t* begin = reinterpret_cast(buffer); uint8_t* end = wchar_decoder::process(str, length, begin, utf8_writer()); assert(begin + size == end); (void)!end; (void)!size; } #ifndef PUGIXML_NO_STL PUGI_IMPL_FN std::string as_utf8_impl(const wchar_t* str, size_t length) { // first pass: get length in utf8 characters size_t size = as_utf8_begin(str, length); // allocate resulting string std::string result; result.resize(size); // second pass: convert to utf8 if (size > 0) as_utf8_end(&result[0], size, str, length); return result; } PUGI_IMPL_FN std::basic_string as_wide_impl(const char* str, size_t size) { const uint8_t* data = reinterpret_cast(str); // first pass: get length in wchar_t units size_t length = utf8_decoder::process(data, size, 0, wchar_counter()); // allocate resulting string std::basic_string result; result.resize(length); // second pass: convert to wchar_t if (length > 0) { wchar_writer::value_type begin = reinterpret_cast(&result[0]); wchar_writer::value_type end = utf8_decoder::process(data, size, begin, wchar_writer()); assert(begin + length == end); (void)!end; } return result; } #endif template inline bool strcpy_insitu_allow(size_t length, const Header& header, uintptr_t header_mask, char_t* target) { // never reuse shared memory if (header & xml_memory_page_contents_shared_mask) return false; size_t target_length = strlength(target); // always reuse document buffer memory if possible if ((header & header_mask) == 0) return target_length >= length; // reuse heap memory if waste is not too great const size_t reuse_threshold = 32; return target_length >= length && (target_length < reuse_threshold || target_length - length < target_length / 2); } template PUGI_IMPL_FN bool strcpy_insitu(String& dest, Header& header, uintptr_t header_mask, const char_t* source, size_t source_length) { assert((header & header_mask) == 0 || dest); // header bit indicates whether dest was previously allocated if (source_length == 0) { // empty string and null pointer are equivalent, so just deallocate old memory xml_allocator* alloc = PUGI_IMPL_GETPAGE_IMPL(header)->allocator; if (header & header_mask) alloc->deallocate_string(dest); // mark the string as not allocated dest = 0; header &= ~header_mask; return true; } else if (dest && strcpy_insitu_allow(source_length, header, header_mask, dest)) { // we can reuse old buffer, so just copy the new data (including zero terminator) memcpy(dest, source, source_length * sizeof(char_t)); dest[source_length] = 0; return true; } else { xml_allocator* alloc = PUGI_IMPL_GETPAGE_IMPL(header)->allocator; if (!alloc->reserve()) return false; // allocate new buffer char_t* buf = alloc->allocate_string(source_length + 1); if (!buf) return false; // copy the string (including zero terminator) memcpy(buf, source, source_length * sizeof(char_t)); buf[source_length] = 0; // deallocate old buffer (*after* the above to protect against overlapping memory and/or allocation failures) if (header & header_mask) alloc->deallocate_string(dest); // the string is now allocated, so set the flag dest = buf; header |= header_mask; return true; } } struct gap { char_t* end; size_t size; gap(): end(0), size(0) { } // Push new gap, move s count bytes further (skipping the gap). // Collapse previous gap. void push(char_t*& s, size_t count) { if (end) // there was a gap already; collapse it { // Move [old_gap_end, new_gap_start) to [old_gap_start, ...) assert(s >= end); memmove(end - size, end, reinterpret_cast(s) - reinterpret_cast(end)); } s += count; // end of current gap // "merge" two gaps end = s; size += count; } // Collapse all gaps, return past-the-end pointer char_t* flush(char_t* s) { if (end) { // Move [old_gap_end, current_pos) to [old_gap_start, ...) assert(s >= end); memmove(end - size, end, reinterpret_cast(s) - reinterpret_cast(end)); return s - size; } else return s; } }; PUGI_IMPL_FN char_t* strconv_escape(char_t* s, gap& g) { char_t* stre = s + 1; switch (*stre) { case '#': // &#... { unsigned int ucsc = 0; if (stre[1] == 'x') // &#x... (hex code) { stre += 2; char_t ch = *stre; if (ch == ';') return stre; for (;;) { if (static_cast(ch - '0') <= 9) ucsc = 16 * ucsc + (ch - '0'); else if (static_cast((ch | ' ') - 'a') <= 5) ucsc = 16 * ucsc + ((ch | ' ') - 'a' + 10); else if (ch == ';') break; else // cancel return stre; ch = *++stre; } ++stre; } else // &#... (dec code) { char_t ch = *++stre; if (ch == ';') return stre; for (;;) { if (static_cast(ch - '0') <= 9) ucsc = 10 * ucsc + (ch - '0'); else if (ch == ';') break; else // cancel return stre; ch = *++stre; } ++stre; } #ifdef PUGIXML_WCHAR_MODE s = reinterpret_cast(wchar_writer::any(reinterpret_cast(s), ucsc)); #else s = reinterpret_cast(utf8_writer::any(reinterpret_cast(s), ucsc)); #endif g.push(s, stre - s); return stre; } case 'a': // &a { ++stre; if (*stre == 'm') // &am { if (*++stre == 'p' && *++stre == ';') // & { *s++ = '&'; ++stre; g.push(s, stre - s); return stre; } } else if (*stre == 'p') // &ap { if (*++stre == 'o' && *++stre == 's' && *++stre == ';') // ' { *s++ = '\''; ++stre; g.push(s, stre - s); return stre; } } break; } case 'g': // &g { if (*++stre == 't' && *++stre == ';') // > { *s++ = '>'; ++stre; g.push(s, stre - s); return stre; } break; } case 'l': // &l { if (*++stre == 't' && *++stre == ';') // < { *s++ = '<'; ++stre; g.push(s, stre - s); return stre; } break; } case 'q': // &q { if (*++stre == 'u' && *++stre == 'o' && *++stre == 't' && *++stre == ';') // " { *s++ = '"'; ++stre; g.push(s, stre - s); return stre; } break; } default: break; } return stre; } // Parser utilities #define PUGI_IMPL_ENDSWITH(c, e) ((c) == (e) || ((c) == 0 && endch == (e))) #define PUGI_IMPL_SKIPWS() { while (PUGI_IMPL_IS_CHARTYPE(*s, ct_space)) ++s; } #define PUGI_IMPL_OPTSET(OPT) ( optmsk & (OPT) ) #define PUGI_IMPL_PUSHNODE(TYPE) { cursor = append_new_node(cursor, *alloc, TYPE); if (!cursor) PUGI_IMPL_THROW_ERROR(status_out_of_memory, s); } #define PUGI_IMPL_POPNODE() { cursor = cursor->parent; } #define PUGI_IMPL_SCANFOR(X) { while (*s != 0 && !(X)) ++s; } #define PUGI_IMPL_SCANWHILE(X) { while (X) ++s; } #define PUGI_IMPL_SCANWHILE_UNROLL(X) { for (;;) { char_t ss = s[0]; if (PUGI_IMPL_UNLIKELY(!(X))) { break; } ss = s[1]; if (PUGI_IMPL_UNLIKELY(!(X))) { s += 1; break; } ss = s[2]; if (PUGI_IMPL_UNLIKELY(!(X))) { s += 2; break; } ss = s[3]; if (PUGI_IMPL_UNLIKELY(!(X))) { s += 3; break; } s += 4; } } #define PUGI_IMPL_ENDSEG() { ch = *s; *s = 0; ++s; } #define PUGI_IMPL_THROW_ERROR(err, m) return error_offset = m, error_status = err, static_cast(0) #define PUGI_IMPL_CHECK_ERROR(err, m) { if (*s == 0) PUGI_IMPL_THROW_ERROR(err, m); } PUGI_IMPL_FN char_t* strconv_comment(char_t* s, char_t endch) { gap g; while (true) { PUGI_IMPL_SCANWHILE_UNROLL(!PUGI_IMPL_IS_CHARTYPE(ss, ct_parse_comment)); if (*s == '\r') // Either a single 0x0d or 0x0d 0x0a pair { *s++ = '\n'; // replace first one with 0x0a if (*s == '\n') g.push(s, 1); } else if (s[0] == '-' && s[1] == '-' && PUGI_IMPL_ENDSWITH(s[2], '>')) // comment ends here { *g.flush(s) = 0; return s + (s[2] == '>' ? 3 : 2); } else if (*s == 0) { return 0; } else ++s; } } PUGI_IMPL_FN char_t* strconv_cdata(char_t* s, char_t endch) { gap g; while (true) { PUGI_IMPL_SCANWHILE_UNROLL(!PUGI_IMPL_IS_CHARTYPE(ss, ct_parse_cdata)); if (*s == '\r') // Either a single 0x0d or 0x0d 0x0a pair { *s++ = '\n'; // replace first one with 0x0a if (*s == '\n') g.push(s, 1); } else if (s[0] == ']' && s[1] == ']' && PUGI_IMPL_ENDSWITH(s[2], '>')) // CDATA ends here { *g.flush(s) = 0; return s + 1; } else if (*s == 0) { return 0; } else ++s; } } typedef char_t* (*strconv_pcdata_t)(char_t*); template struct strconv_pcdata_impl { static char_t* parse(char_t* s) { gap g; char_t* begin = s; while (true) { PUGI_IMPL_SCANWHILE_UNROLL(!PUGI_IMPL_IS_CHARTYPE(ss, ct_parse_pcdata)); if (*s == '<') // PCDATA ends here { char_t* end = g.flush(s); if (opt_trim::value) while (end > begin && PUGI_IMPL_IS_CHARTYPE(end[-1], ct_space)) --end; *end = 0; return s + 1; } else if (opt_eol::value && *s == '\r') // Either a single 0x0d or 0x0d 0x0a pair { *s++ = '\n'; // replace first one with 0x0a if (*s == '\n') g.push(s, 1); } else if (opt_escape::value && *s == '&') { s = strconv_escape(s, g); } else if (*s == 0) { char_t* end = g.flush(s); if (opt_trim::value) while (end > begin && PUGI_IMPL_IS_CHARTYPE(end[-1], ct_space)) --end; *end = 0; return s; } else ++s; } } }; PUGI_IMPL_FN strconv_pcdata_t get_strconv_pcdata(unsigned int optmask) { PUGI_IMPL_STATIC_ASSERT(parse_escapes == 0x10 && parse_eol == 0x20 && parse_trim_pcdata == 0x0800); switch (((optmask >> 4) & 3) | ((optmask >> 9) & 4)) // get bitmask for flags (trim eol escapes); this simultaneously checks 3 options from assertion above { case 0: return strconv_pcdata_impl::parse; case 1: return strconv_pcdata_impl::parse; case 2: return strconv_pcdata_impl::parse; case 3: return strconv_pcdata_impl::parse; case 4: return strconv_pcdata_impl::parse; case 5: return strconv_pcdata_impl::parse; case 6: return strconv_pcdata_impl::parse; case 7: return strconv_pcdata_impl::parse; default: assert(false); return 0; // unreachable } } typedef char_t* (*strconv_attribute_t)(char_t*, char_t); template struct strconv_attribute_impl { static char_t* parse_wnorm(char_t* s, char_t end_quote) { gap g; // trim leading whitespaces if (PUGI_IMPL_IS_CHARTYPE(*s, ct_space)) { char_t* str = s; do ++str; while (PUGI_IMPL_IS_CHARTYPE(*str, ct_space)); g.push(s, str - s); } while (true) { PUGI_IMPL_SCANWHILE_UNROLL(!PUGI_IMPL_IS_CHARTYPE(ss, ct_parse_attr_ws | ct_space)); if (*s == end_quote) { char_t* str = g.flush(s); do *str-- = 0; while (PUGI_IMPL_IS_CHARTYPE(*str, ct_space)); return s + 1; } else if (PUGI_IMPL_IS_CHARTYPE(*s, ct_space)) { *s++ = ' '; if (PUGI_IMPL_IS_CHARTYPE(*s, ct_space)) { char_t* str = s + 1; while (PUGI_IMPL_IS_CHARTYPE(*str, ct_space)) ++str; g.push(s, str - s); } } else if (opt_escape::value && *s == '&') { s = strconv_escape(s, g); } else if (!*s) { return 0; } else ++s; } } static char_t* parse_wconv(char_t* s, char_t end_quote) { gap g; while (true) { PUGI_IMPL_SCANWHILE_UNROLL(!PUGI_IMPL_IS_CHARTYPE(ss, ct_parse_attr_ws)); if (*s == end_quote) { *g.flush(s) = 0; return s + 1; } else if (PUGI_IMPL_IS_CHARTYPE(*s, ct_space)) { if (*s == '\r') { *s++ = ' '; if (*s == '\n') g.push(s, 1); } else *s++ = ' '; } else if (opt_escape::value && *s == '&') { s = strconv_escape(s, g); } else if (!*s) { return 0; } else ++s; } } static char_t* parse_eol(char_t* s, char_t end_quote) { gap g; while (true) { PUGI_IMPL_SCANWHILE_UNROLL(!PUGI_IMPL_IS_CHARTYPE(ss, ct_parse_attr)); if (*s == end_quote) { *g.flush(s) = 0; return s + 1; } else if (*s == '\r') { *s++ = '\n'; if (*s == '\n') g.push(s, 1); } else if (opt_escape::value && *s == '&') { s = strconv_escape(s, g); } else if (!*s) { return 0; } else ++s; } } static char_t* parse_simple(char_t* s, char_t end_quote) { gap g; while (true) { PUGI_IMPL_SCANWHILE_UNROLL(!PUGI_IMPL_IS_CHARTYPE(ss, ct_parse_attr)); if (*s == end_quote) { *g.flush(s) = 0; return s + 1; } else if (opt_escape::value && *s == '&') { s = strconv_escape(s, g); } else if (!*s) { return 0; } else ++s; } } }; PUGI_IMPL_FN strconv_attribute_t get_strconv_attribute(unsigned int optmask) { PUGI_IMPL_STATIC_ASSERT(parse_escapes == 0x10 && parse_eol == 0x20 && parse_wconv_attribute == 0x40 && parse_wnorm_attribute == 0x80); switch ((optmask >> 4) & 15) // get bitmask for flags (wnorm wconv eol escapes); this simultaneously checks 4 options from assertion above { case 0: return strconv_attribute_impl::parse_simple; case 1: return strconv_attribute_impl::parse_simple; case 2: return strconv_attribute_impl::parse_eol; case 3: return strconv_attribute_impl::parse_eol; case 4: return strconv_attribute_impl::parse_wconv; case 5: return strconv_attribute_impl::parse_wconv; case 6: return strconv_attribute_impl::parse_wconv; case 7: return strconv_attribute_impl::parse_wconv; case 8: return strconv_attribute_impl::parse_wnorm; case 9: return strconv_attribute_impl::parse_wnorm; case 10: return strconv_attribute_impl::parse_wnorm; case 11: return strconv_attribute_impl::parse_wnorm; case 12: return strconv_attribute_impl::parse_wnorm; case 13: return strconv_attribute_impl::parse_wnorm; case 14: return strconv_attribute_impl::parse_wnorm; case 15: return strconv_attribute_impl::parse_wnorm; default: assert(false); return 0; // unreachable } } inline xml_parse_result make_parse_result(xml_parse_status status, ptrdiff_t offset = 0) { xml_parse_result result; result.status = status; result.offset = offset; return result; } struct xml_parser { xml_allocator* alloc; char_t* error_offset; xml_parse_status error_status; xml_parser(xml_allocator* alloc_): alloc(alloc_), error_offset(0), error_status(status_ok) { } // DOCTYPE consists of nested sections of the following possible types: // , , "...", '...' // // // First group can not contain nested groups // Second group can contain nested groups of the same type // Third group can contain all other groups char_t* parse_doctype_primitive(char_t* s) { if (*s == '"' || *s == '\'') { // quoted string char_t ch = *s++; PUGI_IMPL_SCANFOR(*s == ch); if (!*s) PUGI_IMPL_THROW_ERROR(status_bad_doctype, s); s++; } else if (s[0] == '<' && s[1] == '?') { // s += 2; PUGI_IMPL_SCANFOR(s[0] == '?' && s[1] == '>'); // no need for ENDSWITH because ?> can't terminate proper doctype if (!*s) PUGI_IMPL_THROW_ERROR(status_bad_doctype, s); s += 2; } else if (s[0] == '<' && s[1] == '!' && s[2] == '-' && s[3] == '-') { s += 4; PUGI_IMPL_SCANFOR(s[0] == '-' && s[1] == '-' && s[2] == '>'); // no need for ENDSWITH because --> can't terminate proper doctype if (!*s) PUGI_IMPL_THROW_ERROR(status_bad_doctype, s); s += 3; } else PUGI_IMPL_THROW_ERROR(status_bad_doctype, s); return s; } char_t* parse_doctype_ignore(char_t* s) { size_t depth = 0; assert(s[0] == '<' && s[1] == '!' && s[2] == '['); s += 3; while (*s) { if (s[0] == '<' && s[1] == '!' && s[2] == '[') { // nested ignore section s += 3; depth++; } else if (s[0] == ']' && s[1] == ']' && s[2] == '>') { // ignore section end s += 3; if (depth == 0) return s; depth--; } else s++; } PUGI_IMPL_THROW_ERROR(status_bad_doctype, s); } char_t* parse_doctype_group(char_t* s, char_t endch) { size_t depth = 0; assert((s[0] == '<' || s[0] == 0) && s[1] == '!'); s += 2; while (*s) { if (s[0] == '<' && s[1] == '!' && s[2] != '-') { if (s[2] == '[') { // ignore s = parse_doctype_ignore(s); if (!s) return s; } else { // some control group s += 2; depth++; } } else if (s[0] == '<' || s[0] == '"' || s[0] == '\'') { // unknown tag (forbidden), or some primitive group s = parse_doctype_primitive(s); if (!s) return s; } else if (*s == '>') { if (depth == 0) return s; depth--; s++; } else s++; } if (depth != 0 || endch != '>') PUGI_IMPL_THROW_ERROR(status_bad_doctype, s); return s; } char_t* parse_exclamation(char_t* s, xml_node_struct* cursor, unsigned int optmsk, char_t endch) { // parse node contents, starting with exclamation mark ++s; if (*s == '-') // 'value = s; // Save the offset. } if (PUGI_IMPL_OPTSET(parse_eol) && PUGI_IMPL_OPTSET(parse_comments)) { s = strconv_comment(s, endch); if (!s) PUGI_IMPL_THROW_ERROR(status_bad_comment, cursor->value); } else { // Scan for terminating '-->'. PUGI_IMPL_SCANFOR(s[0] == '-' && s[1] == '-' && PUGI_IMPL_ENDSWITH(s[2], '>')); PUGI_IMPL_CHECK_ERROR(status_bad_comment, s); if (PUGI_IMPL_OPTSET(parse_comments)) *s = 0; // Zero-terminate this segment at the first terminating '-'. s += (s[2] == '>' ? 3 : 2); // Step over the '\0->'. } } else PUGI_IMPL_THROW_ERROR(status_bad_comment, s); } else if (*s == '[') { // 'value = s; // Save the offset. if (PUGI_IMPL_OPTSET(parse_eol)) { s = strconv_cdata(s, endch); if (!s) PUGI_IMPL_THROW_ERROR(status_bad_cdata, cursor->value); } else { // Scan for terminating ']]>'. PUGI_IMPL_SCANFOR(s[0] == ']' && s[1] == ']' && PUGI_IMPL_ENDSWITH(s[2], '>')); PUGI_IMPL_CHECK_ERROR(status_bad_cdata, s); *s++ = 0; // Zero-terminate this segment. } } else // Flagged for discard, but we still have to scan for the terminator. { // Scan for terminating ']]>'. PUGI_IMPL_SCANFOR(s[0] == ']' && s[1] == ']' && PUGI_IMPL_ENDSWITH(s[2], '>')); PUGI_IMPL_CHECK_ERROR(status_bad_cdata, s); ++s; } s += (s[1] == '>' ? 2 : 1); // Step over the last ']>'. } else PUGI_IMPL_THROW_ERROR(status_bad_cdata, s); } else if (s[0] == 'D' && s[1] == 'O' && s[2] == 'C' && s[3] == 'T' && s[4] == 'Y' && s[5] == 'P' && PUGI_IMPL_ENDSWITH(s[6], 'E')) { s -= 2; if (cursor->parent) PUGI_IMPL_THROW_ERROR(status_bad_doctype, s); char_t* mark = s + 9; s = parse_doctype_group(s, endch); if (!s) return s; assert((*s == 0 && endch == '>') || *s == '>'); if (*s) *s++ = 0; if (PUGI_IMPL_OPTSET(parse_doctype)) { while (PUGI_IMPL_IS_CHARTYPE(*mark, ct_space)) ++mark; PUGI_IMPL_PUSHNODE(node_doctype); cursor->value = mark; } } else if (*s == 0 && endch == '-') PUGI_IMPL_THROW_ERROR(status_bad_comment, s); else if (*s == 0 && endch == '[') PUGI_IMPL_THROW_ERROR(status_bad_cdata, s); else PUGI_IMPL_THROW_ERROR(status_unrecognized_tag, s); return s; } char_t* parse_question(char_t* s, xml_node_struct*& ref_cursor, unsigned int optmsk, char_t endch) { // load into registers xml_node_struct* cursor = ref_cursor; char_t ch = 0; // parse node contents, starting with question mark ++s; // read PI target char_t* target = s; if (!PUGI_IMPL_IS_CHARTYPE(*s, ct_start_symbol)) PUGI_IMPL_THROW_ERROR(status_bad_pi, s); PUGI_IMPL_SCANWHILE(PUGI_IMPL_IS_CHARTYPE(*s, ct_symbol)); PUGI_IMPL_CHECK_ERROR(status_bad_pi, s); // determine node type; stricmp / strcasecmp is not portable bool declaration = (target[0] | ' ') == 'x' && (target[1] | ' ') == 'm' && (target[2] | ' ') == 'l' && target + 3 == s; if (declaration ? PUGI_IMPL_OPTSET(parse_declaration) : PUGI_IMPL_OPTSET(parse_pi)) { if (declaration) { // disallow non top-level declarations if (cursor->parent) PUGI_IMPL_THROW_ERROR(status_bad_pi, s); PUGI_IMPL_PUSHNODE(node_declaration); } else { PUGI_IMPL_PUSHNODE(node_pi); } cursor->name = target; PUGI_IMPL_ENDSEG(); // parse value/attributes if (ch == '?') { // empty node if (!PUGI_IMPL_ENDSWITH(*s, '>')) PUGI_IMPL_THROW_ERROR(status_bad_pi, s); s += (*s == '>'); PUGI_IMPL_POPNODE(); } else if (PUGI_IMPL_IS_CHARTYPE(ch, ct_space)) { PUGI_IMPL_SKIPWS(); // scan for tag end char_t* value = s; PUGI_IMPL_SCANFOR(s[0] == '?' && PUGI_IMPL_ENDSWITH(s[1], '>')); PUGI_IMPL_CHECK_ERROR(status_bad_pi, s); if (declaration) { // replace ending ? with / so that 'element' terminates properly *s = '/'; // we exit from this function with cursor at node_declaration, which is a signal to parse() to go to LOC_ATTRIBUTES s = value; } else { // store value and step over > cursor->value = value; PUGI_IMPL_POPNODE(); PUGI_IMPL_ENDSEG(); s += (*s == '>'); } } else PUGI_IMPL_THROW_ERROR(status_bad_pi, s); } else { // scan for tag end PUGI_IMPL_SCANFOR(s[0] == '?' && PUGI_IMPL_ENDSWITH(s[1], '>')); PUGI_IMPL_CHECK_ERROR(status_bad_pi, s); s += (s[1] == '>' ? 2 : 1); } // store from registers ref_cursor = cursor; return s; } char_t* parse_tree(char_t* s, xml_node_struct* root, unsigned int optmsk, char_t endch) { strconv_attribute_t strconv_attribute = get_strconv_attribute(optmsk); strconv_pcdata_t strconv_pcdata = get_strconv_pcdata(optmsk); char_t ch = 0; xml_node_struct* cursor = root; char_t* mark = s; char_t* merged_pcdata = s; while (*s != 0) { if (*s == '<') { ++s; LOC_TAG: if (PUGI_IMPL_IS_CHARTYPE(*s, ct_start_symbol)) // '<#...' { PUGI_IMPL_PUSHNODE(node_element); // Append a new node to the tree. cursor->name = s; PUGI_IMPL_SCANWHILE_UNROLL(PUGI_IMPL_IS_CHARTYPE(ss, ct_symbol)); // Scan for a terminator. PUGI_IMPL_ENDSEG(); // Save char in 'ch', terminate & step over. if (ch == '>') { // end of tag } else if (PUGI_IMPL_IS_CHARTYPE(ch, ct_space)) { LOC_ATTRIBUTES: while (true) { PUGI_IMPL_SKIPWS(); // Eat any whitespace. if (PUGI_IMPL_IS_CHARTYPE(*s, ct_start_symbol)) // <... #... { xml_attribute_struct* a = append_new_attribute(cursor, *alloc); // Make space for this attribute. if (!a) PUGI_IMPL_THROW_ERROR(status_out_of_memory, s); a->name = s; // Save the offset. PUGI_IMPL_SCANWHILE_UNROLL(PUGI_IMPL_IS_CHARTYPE(ss, ct_symbol)); // Scan for a terminator. PUGI_IMPL_ENDSEG(); // Save char in 'ch', terminate & step over. if (PUGI_IMPL_IS_CHARTYPE(ch, ct_space)) { PUGI_IMPL_SKIPWS(); // Eat any whitespace. ch = *s; ++s; } if (ch == '=') // '<... #=...' { PUGI_IMPL_SKIPWS(); // Eat any whitespace. if (*s == '"' || *s == '\'') // '<... #="...' { ch = *s; // Save quote char to avoid breaking on "''" -or- '""'. ++s; // Step over the quote. a->value = s; // Save the offset. s = strconv_attribute(s, ch); if (!s) PUGI_IMPL_THROW_ERROR(status_bad_attribute, a->value); // After this line the loop continues from the start; // Whitespaces, / and > are ok, symbols and EOF are wrong, // everything else will be detected if (PUGI_IMPL_IS_CHARTYPE(*s, ct_start_symbol)) PUGI_IMPL_THROW_ERROR(status_bad_attribute, s); } else PUGI_IMPL_THROW_ERROR(status_bad_attribute, s); } else PUGI_IMPL_THROW_ERROR(status_bad_attribute, s); } else if (*s == '/') { ++s; if (*s == '>') { PUGI_IMPL_POPNODE(); s++; break; } else if (*s == 0 && endch == '>') { PUGI_IMPL_POPNODE(); break; } else PUGI_IMPL_THROW_ERROR(status_bad_start_element, s); } else if (*s == '>') { ++s; break; } else if (*s == 0 && endch == '>') { break; } else PUGI_IMPL_THROW_ERROR(status_bad_start_element, s); } // !!! } else if (ch == '/') // '<#.../' { if (!PUGI_IMPL_ENDSWITH(*s, '>')) PUGI_IMPL_THROW_ERROR(status_bad_start_element, s); PUGI_IMPL_POPNODE(); // Pop. s += (*s == '>'); } else if (ch == 0) { // we stepped over null terminator, backtrack & handle closing tag --s; if (endch != '>') PUGI_IMPL_THROW_ERROR(status_bad_start_element, s); } else PUGI_IMPL_THROW_ERROR(status_bad_start_element, s); } else if (*s == '/') { ++s; mark = s; char_t* name = cursor->name; if (!name) PUGI_IMPL_THROW_ERROR(status_end_element_mismatch, mark); while (PUGI_IMPL_IS_CHARTYPE(*s, ct_symbol)) { if (*s++ != *name++) PUGI_IMPL_THROW_ERROR(status_end_element_mismatch, mark); } if (*name) { if (*s == 0 && name[0] == endch && name[1] == 0) PUGI_IMPL_THROW_ERROR(status_bad_end_element, s); else PUGI_IMPL_THROW_ERROR(status_end_element_mismatch, mark); } PUGI_IMPL_POPNODE(); // Pop. PUGI_IMPL_SKIPWS(); if (*s == 0) { if (endch != '>') PUGI_IMPL_THROW_ERROR(status_bad_end_element, s); } else { if (*s != '>') PUGI_IMPL_THROW_ERROR(status_bad_end_element, s); ++s; } } else if (*s == '?') // 'first_child) continue; } } if (!PUGI_IMPL_OPTSET(parse_trim_pcdata)) s = mark; if (cursor->parent || PUGI_IMPL_OPTSET(parse_fragment)) { char_t* parsed_pcdata = s; s = strconv_pcdata(s); if (PUGI_IMPL_OPTSET(parse_embed_pcdata) && cursor->parent && !cursor->first_child && !cursor->value) { cursor->value = parsed_pcdata; // Save the offset. } else if (PUGI_IMPL_OPTSET(parse_merge_pcdata) && cursor->first_child && PUGI_IMPL_NODETYPE(cursor->first_child->prev_sibling_c) == node_pcdata) { assert(merged_pcdata >= cursor->first_child->prev_sibling_c->value); // Catch up to the end of last parsed value; only needed for the first fragment. merged_pcdata += strlength(merged_pcdata); size_t length = strlength(parsed_pcdata); // Must use memmove instead of memcpy as this move may overlap memmove(merged_pcdata, parsed_pcdata, (length + 1) * sizeof(char_t)); merged_pcdata += length; } else { xml_node_struct* prev_cursor = cursor; PUGI_IMPL_PUSHNODE(node_pcdata); // Append a new node on the tree. cursor->value = parsed_pcdata; // Save the offset. merged_pcdata = parsed_pcdata; // Used for parse_merge_pcdata above, cheaper to save unconditionally cursor = prev_cursor; // Pop since this is a standalone. } if (!*s) break; } else { PUGI_IMPL_SCANFOR(*s == '<'); // '...<' if (!*s) break; ++s; } // We're after '<' goto LOC_TAG; } } // check that last tag is closed if (cursor != root) PUGI_IMPL_THROW_ERROR(status_end_element_mismatch, s); return s; } #ifdef PUGIXML_WCHAR_MODE static char_t* parse_skip_bom(char_t* s) { unsigned int bom = 0xfeff; return (s[0] == static_cast(bom)) ? s + 1 : s; } #else static char_t* parse_skip_bom(char_t* s) { return (s[0] == '\xef' && s[1] == '\xbb' && s[2] == '\xbf') ? s + 3 : s; } #endif static bool has_element_node_siblings(xml_node_struct* node) { while (node) { if (PUGI_IMPL_NODETYPE(node) == node_element) return true; node = node->next_sibling; } return false; } static xml_parse_result parse(char_t* buffer, size_t length, xml_document_struct* xmldoc, xml_node_struct* root, unsigned int optmsk) { // early-out for empty documents if (length == 0) return make_parse_result(PUGI_IMPL_OPTSET(parse_fragment) ? status_ok : status_no_document_element); // get last child of the root before parsing xml_node_struct* last_root_child = root->first_child ? root->first_child->prev_sibling_c + 0 : 0; // create parser on stack xml_parser parser(static_cast(xmldoc)); // save last character and make buffer zero-terminated (speeds up parsing) char_t endch = buffer[length - 1]; buffer[length - 1] = 0; // skip BOM to make sure it does not end up as part of parse output char_t* buffer_data = parse_skip_bom(buffer); // perform actual parsing parser.parse_tree(buffer_data, root, optmsk, endch); xml_parse_result result = make_parse_result(parser.error_status, parser.error_offset ? parser.error_offset - buffer : 0); assert(result.offset >= 0 && static_cast(result.offset) <= length); if (result) { // since we removed last character, we have to handle the only possible false positive (stray <) if (endch == '<') return make_parse_result(status_unrecognized_tag, length - 1); // check if there are any element nodes parsed xml_node_struct* first_root_child_parsed = last_root_child ? last_root_child->next_sibling + 0 : root->first_child + 0; if (!PUGI_IMPL_OPTSET(parse_fragment) && !has_element_node_siblings(first_root_child_parsed)) return make_parse_result(status_no_document_element, length - 1); } else { // roll back offset if it occurs on a null terminator in the source buffer if (result.offset > 0 && static_cast(result.offset) == length - 1 && endch == 0) result.offset--; } return result; } }; // Output facilities PUGI_IMPL_FN xml_encoding get_write_native_encoding() { #ifdef PUGIXML_WCHAR_MODE return get_wchar_encoding(); #else return encoding_utf8; #endif } PUGI_IMPL_FN xml_encoding get_write_encoding(xml_encoding encoding) { // replace wchar encoding with utf implementation if (encoding == encoding_wchar) return get_wchar_encoding(); // replace utf16 encoding with utf16 with specific endianness if (encoding == encoding_utf16) return is_little_endian() ? encoding_utf16_le : encoding_utf16_be; // replace utf32 encoding with utf32 with specific endianness if (encoding == encoding_utf32) return is_little_endian() ? encoding_utf32_le : encoding_utf32_be; // only do autodetection if no explicit encoding is requested if (encoding != encoding_auto) return encoding; // assume utf8 encoding return encoding_utf8; } template PUGI_IMPL_FN size_t convert_buffer_output_generic(typename T::value_type dest, const char_t* data, size_t length, D, T) { PUGI_IMPL_STATIC_ASSERT(sizeof(char_t) == sizeof(typename D::type)); typename T::value_type end = D::process(reinterpret_cast(data), length, dest, T()); return static_cast(end - dest) * sizeof(*dest); } template PUGI_IMPL_FN size_t convert_buffer_output_generic(typename T::value_type dest, const char_t* data, size_t length, D, T, bool opt_swap) { PUGI_IMPL_STATIC_ASSERT(sizeof(char_t) == sizeof(typename D::type)); typename T::value_type end = D::process(reinterpret_cast(data), length, dest, T()); if (opt_swap) { for (typename T::value_type i = dest; i != end; ++i) *i = endian_swap(*i); } return static_cast(end - dest) * sizeof(*dest); } #ifdef PUGIXML_WCHAR_MODE PUGI_IMPL_FN size_t get_valid_length(const char_t* data, size_t length) { if (length < 1) return 0; // discard last character if it's the lead of a surrogate pair return (sizeof(wchar_t) == 2 && static_cast(static_cast(data[length - 1]) - 0xD800) < 0x400) ? length - 1 : length; } PUGI_IMPL_FN size_t convert_buffer_output(char_t* r_char, uint8_t* r_u8, uint16_t* r_u16, uint32_t* r_u32, const char_t* data, size_t length, xml_encoding encoding) { // only endian-swapping is required if (need_endian_swap_utf(encoding, get_wchar_encoding())) { convert_wchar_endian_swap(r_char, data, length); return length * sizeof(char_t); } // convert to utf8 if (encoding == encoding_utf8) return convert_buffer_output_generic(r_u8, data, length, wchar_decoder(), utf8_writer()); // convert to utf16 if (encoding == encoding_utf16_be || encoding == encoding_utf16_le) { xml_encoding native_encoding = is_little_endian() ? encoding_utf16_le : encoding_utf16_be; return convert_buffer_output_generic(r_u16, data, length, wchar_decoder(), utf16_writer(), native_encoding != encoding); } // convert to utf32 if (encoding == encoding_utf32_be || encoding == encoding_utf32_le) { xml_encoding native_encoding = is_little_endian() ? encoding_utf32_le : encoding_utf32_be; return convert_buffer_output_generic(r_u32, data, length, wchar_decoder(), utf32_writer(), native_encoding != encoding); } // convert to latin1 if (encoding == encoding_latin1) return convert_buffer_output_generic(r_u8, data, length, wchar_decoder(), latin1_writer()); assert(false && "Invalid encoding"); // unreachable return 0; } #else PUGI_IMPL_FN size_t get_valid_length(const char_t* data, size_t length) { if (length < 5) return 0; for (size_t i = 1; i <= 4; ++i) { uint8_t ch = static_cast(data[length - i]); // either a standalone character or a leading one if ((ch & 0xc0) != 0x80) return length - i; } // there are four non-leading characters at the end, sequence tail is broken so might as well process the whole chunk return length; } PUGI_IMPL_FN size_t convert_buffer_output(char_t* /* r_char */, uint8_t* r_u8, uint16_t* r_u16, uint32_t* r_u32, const char_t* data, size_t length, xml_encoding encoding) { if (encoding == encoding_utf16_be || encoding == encoding_utf16_le) { xml_encoding native_encoding = is_little_endian() ? encoding_utf16_le : encoding_utf16_be; return convert_buffer_output_generic(r_u16, data, length, utf8_decoder(), utf16_writer(), native_encoding != encoding); } if (encoding == encoding_utf32_be || encoding == encoding_utf32_le) { xml_encoding native_encoding = is_little_endian() ? encoding_utf32_le : encoding_utf32_be; return convert_buffer_output_generic(r_u32, data, length, utf8_decoder(), utf32_writer(), native_encoding != encoding); } if (encoding == encoding_latin1) return convert_buffer_output_generic(r_u8, data, length, utf8_decoder(), latin1_writer()); assert(false && "Invalid encoding"); // unreachable return 0; } #endif class xml_buffered_writer { xml_buffered_writer(const xml_buffered_writer&); xml_buffered_writer& operator=(const xml_buffered_writer&); public: xml_buffered_writer(xml_writer& writer_, xml_encoding user_encoding): writer(writer_), bufsize(0), encoding(get_write_encoding(user_encoding)) { PUGI_IMPL_STATIC_ASSERT(bufcapacity >= 8); } size_t flush() { flush(buffer, bufsize); bufsize = 0; return 0; } void flush(const char_t* data, size_t size) { if (size == 0) return; // fast path, just write data if (encoding == get_write_native_encoding()) writer.write(data, size * sizeof(char_t)); else { // convert chunk size_t result = convert_buffer_output(scratch.data_char, scratch.data_u8, scratch.data_u16, scratch.data_u32, data, size, encoding); assert(result <= sizeof(scratch)); // write data writer.write(scratch.data_u8, result); } } void write_direct(const char_t* data, size_t length) { // flush the remaining buffer contents flush(); // handle large chunks if (length > bufcapacity) { if (encoding == get_write_native_encoding()) { // fast path, can just write data chunk writer.write(data, length * sizeof(char_t)); return; } // need to convert in suitable chunks while (length > bufcapacity) { // get chunk size by selecting such number of characters that are guaranteed to fit into scratch buffer // and form a complete codepoint sequence (i.e. discard start of last codepoint if necessary) size_t chunk_size = get_valid_length(data, bufcapacity); assert(chunk_size); // convert chunk and write flush(data, chunk_size); // iterate data += chunk_size; length -= chunk_size; } // small tail is copied below bufsize = 0; } memcpy(buffer + bufsize, data, length * sizeof(char_t)); bufsize += length; } void write_buffer(const char_t* data, size_t length) { size_t offset = bufsize; if (offset + length <= bufcapacity) { memcpy(buffer + offset, data, length * sizeof(char_t)); bufsize = offset + length; } else { write_direct(data, length); } } void write_string(const char_t* data) { // write the part of the string that fits in the buffer size_t offset = bufsize; while (*data && offset < bufcapacity) buffer[offset++] = *data++; // write the rest if (offset < bufcapacity) { bufsize = offset; } else { // backtrack a bit if we have split the codepoint size_t length = offset - bufsize; size_t extra = length - get_valid_length(data - length, length); bufsize = offset - extra; write_direct(data - extra, strlength(data) + extra); } } void write(char_t d0) { size_t offset = bufsize; if (offset > bufcapacity - 1) offset = flush(); buffer[offset + 0] = d0; bufsize = offset + 1; } void write(char_t d0, char_t d1) { size_t offset = bufsize; if (offset > bufcapacity - 2) offset = flush(); buffer[offset + 0] = d0; buffer[offset + 1] = d1; bufsize = offset + 2; } void write(char_t d0, char_t d1, char_t d2) { size_t offset = bufsize; if (offset > bufcapacity - 3) offset = flush(); buffer[offset + 0] = d0; buffer[offset + 1] = d1; buffer[offset + 2] = d2; bufsize = offset + 3; } void write(char_t d0, char_t d1, char_t d2, char_t d3) { size_t offset = bufsize; if (offset > bufcapacity - 4) offset = flush(); buffer[offset + 0] = d0; buffer[offset + 1] = d1; buffer[offset + 2] = d2; buffer[offset + 3] = d3; bufsize = offset + 4; } void write(char_t d0, char_t d1, char_t d2, char_t d3, char_t d4) { size_t offset = bufsize; if (offset > bufcapacity - 5) offset = flush(); buffer[offset + 0] = d0; buffer[offset + 1] = d1; buffer[offset + 2] = d2; buffer[offset + 3] = d3; buffer[offset + 4] = d4; bufsize = offset + 5; } void write(char_t d0, char_t d1, char_t d2, char_t d3, char_t d4, char_t d5) { size_t offset = bufsize; if (offset > bufcapacity - 6) offset = flush(); buffer[offset + 0] = d0; buffer[offset + 1] = d1; buffer[offset + 2] = d2; buffer[offset + 3] = d3; buffer[offset + 4] = d4; buffer[offset + 5] = d5; bufsize = offset + 6; } // utf8 maximum expansion: x4 (-> utf32) // utf16 maximum expansion: x2 (-> utf32) // utf32 maximum expansion: x1 enum { bufcapacitybytes = #ifdef PUGIXML_MEMORY_OUTPUT_STACK PUGIXML_MEMORY_OUTPUT_STACK #else 10240 #endif , bufcapacity = bufcapacitybytes / (sizeof(char_t) + 4) }; char_t buffer[bufcapacity]; union { uint8_t data_u8[4 * bufcapacity]; uint16_t data_u16[2 * bufcapacity]; uint32_t data_u32[bufcapacity]; char_t data_char[bufcapacity]; } scratch; xml_writer& writer; size_t bufsize; xml_encoding encoding; }; PUGI_IMPL_FN void text_output_escaped(xml_buffered_writer& writer, const char_t* s, chartypex_t type, unsigned int flags) { while (*s) { const char_t* prev = s; // While *s is a usual symbol PUGI_IMPL_SCANWHILE_UNROLL(!PUGI_IMPL_IS_CHARTYPEX(ss, type)); writer.write_buffer(prev, static_cast(s - prev)); switch (*s) { case 0: break; case '&': writer.write('&', 'a', 'm', 'p', ';'); ++s; break; case '<': writer.write('&', 'l', 't', ';'); ++s; break; case '>': writer.write('&', 'g', 't', ';'); ++s; break; case '"': if (flags & format_attribute_single_quote) writer.write('"'); else writer.write('&', 'q', 'u', 'o', 't', ';'); ++s; break; case '\'': if (flags & format_attribute_single_quote) writer.write('&', 'a', 'p', 'o', 's', ';'); else writer.write('\''); ++s; break; default: // s is not a usual symbol { unsigned int ch = static_cast(*s++); assert(ch < 32); if (!(flags & format_skip_control_chars)) writer.write('&', '#', static_cast((ch / 10) + '0'), static_cast((ch % 10) + '0'), ';'); } } } } PUGI_IMPL_FN void text_output(xml_buffered_writer& writer, const char_t* s, chartypex_t type, unsigned int flags) { if (flags & format_no_escapes) writer.write_string(s); else text_output_escaped(writer, s, type, flags); } PUGI_IMPL_FN void text_output_cdata(xml_buffered_writer& writer, const char_t* s) { do { writer.write('<', '!', '[', 'C', 'D'); writer.write('A', 'T', 'A', '['); const char_t* prev = s; // look for ]]> sequence - we can't output it as is since it terminates CDATA while (*s && !(s[0] == ']' && s[1] == ']' && s[2] == '>')) ++s; // skip ]] if we stopped at ]]>, > will go to the next CDATA section if (*s) s += 2; writer.write_buffer(prev, static_cast(s - prev)); writer.write(']', ']', '>'); } while (*s); } PUGI_IMPL_FN void text_output_indent(xml_buffered_writer& writer, const char_t* indent, size_t indent_length, unsigned int depth) { switch (indent_length) { case 1: { for (unsigned int i = 0; i < depth; ++i) writer.write(indent[0]); break; } case 2: { for (unsigned int i = 0; i < depth; ++i) writer.write(indent[0], indent[1]); break; } case 3: { for (unsigned int i = 0; i < depth; ++i) writer.write(indent[0], indent[1], indent[2]); break; } case 4: { for (unsigned int i = 0; i < depth; ++i) writer.write(indent[0], indent[1], indent[2], indent[3]); break; } default: { for (unsigned int i = 0; i < depth; ++i) writer.write_buffer(indent, indent_length); } } } PUGI_IMPL_FN void node_output_comment(xml_buffered_writer& writer, const char_t* s) { writer.write('<', '!', '-', '-'); while (*s) { const char_t* prev = s; // look for -\0 or -- sequence - we can't output it since -- is illegal in comment body while (*s && !(s[0] == '-' && (s[1] == '-' || s[1] == 0))) ++s; writer.write_buffer(prev, static_cast(s - prev)); if (*s) { assert(*s == '-'); writer.write('-', ' '); ++s; } } writer.write('-', '-', '>'); } PUGI_IMPL_FN void node_output_pi_value(xml_buffered_writer& writer, const char_t* s) { while (*s) { const char_t* prev = s; // look for ?> sequence - we can't output it since ?> terminates PI while (*s && !(s[0] == '?' && s[1] == '>')) ++s; writer.write_buffer(prev, static_cast(s - prev)); if (*s) { assert(s[0] == '?' && s[1] == '>'); writer.write('?', ' ', '>'); s += 2; } } } PUGI_IMPL_FN void node_output_attributes(xml_buffered_writer& writer, xml_node_struct* node, const char_t* indent, size_t indent_length, unsigned int flags, unsigned int depth) { const char_t* default_name = PUGIXML_TEXT(":anonymous"); const char_t enquotation_char = (flags & format_attribute_single_quote) ? '\'' : '"'; for (xml_attribute_struct* a = node->first_attribute; a; a = a->next_attribute) { if ((flags & (format_indent_attributes | format_raw)) == format_indent_attributes) { writer.write('\n'); text_output_indent(writer, indent, indent_length, depth + 1); } else { writer.write(' '); } writer.write_string(a->name ? a->name + 0 : default_name); writer.write('=', enquotation_char); if (a->value) text_output(writer, a->value, ctx_special_attr, flags); writer.write(enquotation_char); } } PUGI_IMPL_FN bool node_output_start(xml_buffered_writer& writer, xml_node_struct* node, const char_t* indent, size_t indent_length, unsigned int flags, unsigned int depth) { const char_t* default_name = PUGIXML_TEXT(":anonymous"); const char_t* name = node->name ? node->name + 0 : default_name; writer.write('<'); writer.write_string(name); if (node->first_attribute) node_output_attributes(writer, node, indent, indent_length, flags, depth); // element nodes can have value if parse_embed_pcdata was used if (!node->value) { if (!node->first_child) { if (flags & format_no_empty_element_tags) { writer.write('>', '<', '/'); writer.write_string(name); writer.write('>'); return false; } else { if ((flags & format_raw) == 0) writer.write(' '); writer.write('/', '>'); return false; } } else { writer.write('>'); return true; } } else { writer.write('>'); text_output(writer, node->value, ctx_special_pcdata, flags); if (!node->first_child) { writer.write('<', '/'); writer.write_string(name); writer.write('>'); return false; } else { return true; } } } PUGI_IMPL_FN void node_output_end(xml_buffered_writer& writer, xml_node_struct* node) { const char_t* default_name = PUGIXML_TEXT(":anonymous"); const char_t* name = node->name ? node->name + 0 : default_name; writer.write('<', '/'); writer.write_string(name); writer.write('>'); } PUGI_IMPL_FN void node_output_simple(xml_buffered_writer& writer, xml_node_struct* node, unsigned int flags) { const char_t* default_name = PUGIXML_TEXT(":anonymous"); switch (PUGI_IMPL_NODETYPE(node)) { case node_pcdata: text_output(writer, node->value ? node->value + 0 : PUGIXML_TEXT(""), ctx_special_pcdata, flags); break; case node_cdata: text_output_cdata(writer, node->value ? node->value + 0 : PUGIXML_TEXT("")); break; case node_comment: node_output_comment(writer, node->value ? node->value + 0 : PUGIXML_TEXT("")); break; case node_pi: writer.write('<', '?'); writer.write_string(node->name ? node->name + 0 : default_name); if (node->value) { writer.write(' '); node_output_pi_value(writer, node->value); } writer.write('?', '>'); break; case node_declaration: writer.write('<', '?'); writer.write_string(node->name ? node->name + 0 : default_name); node_output_attributes(writer, node, PUGIXML_TEXT(""), 0, flags | format_raw, 0); writer.write('?', '>'); break; case node_doctype: writer.write('<', '!', 'D', 'O', 'C'); writer.write('T', 'Y', 'P', 'E'); if (node->value) { writer.write(' '); writer.write_string(node->value); } writer.write('>'); break; default: assert(false && "Invalid node type"); // unreachable } } enum indent_flags_t { indent_newline = 1, indent_indent = 2 }; PUGI_IMPL_FN void node_output(xml_buffered_writer& writer, xml_node_struct* root, const char_t* indent, unsigned int flags, unsigned int depth) { size_t indent_length = ((flags & (format_indent | format_indent_attributes)) && (flags & format_raw) == 0) ? strlength(indent) : 0; unsigned int indent_flags = indent_indent; xml_node_struct* node = root; do { assert(node); // begin writing current node if (PUGI_IMPL_NODETYPE(node) == node_pcdata || PUGI_IMPL_NODETYPE(node) == node_cdata) { node_output_simple(writer, node, flags); indent_flags = 0; } else { if ((indent_flags & indent_newline) && (flags & format_raw) == 0) writer.write('\n'); if ((indent_flags & indent_indent) && indent_length) text_output_indent(writer, indent, indent_length, depth); if (PUGI_IMPL_NODETYPE(node) == node_element) { indent_flags = indent_newline | indent_indent; if (node_output_start(writer, node, indent, indent_length, flags, depth)) { // element nodes can have value if parse_embed_pcdata was used if (node->value) indent_flags = 0; node = node->first_child; depth++; continue; } } else if (PUGI_IMPL_NODETYPE(node) == node_document) { indent_flags = indent_indent; if (node->first_child) { node = node->first_child; continue; } } else { node_output_simple(writer, node, flags); indent_flags = indent_newline | indent_indent; } } // continue to the next node while (node != root) { if (node->next_sibling) { node = node->next_sibling; break; } node = node->parent; // write closing node if (PUGI_IMPL_NODETYPE(node) == node_element) { depth--; if ((indent_flags & indent_newline) && (flags & format_raw) == 0) writer.write('\n'); if ((indent_flags & indent_indent) && indent_length) text_output_indent(writer, indent, indent_length, depth); node_output_end(writer, node); indent_flags = indent_newline | indent_indent; } } } while (node != root); if ((indent_flags & indent_newline) && (flags & format_raw) == 0) writer.write('\n'); } PUGI_IMPL_FN bool has_declaration(xml_node_struct* node) { for (xml_node_struct* child = node->first_child; child; child = child->next_sibling) { xml_node_type type = PUGI_IMPL_NODETYPE(child); if (type == node_declaration) return true; if (type == node_element) return false; } return false; } PUGI_IMPL_FN bool is_attribute_of(xml_attribute_struct* attr, xml_node_struct* node) { for (xml_attribute_struct* a = node->first_attribute; a; a = a->next_attribute) if (a == attr) return true; return false; } PUGI_IMPL_FN bool allow_insert_attribute(xml_node_type parent) { return parent == node_element || parent == node_declaration; } PUGI_IMPL_FN bool allow_insert_child(xml_node_type parent, xml_node_type child) { if (parent != node_document && parent != node_element) return false; if (child == node_document || child == node_null) return false; if (parent != node_document && (child == node_declaration || child == node_doctype)) return false; return true; } PUGI_IMPL_FN bool allow_move(xml_node parent, xml_node child) { // check that child can be a child of parent if (!allow_insert_child(parent.type(), child.type())) return false; // check that node is not moved between documents if (parent.root() != child.root()) return false; // check that new parent is not in the child subtree xml_node cur = parent; while (cur) { if (cur == child) return false; cur = cur.parent(); } return true; } template PUGI_IMPL_FN void node_copy_string(String& dest, Header& header, uintptr_t header_mask, char_t* source, Header& source_header, xml_allocator* alloc) { assert(!dest && (header & header_mask) == 0); // copies are performed into fresh nodes if (source) { if (alloc && (source_header & header_mask) == 0) { dest = source; // since strcpy_insitu can reuse document buffer memory we need to mark both source and dest as shared header |= xml_memory_page_contents_shared_mask; source_header |= xml_memory_page_contents_shared_mask; } else strcpy_insitu(dest, header, header_mask, source, strlength(source)); } } PUGI_IMPL_FN void node_copy_contents(xml_node_struct* dn, xml_node_struct* sn, xml_allocator* shared_alloc) { node_copy_string(dn->name, dn->header, xml_memory_page_name_allocated_mask, sn->name, sn->header, shared_alloc); node_copy_string(dn->value, dn->header, xml_memory_page_value_allocated_mask, sn->value, sn->header, shared_alloc); for (xml_attribute_struct* sa = sn->first_attribute; sa; sa = sa->next_attribute) { xml_attribute_struct* da = append_new_attribute(dn, get_allocator(dn)); if (da) { node_copy_string(da->name, da->header, xml_memory_page_name_allocated_mask, sa->name, sa->header, shared_alloc); node_copy_string(da->value, da->header, xml_memory_page_value_allocated_mask, sa->value, sa->header, shared_alloc); } } } PUGI_IMPL_FN void node_copy_tree(xml_node_struct* dn, xml_node_struct* sn) { xml_allocator& alloc = get_allocator(dn); xml_allocator* shared_alloc = (&alloc == &get_allocator(sn)) ? &alloc : 0; node_copy_contents(dn, sn, shared_alloc); xml_node_struct* dit = dn; xml_node_struct* sit = sn->first_child; while (sit && sit != sn) { // loop invariant: dit is inside the subtree rooted at dn assert(dit); // when a tree is copied into one of the descendants, we need to skip that subtree to avoid an infinite loop if (sit != dn) { xml_node_struct* copy = append_new_node(dit, alloc, PUGI_IMPL_NODETYPE(sit)); if (copy) { node_copy_contents(copy, sit, shared_alloc); if (sit->first_child) { dit = copy; sit = sit->first_child; continue; } } } // continue to the next node do { if (sit->next_sibling) { sit = sit->next_sibling; break; } sit = sit->parent; dit = dit->parent; // loop invariant: dit is inside the subtree rooted at dn while sit is inside sn assert(sit == sn || dit); } while (sit != sn); } assert(!sit || dit == dn->parent); } PUGI_IMPL_FN void node_copy_attribute(xml_attribute_struct* da, xml_attribute_struct* sa) { xml_allocator& alloc = get_allocator(da); xml_allocator* shared_alloc = (&alloc == &get_allocator(sa)) ? &alloc : 0; node_copy_string(da->name, da->header, xml_memory_page_name_allocated_mask, sa->name, sa->header, shared_alloc); node_copy_string(da->value, da->header, xml_memory_page_value_allocated_mask, sa->value, sa->header, shared_alloc); } inline bool is_text_node(xml_node_struct* node) { xml_node_type type = PUGI_IMPL_NODETYPE(node); return type == node_pcdata || type == node_cdata; } // get value with conversion functions template PUGI_IMPL_FN PUGI_IMPL_UNSIGNED_OVERFLOW U string_to_integer(const char_t* value, U minv, U maxv) { U result = 0; const char_t* s = value; while (PUGI_IMPL_IS_CHARTYPE(*s, ct_space)) s++; bool negative = (*s == '-'); s += (*s == '+' || *s == '-'); bool overflow = false; if (s[0] == '0' && (s[1] | ' ') == 'x') { s += 2; // since overflow detection relies on length of the sequence skip leading zeros while (*s == '0') s++; const char_t* start = s; for (;;) { if (static_cast(*s - '0') < 10) result = result * 16 + (*s - '0'); else if (static_cast((*s | ' ') - 'a') < 6) result = result * 16 + ((*s | ' ') - 'a' + 10); else break; s++; } size_t digits = static_cast(s - start); overflow = digits > sizeof(U) * 2; } else { // since overflow detection relies on length of the sequence skip leading zeros while (*s == '0') s++; const char_t* start = s; for (;;) { if (static_cast(*s - '0') < 10) result = result * 10 + (*s - '0'); else break; s++; } size_t digits = static_cast(s - start); PUGI_IMPL_STATIC_ASSERT(sizeof(U) == 8 || sizeof(U) == 4 || sizeof(U) == 2); const size_t max_digits10 = sizeof(U) == 8 ? 20 : sizeof(U) == 4 ? 10 : 5; const char_t max_lead = sizeof(U) == 8 ? '1' : sizeof(U) == 4 ? '4' : '6'; const size_t high_bit = sizeof(U) * 8 - 1; overflow = digits >= max_digits10 && !(digits == max_digits10 && (*start < max_lead || (*start == max_lead && result >> high_bit))); } if (negative) { // Workaround for crayc++ CC-3059: Expected no overflow in routine. #ifdef _CRAYC return (overflow || result > ~minv + 1) ? minv : ~result + 1; #else return (overflow || result > 0 - minv) ? minv : 0 - result; #endif } else return (overflow || result > maxv) ? maxv : result; } PUGI_IMPL_FN int get_value_int(const char_t* value) { return string_to_integer(value, static_cast(INT_MIN), INT_MAX); } PUGI_IMPL_FN unsigned int get_value_uint(const char_t* value) { return string_to_integer(value, 0, UINT_MAX); } PUGI_IMPL_FN double get_value_double(const char_t* value) { #ifdef PUGIXML_WCHAR_MODE return wcstod(value, 0); #else return strtod(value, 0); #endif } PUGI_IMPL_FN float get_value_float(const char_t* value) { #ifdef PUGIXML_WCHAR_MODE return static_cast(wcstod(value, 0)); #else return static_cast(strtod(value, 0)); #endif } PUGI_IMPL_FN bool get_value_bool(const char_t* value) { // only look at first char char_t first = *value; // 1*, t* (true), T* (True), y* (yes), Y* (YES) return (first == '1' || first == 't' || first == 'T' || first == 'y' || first == 'Y'); } #ifdef PUGIXML_HAS_LONG_LONG PUGI_IMPL_FN long long get_value_llong(const char_t* value) { return string_to_integer(value, static_cast(LLONG_MIN), LLONG_MAX); } PUGI_IMPL_FN unsigned long long get_value_ullong(const char_t* value) { return string_to_integer(value, 0, ULLONG_MAX); } #endif template PUGI_IMPL_FN PUGI_IMPL_UNSIGNED_OVERFLOW char_t* integer_to_string(char_t* begin, char_t* end, U value, bool negative) { char_t* result = end - 1; U rest = negative ? 0 - value : value; do { *result-- = static_cast('0' + (rest % 10)); rest /= 10; } while (rest); assert(result >= begin); (void)begin; *result = '-'; return result + !negative; } // set value with conversion functions template PUGI_IMPL_FN bool set_value_ascii(String& dest, Header& header, uintptr_t header_mask, char* buf) { #ifdef PUGIXML_WCHAR_MODE char_t wbuf[128]; assert(strlen(buf) < sizeof(wbuf) / sizeof(wbuf[0])); size_t offset = 0; for (; buf[offset]; ++offset) wbuf[offset] = buf[offset]; return strcpy_insitu(dest, header, header_mask, wbuf, offset); #else return strcpy_insitu(dest, header, header_mask, buf, strlen(buf)); #endif } template PUGI_IMPL_FN bool set_value_integer(String& dest, Header& header, uintptr_t header_mask, U value, bool negative) { char_t buf[64]; char_t* end = buf + sizeof(buf) / sizeof(buf[0]); char_t* begin = integer_to_string(buf, end, value, negative); return strcpy_insitu(dest, header, header_mask, begin, end - begin); } template PUGI_IMPL_FN bool set_value_convert(String& dest, Header& header, uintptr_t header_mask, float value, int precision) { char buf[128]; PUGI_IMPL_SNPRINTF(buf, "%.*g", precision, double(value)); return set_value_ascii(dest, header, header_mask, buf); } template PUGI_IMPL_FN bool set_value_convert(String& dest, Header& header, uintptr_t header_mask, double value, int precision) { char buf[128]; PUGI_IMPL_SNPRINTF(buf, "%.*g", precision, value); return set_value_ascii(dest, header, header_mask, buf); } template PUGI_IMPL_FN bool set_value_bool(String& dest, Header& header, uintptr_t header_mask, bool value) { return strcpy_insitu(dest, header, header_mask, value ? PUGIXML_TEXT("true") : PUGIXML_TEXT("false"), value ? 4 : 5); } PUGI_IMPL_FN xml_parse_result load_buffer_impl(xml_document_struct* doc, xml_node_struct* root, void* contents, size_t size, unsigned int options, xml_encoding encoding, bool is_mutable, bool own, char_t** out_buffer) { // check input buffer if (!contents && size) return make_parse_result(status_io_error); // get actual encoding xml_encoding buffer_encoding = impl::get_buffer_encoding(encoding, contents, size); // if convert_buffer below throws bad_alloc, we still need to deallocate contents if we own it auto_deleter contents_guard(own ? contents : 0, xml_memory::deallocate); // get private buffer char_t* buffer = 0; size_t length = 0; // coverity[var_deref_model] if (!impl::convert_buffer(buffer, length, buffer_encoding, contents, size, is_mutable)) return impl::make_parse_result(status_out_of_memory); // after this we either deallocate contents (below) or hold on to it via doc->buffer, so we don't need to guard it contents_guard.release(); // delete original buffer if we performed a conversion if (own && buffer != contents && contents) impl::xml_memory::deallocate(contents); // grab onto buffer if it's our buffer, user is responsible for deallocating contents himself if (own || buffer != contents) *out_buffer = buffer; // store buffer for offset_debug doc->buffer = buffer; // parse xml_parse_result res = impl::xml_parser::parse(buffer, length, doc, root, options); // remember encoding res.encoding = buffer_encoding; return res; } // we need to get length of entire file to load it in memory; the only (relatively) sane way to do it is via seek/tell trick PUGI_IMPL_FN xml_parse_status get_file_size(FILE* file, size_t& out_result) { #if defined(__linux__) || defined(__APPLE__) // this simultaneously retrieves the file size and file mode (to guard against loading non-files) struct stat st; if (fstat(fileno(file), &st) != 0) return status_io_error; // anything that's not a regular file doesn't have a coherent length if (!S_ISREG(st.st_mode)) return status_io_error; typedef off_t length_type; length_type length = st.st_size; #elif defined(PUGI_IMPL_MSVC_CRT_VERSION) && PUGI_IMPL_MSVC_CRT_VERSION >= 1400 // there are 64-bit versions of fseek/ftell, let's use them typedef __int64 length_type; _fseeki64(file, 0, SEEK_END); length_type length = _ftelli64(file); _fseeki64(file, 0, SEEK_SET); #elif defined(__MINGW32__) && !defined(__NO_MINGW_LFS) && (!defined(__STRICT_ANSI__) || defined(__MINGW64_VERSION_MAJOR)) // there are 64-bit versions of fseek/ftell, let's use them typedef off64_t length_type; fseeko64(file, 0, SEEK_END); length_type length = ftello64(file); fseeko64(file, 0, SEEK_SET); #else // if this is a 32-bit OS, long is enough; if this is a unix system, long is 64-bit, which is enough; otherwise we can't do anything anyway. typedef long length_type; fseek(file, 0, SEEK_END); length_type length = ftell(file); fseek(file, 0, SEEK_SET); #endif // check for I/O errors if (length < 0) return status_io_error; // check for overflow size_t result = static_cast(length); if (static_cast(result) != length) return status_out_of_memory; // finalize out_result = result; return status_ok; } // This function assumes that buffer has extra sizeof(char_t) writable bytes after size PUGI_IMPL_FN size_t zero_terminate_buffer(void* buffer, size_t size, xml_encoding encoding) { // We only need to zero-terminate if encoding conversion does not do it for us #ifdef PUGIXML_WCHAR_MODE xml_encoding wchar_encoding = get_wchar_encoding(); if (encoding == wchar_encoding || need_endian_swap_utf(encoding, wchar_encoding)) { size_t length = size / sizeof(char_t); static_cast(buffer)[length] = 0; return (length + 1) * sizeof(char_t); } #else if (encoding == encoding_utf8) { static_cast(buffer)[size] = 0; return size + 1; } #endif return size; } PUGI_IMPL_FN xml_parse_result load_file_impl(xml_document_struct* doc, FILE* file, unsigned int options, xml_encoding encoding, char_t** out_buffer) { if (!file) return make_parse_result(status_file_not_found); // get file size (can result in I/O errors) size_t size = 0; xml_parse_status size_status = get_file_size(file, size); if (size_status != status_ok) return make_parse_result(size_status); size_t max_suffix_size = sizeof(char_t); // allocate buffer for the whole file char* contents = static_cast(xml_memory::allocate(size + max_suffix_size)); if (!contents) return make_parse_result(status_out_of_memory); // read file in memory size_t read_size = fread(contents, 1, size, file); if (read_size != size) { xml_memory::deallocate(contents); return make_parse_result(status_io_error); } xml_encoding real_encoding = get_buffer_encoding(encoding, contents, size); return load_buffer_impl(doc, doc, contents, zero_terminate_buffer(contents, size, real_encoding), options, real_encoding, true, true, out_buffer); } PUGI_IMPL_FN void close_file(FILE* file) { fclose(file); } #ifndef PUGIXML_NO_STL template struct xml_stream_chunk { static xml_stream_chunk* create() { void* memory = xml_memory::allocate(sizeof(xml_stream_chunk)); if (!memory) return 0; return new (memory) xml_stream_chunk(); } static void destroy(xml_stream_chunk* chunk) { // free chunk chain while (chunk) { xml_stream_chunk* next_ = chunk->next; xml_memory::deallocate(chunk); chunk = next_; } } xml_stream_chunk(): next(0), size(0) { } xml_stream_chunk* next; size_t size; T data[xml_memory_page_size / sizeof(T)]; }; template PUGI_IMPL_FN xml_parse_status load_stream_data_noseek(std::basic_istream& stream, void** out_buffer, size_t* out_size) { auto_deleter > chunks(0, xml_stream_chunk::destroy); // read file to a chunk list size_t total = 0; xml_stream_chunk* last = 0; while (!stream.eof()) { // allocate new chunk xml_stream_chunk* chunk = xml_stream_chunk::create(); if (!chunk) return status_out_of_memory; // append chunk to list if (last) last = last->next = chunk; else chunks.data = last = chunk; // read data to chunk stream.read(chunk->data, static_cast(sizeof(chunk->data) / sizeof(T))); chunk->size = static_cast(stream.gcount()) * sizeof(T); // read may set failbit | eofbit in case gcount() is less than read length, so check for other I/O errors if (stream.bad() || (!stream.eof() && stream.fail())) return status_io_error; // guard against huge files (chunk size is small enough to make this overflow check work) if (total + chunk->size < total) return status_out_of_memory; total += chunk->size; } size_t max_suffix_size = sizeof(char_t); // copy chunk list to a contiguous buffer char* buffer = static_cast(xml_memory::allocate(total + max_suffix_size)); if (!buffer) return status_out_of_memory; char* write = buffer; for (xml_stream_chunk* chunk = chunks.data; chunk; chunk = chunk->next) { assert(write + chunk->size <= buffer + total); memcpy(write, chunk->data, chunk->size); write += chunk->size; } assert(write == buffer + total); // return buffer *out_buffer = buffer; *out_size = total; return status_ok; } template PUGI_IMPL_FN xml_parse_status load_stream_data_seek(std::basic_istream& stream, void** out_buffer, size_t* out_size) { // get length of remaining data in stream typename std::basic_istream::pos_type pos = stream.tellg(); stream.seekg(0, std::ios::end); std::streamoff length = stream.tellg() - pos; stream.seekg(pos); if (stream.fail() || pos < 0) return status_io_error; // guard against huge files size_t read_length = static_cast(length); if (static_cast(read_length) != length || length < 0) return status_out_of_memory; size_t max_suffix_size = sizeof(char_t); // read stream data into memory (guard against stream exceptions with buffer holder) auto_deleter buffer(xml_memory::allocate(read_length * sizeof(T) + max_suffix_size), xml_memory::deallocate); if (!buffer.data) return status_out_of_memory; stream.read(static_cast(buffer.data), static_cast(read_length)); // read may set failbit | eofbit in case gcount() is less than read_length (i.e. line ending conversion), so check for other I/O errors if (stream.bad() || (!stream.eof() && stream.fail())) return status_io_error; // return buffer size_t actual_length = static_cast(stream.gcount()); assert(actual_length <= read_length); *out_buffer = buffer.release(); *out_size = actual_length * sizeof(T); return status_ok; } template PUGI_IMPL_FN xml_parse_result load_stream_impl(xml_document_struct* doc, std::basic_istream& stream, unsigned int options, xml_encoding encoding, char_t** out_buffer) { void* buffer = 0; size_t size = 0; xml_parse_status status = status_ok; // if stream has an error bit set, bail out (otherwise tellg() can fail and we'll clear error bits) if (stream.fail()) return make_parse_result(status_io_error); // load stream to memory (using seek-based implementation if possible, since it's faster and takes less memory) if (stream.tellg() < 0) { stream.clear(); // clear error flags that could be set by a failing tellg status = load_stream_data_noseek(stream, &buffer, &size); } else status = load_stream_data_seek(stream, &buffer, &size); if (status != status_ok) return make_parse_result(status); xml_encoding real_encoding = get_buffer_encoding(encoding, buffer, size); return load_buffer_impl(doc, doc, buffer, zero_terminate_buffer(buffer, size, real_encoding), options, real_encoding, true, true, out_buffer); } #endif #if defined(PUGI_IMPL_MSVC_CRT_VERSION) || defined(__BORLANDC__) || (defined(__MINGW32__) && (!defined(__STRICT_ANSI__) || defined(__MINGW64_VERSION_MAJOR))) PUGI_IMPL_FN FILE* open_file_wide(const wchar_t* path, const wchar_t* mode) { #if defined(PUGI_IMPL_MSVC_CRT_VERSION) && PUGI_IMPL_MSVC_CRT_VERSION >= 1400 FILE* file = 0; return _wfopen_s(&file, path, mode) == 0 ? file : 0; #else return _wfopen(path, mode); #endif } #else PUGI_IMPL_FN char* convert_path_heap(const wchar_t* str) { assert(str); // first pass: get length in utf8 characters size_t length = strlength_wide(str); size_t size = as_utf8_begin(str, length); // allocate resulting string char* result = static_cast(xml_memory::allocate(size + 1)); if (!result) return 0; // second pass: convert to utf8 as_utf8_end(result, size, str, length); // zero-terminate result[size] = 0; return result; } PUGI_IMPL_FN FILE* open_file_wide(const wchar_t* path, const wchar_t* mode) { // there is no standard function to open wide paths, so our best bet is to try utf8 path char* path_utf8 = convert_path_heap(path); if (!path_utf8) return 0; // convert mode to ASCII (we mirror _wfopen interface) char mode_ascii[4] = {0}; for (size_t i = 0; mode[i]; ++i) mode_ascii[i] = static_cast(mode[i]); // try to open the utf8 path FILE* result = fopen(path_utf8, mode_ascii); // free dummy buffer xml_memory::deallocate(path_utf8); return result; } #endif PUGI_IMPL_FN FILE* open_file(const char* path, const char* mode) { #if defined(PUGI_IMPL_MSVC_CRT_VERSION) && PUGI_IMPL_MSVC_CRT_VERSION >= 1400 FILE* file = 0; return fopen_s(&file, path, mode) == 0 ? file : 0; #else return fopen(path, mode); #endif } PUGI_IMPL_FN bool save_file_impl(const xml_document& doc, FILE* file, const char_t* indent, unsigned int flags, xml_encoding encoding) { if (!file) return false; xml_writer_file writer(file); doc.save(writer, indent, flags, encoding); return fflush(file) == 0 && ferror(file) == 0; } struct name_null_sentry { xml_node_struct* node; char_t* name; name_null_sentry(xml_node_struct* node_): node(node_), name(node_->name) { node->name = 0; } ~name_null_sentry() { node->name = name; } }; PUGI_IMPL_NS_END namespace pugi { PUGI_IMPL_FN xml_writer::~xml_writer() { } PUGI_IMPL_FN xml_writer_file::xml_writer_file(void* file_): file(file_) { } PUGI_IMPL_FN void xml_writer_file::write(const void* data, size_t size) { size_t result = fwrite(data, 1, size, static_cast(file)); (void)!result; // unfortunately we can't do proper error handling here } #ifndef PUGIXML_NO_STL PUGI_IMPL_FN xml_writer_stream::xml_writer_stream(std::basic_ostream >& stream): narrow_stream(&stream), wide_stream(0) { } PUGI_IMPL_FN xml_writer_stream::xml_writer_stream(std::basic_ostream >& stream): narrow_stream(0), wide_stream(&stream) { } PUGI_IMPL_FN void xml_writer_stream::write(const void* data, size_t size) { if (narrow_stream) { assert(!wide_stream); narrow_stream->write(reinterpret_cast(data), static_cast(size)); } else { assert(wide_stream); assert(size % sizeof(wchar_t) == 0); wide_stream->write(reinterpret_cast(data), static_cast(size / sizeof(wchar_t))); } } #endif PUGI_IMPL_FN xml_tree_walker::xml_tree_walker(): _depth(0) { } PUGI_IMPL_FN xml_tree_walker::~xml_tree_walker() { } PUGI_IMPL_FN int xml_tree_walker::depth() const { return _depth; } PUGI_IMPL_FN bool xml_tree_walker::begin(xml_node&) { return true; } PUGI_IMPL_FN bool xml_tree_walker::end(xml_node&) { return true; } PUGI_IMPL_FN xml_attribute::xml_attribute(): _attr(0) { } PUGI_IMPL_FN xml_attribute::xml_attribute(xml_attribute_struct* attr): _attr(attr) { } PUGI_IMPL_FN static void unspecified_bool_xml_attribute(xml_attribute***) { } PUGI_IMPL_FN xml_attribute::operator xml_attribute::unspecified_bool_type() const { return _attr ? unspecified_bool_xml_attribute : 0; } PUGI_IMPL_FN bool xml_attribute::operator!() const { return !_attr; } PUGI_IMPL_FN bool xml_attribute::operator==(const xml_attribute& r) const { return (_attr == r._attr); } PUGI_IMPL_FN bool xml_attribute::operator!=(const xml_attribute& r) const { return (_attr != r._attr); } PUGI_IMPL_FN bool xml_attribute::operator<(const xml_attribute& r) const { return (_attr < r._attr); } PUGI_IMPL_FN bool xml_attribute::operator>(const xml_attribute& r) const { return (_attr > r._attr); } PUGI_IMPL_FN bool xml_attribute::operator<=(const xml_attribute& r) const { return (_attr <= r._attr); } PUGI_IMPL_FN bool xml_attribute::operator>=(const xml_attribute& r) const { return (_attr >= r._attr); } PUGI_IMPL_FN xml_attribute xml_attribute::next_attribute() const { if (!_attr) return xml_attribute(); return xml_attribute(_attr->next_attribute); } PUGI_IMPL_FN xml_attribute xml_attribute::previous_attribute() const { if (!_attr) return xml_attribute(); xml_attribute_struct* prev = _attr->prev_attribute_c; return prev->next_attribute ? xml_attribute(prev) : xml_attribute(); } PUGI_IMPL_FN const char_t* xml_attribute::as_string(const char_t* def) const { if (!_attr) return def; const char_t* value = _attr->value; return value ? value : def; } PUGI_IMPL_FN int xml_attribute::as_int(int def) const { if (!_attr) return def; const char_t* value = _attr->value; return value ? impl::get_value_int(value) : def; } PUGI_IMPL_FN unsigned int xml_attribute::as_uint(unsigned int def) const { if (!_attr) return def; const char_t* value = _attr->value; return value ? impl::get_value_uint(value) : def; } PUGI_IMPL_FN double xml_attribute::as_double(double def) const { if (!_attr) return def; const char_t* value = _attr->value; return value ? impl::get_value_double(value) : def; } PUGI_IMPL_FN float xml_attribute::as_float(float def) const { if (!_attr) return def; const char_t* value = _attr->value; return value ? impl::get_value_float(value) : def; } PUGI_IMPL_FN bool xml_attribute::as_bool(bool def) const { if (!_attr) return def; const char_t* value = _attr->value; return value ? impl::get_value_bool(value) : def; } #ifdef PUGIXML_HAS_LONG_LONG PUGI_IMPL_FN long long xml_attribute::as_llong(long long def) const { if (!_attr) return def; const char_t* value = _attr->value; return value ? impl::get_value_llong(value) : def; } PUGI_IMPL_FN unsigned long long xml_attribute::as_ullong(unsigned long long def) const { if (!_attr) return def; const char_t* value = _attr->value; return value ? impl::get_value_ullong(value) : def; } #endif PUGI_IMPL_FN bool xml_attribute::empty() const { return !_attr; } PUGI_IMPL_FN const char_t* xml_attribute::name() const { if (!_attr) return PUGIXML_TEXT(""); const char_t* name = _attr->name; return name ? name : PUGIXML_TEXT(""); } PUGI_IMPL_FN const char_t* xml_attribute::value() const { if (!_attr) return PUGIXML_TEXT(""); const char_t* value = _attr->value; return value ? value : PUGIXML_TEXT(""); } PUGI_IMPL_FN size_t xml_attribute::hash_value() const { return static_cast(reinterpret_cast(_attr) / sizeof(xml_attribute_struct)); } PUGI_IMPL_FN xml_attribute_struct* xml_attribute::internal_object() const { return _attr; } PUGI_IMPL_FN xml_attribute& xml_attribute::operator=(const char_t* rhs) { set_value(rhs); return *this; } PUGI_IMPL_FN xml_attribute& xml_attribute::operator=(int rhs) { set_value(rhs); return *this; } PUGI_IMPL_FN xml_attribute& xml_attribute::operator=(unsigned int rhs) { set_value(rhs); return *this; } PUGI_IMPL_FN xml_attribute& xml_attribute::operator=(long rhs) { set_value(rhs); return *this; } PUGI_IMPL_FN xml_attribute& xml_attribute::operator=(unsigned long rhs) { set_value(rhs); return *this; } PUGI_IMPL_FN xml_attribute& xml_attribute::operator=(double rhs) { set_value(rhs); return *this; } PUGI_IMPL_FN xml_attribute& xml_attribute::operator=(float rhs) { set_value(rhs); return *this; } PUGI_IMPL_FN xml_attribute& xml_attribute::operator=(bool rhs) { set_value(rhs); return *this; } #ifdef PUGIXML_HAS_LONG_LONG PUGI_IMPL_FN xml_attribute& xml_attribute::operator=(long long rhs) { set_value(rhs); return *this; } PUGI_IMPL_FN xml_attribute& xml_attribute::operator=(unsigned long long rhs) { set_value(rhs); return *this; } #endif PUGI_IMPL_FN bool xml_attribute::set_name(const char_t* rhs) { if (!_attr) return false; return impl::strcpy_insitu(_attr->name, _attr->header, impl::xml_memory_page_name_allocated_mask, rhs, impl::strlength(rhs)); } PUGI_IMPL_FN bool xml_attribute::set_name(const char_t* rhs, size_t size) { if (!_attr) return false; return impl::strcpy_insitu(_attr->name, _attr->header, impl::xml_memory_page_name_allocated_mask, rhs, size); } PUGI_IMPL_FN bool xml_attribute::set_value(const char_t* rhs) { if (!_attr) return false; return impl::strcpy_insitu(_attr->value, _attr->header, impl::xml_memory_page_value_allocated_mask, rhs, impl::strlength(rhs)); } PUGI_IMPL_FN bool xml_attribute::set_value(const char_t* rhs, size_t size) { if (!_attr) return false; return impl::strcpy_insitu(_attr->value, _attr->header, impl::xml_memory_page_value_allocated_mask, rhs, size); } PUGI_IMPL_FN bool xml_attribute::set_value(int rhs) { if (!_attr) return false; return impl::set_value_integer(_attr->value, _attr->header, impl::xml_memory_page_value_allocated_mask, rhs, rhs < 0); } PUGI_IMPL_FN bool xml_attribute::set_value(unsigned int rhs) { if (!_attr) return false; return impl::set_value_integer(_attr->value, _attr->header, impl::xml_memory_page_value_allocated_mask, rhs, false); } PUGI_IMPL_FN bool xml_attribute::set_value(long rhs) { if (!_attr) return false; return impl::set_value_integer(_attr->value, _attr->header, impl::xml_memory_page_value_allocated_mask, rhs, rhs < 0); } PUGI_IMPL_FN bool xml_attribute::set_value(unsigned long rhs) { if (!_attr) return false; return impl::set_value_integer(_attr->value, _attr->header, impl::xml_memory_page_value_allocated_mask, rhs, false); } PUGI_IMPL_FN bool xml_attribute::set_value(double rhs) { if (!_attr) return false; return impl::set_value_convert(_attr->value, _attr->header, impl::xml_memory_page_value_allocated_mask, rhs, default_double_precision); } PUGI_IMPL_FN bool xml_attribute::set_value(double rhs, int precision) { if (!_attr) return false; return impl::set_value_convert(_attr->value, _attr->header, impl::xml_memory_page_value_allocated_mask, rhs, precision); } PUGI_IMPL_FN bool xml_attribute::set_value(float rhs) { if (!_attr) return false; return impl::set_value_convert(_attr->value, _attr->header, impl::xml_memory_page_value_allocated_mask, rhs, default_float_precision); } PUGI_IMPL_FN bool xml_attribute::set_value(float rhs, int precision) { if (!_attr) return false; return impl::set_value_convert(_attr->value, _attr->header, impl::xml_memory_page_value_allocated_mask, rhs, precision); } PUGI_IMPL_FN bool xml_attribute::set_value(bool rhs) { if (!_attr) return false; return impl::set_value_bool(_attr->value, _attr->header, impl::xml_memory_page_value_allocated_mask, rhs); } #ifdef PUGIXML_HAS_LONG_LONG PUGI_IMPL_FN bool xml_attribute::set_value(long long rhs) { if (!_attr) return false; return impl::set_value_integer(_attr->value, _attr->header, impl::xml_memory_page_value_allocated_mask, rhs, rhs < 0); } PUGI_IMPL_FN bool xml_attribute::set_value(unsigned long long rhs) { if (!_attr) return false; return impl::set_value_integer(_attr->value, _attr->header, impl::xml_memory_page_value_allocated_mask, rhs, false); } #endif #ifdef __BORLANDC__ PUGI_IMPL_FN bool operator&&(const xml_attribute& lhs, bool rhs) { return (bool)lhs && rhs; } PUGI_IMPL_FN bool operator||(const xml_attribute& lhs, bool rhs) { return (bool)lhs || rhs; } #endif PUGI_IMPL_FN xml_node::xml_node(): _root(0) { } PUGI_IMPL_FN xml_node::xml_node(xml_node_struct* p): _root(p) { } PUGI_IMPL_FN static void unspecified_bool_xml_node(xml_node***) { } PUGI_IMPL_FN xml_node::operator xml_node::unspecified_bool_type() const { return _root ? unspecified_bool_xml_node : 0; } PUGI_IMPL_FN bool xml_node::operator!() const { return !_root; } PUGI_IMPL_FN xml_node::iterator xml_node::begin() const { return iterator(_root ? _root->first_child + 0 : 0, _root); } PUGI_IMPL_FN xml_node::iterator xml_node::end() const { return iterator(0, _root); } PUGI_IMPL_FN xml_node::attribute_iterator xml_node::attributes_begin() const { return attribute_iterator(_root ? _root->first_attribute + 0 : 0, _root); } PUGI_IMPL_FN xml_node::attribute_iterator xml_node::attributes_end() const { return attribute_iterator(0, _root); } PUGI_IMPL_FN xml_object_range xml_node::children() const { return xml_object_range(begin(), end()); } PUGI_IMPL_FN xml_object_range xml_node::children(const char_t* name_) const { return xml_object_range(xml_named_node_iterator(child(name_)._root, _root, name_), xml_named_node_iterator(0, _root, name_)); } PUGI_IMPL_FN xml_object_range xml_node::attributes() const { return xml_object_range(attributes_begin(), attributes_end()); } PUGI_IMPL_FN bool xml_node::operator==(const xml_node& r) const { return (_root == r._root); } PUGI_IMPL_FN bool xml_node::operator!=(const xml_node& r) const { return (_root != r._root); } PUGI_IMPL_FN bool xml_node::operator<(const xml_node& r) const { return (_root < r._root); } PUGI_IMPL_FN bool xml_node::operator>(const xml_node& r) const { return (_root > r._root); } PUGI_IMPL_FN bool xml_node::operator<=(const xml_node& r) const { return (_root <= r._root); } PUGI_IMPL_FN bool xml_node::operator>=(const xml_node& r) const { return (_root >= r._root); } PUGI_IMPL_FN bool xml_node::empty() const { return !_root; } PUGI_IMPL_FN const char_t* xml_node::name() const { if (!_root) return PUGIXML_TEXT(""); const char_t* name = _root->name; return name ? name : PUGIXML_TEXT(""); } PUGI_IMPL_FN xml_node_type xml_node::type() const { return _root ? PUGI_IMPL_NODETYPE(_root) : node_null; } PUGI_IMPL_FN const char_t* xml_node::value() const { if (!_root) return PUGIXML_TEXT(""); const char_t* value = _root->value; return value ? value : PUGIXML_TEXT(""); } PUGI_IMPL_FN xml_node xml_node::child(const char_t* name_) const { if (!_root) return xml_node(); for (xml_node_struct* i = _root->first_child; i; i = i->next_sibling) { const char_t* iname = i->name; if (iname && impl::strequal(name_, iname)) return xml_node(i); } return xml_node(); } PUGI_IMPL_FN xml_attribute xml_node::attribute(const char_t* name_) const { if (!_root) return xml_attribute(); for (xml_attribute_struct* i = _root->first_attribute; i; i = i->next_attribute) { const char_t* iname = i->name; if (iname && impl::strequal(name_, iname)) return xml_attribute(i); } return xml_attribute(); } PUGI_IMPL_FN xml_node xml_node::next_sibling(const char_t* name_) const { if (!_root) return xml_node(); for (xml_node_struct* i = _root->next_sibling; i; i = i->next_sibling) { const char_t* iname = i->name; if (iname && impl::strequal(name_, iname)) return xml_node(i); } return xml_node(); } PUGI_IMPL_FN xml_node xml_node::next_sibling() const { return _root ? xml_node(_root->next_sibling) : xml_node(); } PUGI_IMPL_FN xml_node xml_node::previous_sibling(const char_t* name_) const { if (!_root) return xml_node(); for (xml_node_struct* i = _root->prev_sibling_c; i->next_sibling; i = i->prev_sibling_c) { const char_t* iname = i->name; if (iname && impl::strequal(name_, iname)) return xml_node(i); } return xml_node(); } PUGI_IMPL_FN xml_attribute xml_node::attribute(const char_t* name_, xml_attribute& hint_) const { xml_attribute_struct* hint = hint_._attr; // if hint is not an attribute of node, behavior is not defined assert(!hint || (_root && impl::is_attribute_of(hint, _root))); if (!_root) return xml_attribute(); // optimistically search from hint up until the end for (xml_attribute_struct* i = hint; i; i = i->next_attribute) { const char_t* iname = i->name; if (iname && impl::strequal(name_, iname)) { // update hint to maximize efficiency of searching for consecutive attributes hint_._attr = i->next_attribute; return xml_attribute(i); } } // wrap around and search from the first attribute until the hint // 'j' null pointer check is technically redundant, but it prevents a crash in case the assertion above fails for (xml_attribute_struct* j = _root->first_attribute; j && j != hint; j = j->next_attribute) { const char_t* jname = j->name; if (jname && impl::strequal(name_, jname)) { // update hint to maximize efficiency of searching for consecutive attributes hint_._attr = j->next_attribute; return xml_attribute(j); } } return xml_attribute(); } PUGI_IMPL_FN xml_node xml_node::previous_sibling() const { if (!_root) return xml_node(); xml_node_struct* prev = _root->prev_sibling_c; return prev->next_sibling ? xml_node(prev) : xml_node(); } PUGI_IMPL_FN xml_node xml_node::parent() const { return _root ? xml_node(_root->parent) : xml_node(); } PUGI_IMPL_FN xml_node xml_node::root() const { return _root ? xml_node(&impl::get_document(_root)) : xml_node(); } PUGI_IMPL_FN xml_text xml_node::text() const { return xml_text(_root); } PUGI_IMPL_FN const char_t* xml_node::child_value() const { if (!_root) return PUGIXML_TEXT(""); // element nodes can have value if parse_embed_pcdata was used if (PUGI_IMPL_NODETYPE(_root) == node_element && _root->value) return _root->value; for (xml_node_struct* i = _root->first_child; i; i = i->next_sibling) { const char_t* ivalue = i->value; if (impl::is_text_node(i) && ivalue) return ivalue; } return PUGIXML_TEXT(""); } PUGI_IMPL_FN const char_t* xml_node::child_value(const char_t* name_) const { return child(name_).child_value(); } PUGI_IMPL_FN xml_attribute xml_node::first_attribute() const { if (!_root) return xml_attribute(); return xml_attribute(_root->first_attribute); } PUGI_IMPL_FN xml_attribute xml_node::last_attribute() const { if (!_root) return xml_attribute(); xml_attribute_struct* first = _root->first_attribute; return first ? xml_attribute(first->prev_attribute_c) : xml_attribute(); } PUGI_IMPL_FN xml_node xml_node::first_child() const { if (!_root) return xml_node(); return xml_node(_root->first_child); } PUGI_IMPL_FN xml_node xml_node::last_child() const { if (!_root) return xml_node(); xml_node_struct* first = _root->first_child; return first ? xml_node(first->prev_sibling_c) : xml_node(); } PUGI_IMPL_FN bool xml_node::set_name(const char_t* rhs) { xml_node_type type_ = _root ? PUGI_IMPL_NODETYPE(_root) : node_null; if (type_ != node_element && type_ != node_pi && type_ != node_declaration) return false; return impl::strcpy_insitu(_root->name, _root->header, impl::xml_memory_page_name_allocated_mask, rhs, impl::strlength(rhs)); } PUGI_IMPL_FN bool xml_node::set_name(const char_t* rhs, size_t size) { xml_node_type type_ = _root ? PUGI_IMPL_NODETYPE(_root) : node_null; if (type_ != node_element && type_ != node_pi && type_ != node_declaration) return false; return impl::strcpy_insitu(_root->name, _root->header, impl::xml_memory_page_name_allocated_mask, rhs, size); } PUGI_IMPL_FN bool xml_node::set_value(const char_t* rhs) { xml_node_type type_ = _root ? PUGI_IMPL_NODETYPE(_root) : node_null; if (type_ != node_pcdata && type_ != node_cdata && type_ != node_comment && type_ != node_pi && type_ != node_doctype) return false; return impl::strcpy_insitu(_root->value, _root->header, impl::xml_memory_page_value_allocated_mask, rhs, impl::strlength(rhs)); } PUGI_IMPL_FN bool xml_node::set_value(const char_t* rhs, size_t size) { xml_node_type type_ = _root ? PUGI_IMPL_NODETYPE(_root) : node_null; if (type_ != node_pcdata && type_ != node_cdata && type_ != node_comment && type_ != node_pi && type_ != node_doctype) return false; return impl::strcpy_insitu(_root->value, _root->header, impl::xml_memory_page_value_allocated_mask, rhs, size); } PUGI_IMPL_FN xml_attribute xml_node::append_attribute(const char_t* name_) { if (!impl::allow_insert_attribute(type())) return xml_attribute(); impl::xml_allocator& alloc = impl::get_allocator(_root); if (!alloc.reserve()) return xml_attribute(); xml_attribute a(impl::allocate_attribute(alloc)); if (!a) return xml_attribute(); impl::append_attribute(a._attr, _root); a.set_name(name_); return a; } PUGI_IMPL_FN xml_attribute xml_node::prepend_attribute(const char_t* name_) { if (!impl::allow_insert_attribute(type())) return xml_attribute(); impl::xml_allocator& alloc = impl::get_allocator(_root); if (!alloc.reserve()) return xml_attribute(); xml_attribute a(impl::allocate_attribute(alloc)); if (!a) return xml_attribute(); impl::prepend_attribute(a._attr, _root); a.set_name(name_); return a; } PUGI_IMPL_FN xml_attribute xml_node::insert_attribute_after(const char_t* name_, const xml_attribute& attr) { if (!impl::allow_insert_attribute(type())) return xml_attribute(); if (!attr || !impl::is_attribute_of(attr._attr, _root)) return xml_attribute(); impl::xml_allocator& alloc = impl::get_allocator(_root); if (!alloc.reserve()) return xml_attribute(); xml_attribute a(impl::allocate_attribute(alloc)); if (!a) return xml_attribute(); impl::insert_attribute_after(a._attr, attr._attr, _root); a.set_name(name_); return a; } PUGI_IMPL_FN xml_attribute xml_node::insert_attribute_before(const char_t* name_, const xml_attribute& attr) { if (!impl::allow_insert_attribute(type())) return xml_attribute(); if (!attr || !impl::is_attribute_of(attr._attr, _root)) return xml_attribute(); impl::xml_allocator& alloc = impl::get_allocator(_root); if (!alloc.reserve()) return xml_attribute(); xml_attribute a(impl::allocate_attribute(alloc)); if (!a) return xml_attribute(); impl::insert_attribute_before(a._attr, attr._attr, _root); a.set_name(name_); return a; } PUGI_IMPL_FN xml_attribute xml_node::append_copy(const xml_attribute& proto) { if (!proto) return xml_attribute(); if (!impl::allow_insert_attribute(type())) return xml_attribute(); impl::xml_allocator& alloc = impl::get_allocator(_root); if (!alloc.reserve()) return xml_attribute(); xml_attribute a(impl::allocate_attribute(alloc)); if (!a) return xml_attribute(); impl::append_attribute(a._attr, _root); impl::node_copy_attribute(a._attr, proto._attr); return a; } PUGI_IMPL_FN xml_attribute xml_node::prepend_copy(const xml_attribute& proto) { if (!proto) return xml_attribute(); if (!impl::allow_insert_attribute(type())) return xml_attribute(); impl::xml_allocator& alloc = impl::get_allocator(_root); if (!alloc.reserve()) return xml_attribute(); xml_attribute a(impl::allocate_attribute(alloc)); if (!a) return xml_attribute(); impl::prepend_attribute(a._attr, _root); impl::node_copy_attribute(a._attr, proto._attr); return a; } PUGI_IMPL_FN xml_attribute xml_node::insert_copy_after(const xml_attribute& proto, const xml_attribute& attr) { if (!proto) return xml_attribute(); if (!impl::allow_insert_attribute(type())) return xml_attribute(); if (!attr || !impl::is_attribute_of(attr._attr, _root)) return xml_attribute(); impl::xml_allocator& alloc = impl::get_allocator(_root); if (!alloc.reserve()) return xml_attribute(); xml_attribute a(impl::allocate_attribute(alloc)); if (!a) return xml_attribute(); impl::insert_attribute_after(a._attr, attr._attr, _root); impl::node_copy_attribute(a._attr, proto._attr); return a; } PUGI_IMPL_FN xml_attribute xml_node::insert_copy_before(const xml_attribute& proto, const xml_attribute& attr) { if (!proto) return xml_attribute(); if (!impl::allow_insert_attribute(type())) return xml_attribute(); if (!attr || !impl::is_attribute_of(attr._attr, _root)) return xml_attribute(); impl::xml_allocator& alloc = impl::get_allocator(_root); if (!alloc.reserve()) return xml_attribute(); xml_attribute a(impl::allocate_attribute(alloc)); if (!a) return xml_attribute(); impl::insert_attribute_before(a._attr, attr._attr, _root); impl::node_copy_attribute(a._attr, proto._attr); return a; } PUGI_IMPL_FN xml_node xml_node::append_child(xml_node_type type_) { if (!impl::allow_insert_child(type(), type_)) return xml_node(); impl::xml_allocator& alloc = impl::get_allocator(_root); if (!alloc.reserve()) return xml_node(); xml_node n(impl::allocate_node(alloc, type_)); if (!n) return xml_node(); impl::append_node(n._root, _root); if (type_ == node_declaration) n.set_name(PUGIXML_TEXT("xml")); return n; } PUGI_IMPL_FN xml_node xml_node::prepend_child(xml_node_type type_) { if (!impl::allow_insert_child(type(), type_)) return xml_node(); impl::xml_allocator& alloc = impl::get_allocator(_root); if (!alloc.reserve()) return xml_node(); xml_node n(impl::allocate_node(alloc, type_)); if (!n) return xml_node(); impl::prepend_node(n._root, _root); if (type_ == node_declaration) n.set_name(PUGIXML_TEXT("xml")); return n; } PUGI_IMPL_FN xml_node xml_node::insert_child_before(xml_node_type type_, const xml_node& node) { if (!impl::allow_insert_child(type(), type_)) return xml_node(); if (!node._root || node._root->parent != _root) return xml_node(); impl::xml_allocator& alloc = impl::get_allocator(_root); if (!alloc.reserve()) return xml_node(); xml_node n(impl::allocate_node(alloc, type_)); if (!n) return xml_node(); impl::insert_node_before(n._root, node._root); if (type_ == node_declaration) n.set_name(PUGIXML_TEXT("xml")); return n; } PUGI_IMPL_FN xml_node xml_node::insert_child_after(xml_node_type type_, const xml_node& node) { if (!impl::allow_insert_child(type(), type_)) return xml_node(); if (!node._root || node._root->parent != _root) return xml_node(); impl::xml_allocator& alloc = impl::get_allocator(_root); if (!alloc.reserve()) return xml_node(); xml_node n(impl::allocate_node(alloc, type_)); if (!n) return xml_node(); impl::insert_node_after(n._root, node._root); if (type_ == node_declaration) n.set_name(PUGIXML_TEXT("xml")); return n; } PUGI_IMPL_FN xml_node xml_node::append_child(const char_t* name_) { xml_node result = append_child(node_element); result.set_name(name_); return result; } PUGI_IMPL_FN xml_node xml_node::prepend_child(const char_t* name_) { xml_node result = prepend_child(node_element); result.set_name(name_); return result; } PUGI_IMPL_FN xml_node xml_node::insert_child_after(const char_t* name_, const xml_node& node) { xml_node result = insert_child_after(node_element, node); result.set_name(name_); return result; } PUGI_IMPL_FN xml_node xml_node::insert_child_before(const char_t* name_, const xml_node& node) { xml_node result = insert_child_before(node_element, node); result.set_name(name_); return result; } PUGI_IMPL_FN xml_node xml_node::append_copy(const xml_node& proto) { xml_node_type type_ = proto.type(); if (!impl::allow_insert_child(type(), type_)) return xml_node(); impl::xml_allocator& alloc = impl::get_allocator(_root); if (!alloc.reserve()) return xml_node(); xml_node n(impl::allocate_node(alloc, type_)); if (!n) return xml_node(); impl::append_node(n._root, _root); impl::node_copy_tree(n._root, proto._root); return n; } PUGI_IMPL_FN xml_node xml_node::prepend_copy(const xml_node& proto) { xml_node_type type_ = proto.type(); if (!impl::allow_insert_child(type(), type_)) return xml_node(); impl::xml_allocator& alloc = impl::get_allocator(_root); if (!alloc.reserve()) return xml_node(); xml_node n(impl::allocate_node(alloc, type_)); if (!n) return xml_node(); impl::prepend_node(n._root, _root); impl::node_copy_tree(n._root, proto._root); return n; } PUGI_IMPL_FN xml_node xml_node::insert_copy_after(const xml_node& proto, const xml_node& node) { xml_node_type type_ = proto.type(); if (!impl::allow_insert_child(type(), type_)) return xml_node(); if (!node._root || node._root->parent != _root) return xml_node(); impl::xml_allocator& alloc = impl::get_allocator(_root); if (!alloc.reserve()) return xml_node(); xml_node n(impl::allocate_node(alloc, type_)); if (!n) return xml_node(); impl::insert_node_after(n._root, node._root); impl::node_copy_tree(n._root, proto._root); return n; } PUGI_IMPL_FN xml_node xml_node::insert_copy_before(const xml_node& proto, const xml_node& node) { xml_node_type type_ = proto.type(); if (!impl::allow_insert_child(type(), type_)) return xml_node(); if (!node._root || node._root->parent != _root) return xml_node(); impl::xml_allocator& alloc = impl::get_allocator(_root); if (!alloc.reserve()) return xml_node(); xml_node n(impl::allocate_node(alloc, type_)); if (!n) return xml_node(); impl::insert_node_before(n._root, node._root); impl::node_copy_tree(n._root, proto._root); return n; } PUGI_IMPL_FN xml_node xml_node::append_move(const xml_node& moved) { if (!impl::allow_move(*this, moved)) return xml_node(); impl::xml_allocator& alloc = impl::get_allocator(_root); if (!alloc.reserve()) return xml_node(); // disable document_buffer_order optimization since moving nodes around changes document order without changing buffer pointers impl::get_document(_root).header |= impl::xml_memory_page_contents_shared_mask; impl::remove_node(moved._root); impl::append_node(moved._root, _root); return moved; } PUGI_IMPL_FN xml_node xml_node::prepend_move(const xml_node& moved) { if (!impl::allow_move(*this, moved)) return xml_node(); impl::xml_allocator& alloc = impl::get_allocator(_root); if (!alloc.reserve()) return xml_node(); // disable document_buffer_order optimization since moving nodes around changes document order without changing buffer pointers impl::get_document(_root).header |= impl::xml_memory_page_contents_shared_mask; impl::remove_node(moved._root); impl::prepend_node(moved._root, _root); return moved; } PUGI_IMPL_FN xml_node xml_node::insert_move_after(const xml_node& moved, const xml_node& node) { if (!impl::allow_move(*this, moved)) return xml_node(); if (!node._root || node._root->parent != _root) return xml_node(); if (moved._root == node._root) return xml_node(); impl::xml_allocator& alloc = impl::get_allocator(_root); if (!alloc.reserve()) return xml_node(); // disable document_buffer_order optimization since moving nodes around changes document order without changing buffer pointers impl::get_document(_root).header |= impl::xml_memory_page_contents_shared_mask; impl::remove_node(moved._root); impl::insert_node_after(moved._root, node._root); return moved; } PUGI_IMPL_FN xml_node xml_node::insert_move_before(const xml_node& moved, const xml_node& node) { if (!impl::allow_move(*this, moved)) return xml_node(); if (!node._root || node._root->parent != _root) return xml_node(); if (moved._root == node._root) return xml_node(); impl::xml_allocator& alloc = impl::get_allocator(_root); if (!alloc.reserve()) return xml_node(); // disable document_buffer_order optimization since moving nodes around changes document order without changing buffer pointers impl::get_document(_root).header |= impl::xml_memory_page_contents_shared_mask; impl::remove_node(moved._root); impl::insert_node_before(moved._root, node._root); return moved; } PUGI_IMPL_FN bool xml_node::remove_attribute(const char_t* name_) { return remove_attribute(attribute(name_)); } PUGI_IMPL_FN bool xml_node::remove_attribute(const xml_attribute& a) { if (!_root || !a._attr) return false; if (!impl::is_attribute_of(a._attr, _root)) return false; impl::xml_allocator& alloc = impl::get_allocator(_root); if (!alloc.reserve()) return false; impl::remove_attribute(a._attr, _root); impl::destroy_attribute(a._attr, alloc); return true; } PUGI_IMPL_FN bool xml_node::remove_attributes() { if (!_root) return false; impl::xml_allocator& alloc = impl::get_allocator(_root); if (!alloc.reserve()) return false; for (xml_attribute_struct* attr = _root->first_attribute; attr; ) { xml_attribute_struct* next = attr->next_attribute; impl::destroy_attribute(attr, alloc); attr = next; } _root->first_attribute = 0; return true; } PUGI_IMPL_FN bool xml_node::remove_child(const char_t* name_) { return remove_child(child(name_)); } PUGI_IMPL_FN bool xml_node::remove_child(const xml_node& n) { if (!_root || !n._root || n._root->parent != _root) return false; impl::xml_allocator& alloc = impl::get_allocator(_root); if (!alloc.reserve()) return false; impl::remove_node(n._root); impl::destroy_node(n._root, alloc); return true; } PUGI_IMPL_FN bool xml_node::remove_children() { if (!_root) return false; impl::xml_allocator& alloc = impl::get_allocator(_root); if (!alloc.reserve()) return false; for (xml_node_struct* cur = _root->first_child; cur; ) { xml_node_struct* next = cur->next_sibling; impl::destroy_node(cur, alloc); cur = next; } _root->first_child = 0; return true; } PUGI_IMPL_FN xml_parse_result xml_node::append_buffer(const void* contents, size_t size, unsigned int options, xml_encoding encoding) { // append_buffer is only valid for elements/documents if (!impl::allow_insert_child(type(), node_element)) return impl::make_parse_result(status_append_invalid_root); // append buffer can not merge PCDATA into existing PCDATA nodes if ((options & parse_merge_pcdata) != 0 && last_child().type() == node_pcdata) return impl::make_parse_result(status_append_invalid_root); // get document node impl::xml_document_struct* doc = &impl::get_document(_root); // disable document_buffer_order optimization since in a document with multiple buffers comparing buffer pointers does not make sense doc->header |= impl::xml_memory_page_contents_shared_mask; // get extra buffer element (we'll store the document fragment buffer there so that we can deallocate it later) impl::xml_memory_page* page = 0; impl::xml_extra_buffer* extra = static_cast(doc->allocate_memory(sizeof(impl::xml_extra_buffer) + sizeof(void*), page)); (void)page; if (!extra) return impl::make_parse_result(status_out_of_memory); #ifdef PUGIXML_COMPACT // align the memory block to a pointer boundary; this is required for compact mode where memory allocations are only 4b aligned // note that this requires up to sizeof(void*)-1 additional memory, which the allocation above takes into account extra = reinterpret_cast((reinterpret_cast(extra) + (sizeof(void*) - 1)) & ~(sizeof(void*) - 1)); #endif // add extra buffer to the list extra->buffer = 0; extra->next = doc->extra_buffers; doc->extra_buffers = extra; // name of the root has to be NULL before parsing - otherwise closing node mismatches will not be detected at the top level impl::name_null_sentry sentry(_root); return impl::load_buffer_impl(doc, _root, const_cast(contents), size, options, encoding, false, false, &extra->buffer); } PUGI_IMPL_FN xml_node xml_node::find_child_by_attribute(const char_t* name_, const char_t* attr_name, const char_t* attr_value) const { if (!_root) return xml_node(); for (xml_node_struct* i = _root->first_child; i; i = i->next_sibling) { const char_t* iname = i->name; if (iname && impl::strequal(name_, iname)) { for (xml_attribute_struct* a = i->first_attribute; a; a = a->next_attribute) { const char_t* aname = a->name; if (aname && impl::strequal(attr_name, aname)) { const char_t* avalue = a->value; if (impl::strequal(attr_value, avalue ? avalue : PUGIXML_TEXT(""))) return xml_node(i); } } } } return xml_node(); } PUGI_IMPL_FN xml_node xml_node::find_child_by_attribute(const char_t* attr_name, const char_t* attr_value) const { if (!_root) return xml_node(); for (xml_node_struct* i = _root->first_child; i; i = i->next_sibling) for (xml_attribute_struct* a = i->first_attribute; a; a = a->next_attribute) { const char_t* aname = a->name; if (aname && impl::strequal(attr_name, aname)) { const char_t* avalue = a->value; if (impl::strequal(attr_value, avalue ? avalue : PUGIXML_TEXT(""))) return xml_node(i); } } return xml_node(); } #ifndef PUGIXML_NO_STL PUGI_IMPL_FN string_t xml_node::path(char_t delimiter) const { if (!_root) return string_t(); size_t offset = 0; for (xml_node_struct* i = _root; i; i = i->parent) { const char_t* iname = i->name; offset += (i != _root); offset += iname ? impl::strlength(iname) : 0; } string_t result; result.resize(offset); for (xml_node_struct* j = _root; j; j = j->parent) { if (j != _root) result[--offset] = delimiter; const char_t* jname = j->name; if (jname) { size_t length = impl::strlength(jname); offset -= length; memcpy(&result[offset], jname, length * sizeof(char_t)); } } assert(offset == 0); return result; } #endif PUGI_IMPL_FN xml_node xml_node::first_element_by_path(const char_t* path_, char_t delimiter) const { xml_node context = path_[0] == delimiter ? root() : *this; if (!context._root) return xml_node(); const char_t* path_segment = path_; while (*path_segment == delimiter) ++path_segment; const char_t* path_segment_end = path_segment; while (*path_segment_end && *path_segment_end != delimiter) ++path_segment_end; if (path_segment == path_segment_end) return context; const char_t* next_segment = path_segment_end; while (*next_segment == delimiter) ++next_segment; if (*path_segment == '.' && path_segment + 1 == path_segment_end) return context.first_element_by_path(next_segment, delimiter); else if (*path_segment == '.' && *(path_segment+1) == '.' && path_segment + 2 == path_segment_end) return context.parent().first_element_by_path(next_segment, delimiter); else { for (xml_node_struct* j = context._root->first_child; j; j = j->next_sibling) { const char_t* jname = j->name; if (jname && impl::strequalrange(jname, path_segment, static_cast(path_segment_end - path_segment))) { xml_node subsearch = xml_node(j).first_element_by_path(next_segment, delimiter); if (subsearch) return subsearch; } } return xml_node(); } } PUGI_IMPL_FN bool xml_node::traverse(xml_tree_walker& walker) { walker._depth = -1; xml_node arg_begin(_root); if (!walker.begin(arg_begin)) return false; xml_node_struct* cur = _root ? _root->first_child + 0 : 0; if (cur) { ++walker._depth; do { xml_node arg_for_each(cur); if (!walker.for_each(arg_for_each)) return false; if (cur->first_child) { ++walker._depth; cur = cur->first_child; } else if (cur->next_sibling) cur = cur->next_sibling; else { while (!cur->next_sibling && cur != _root && cur->parent) { --walker._depth; cur = cur->parent; } if (cur != _root) cur = cur->next_sibling; } } while (cur && cur != _root); } assert(walker._depth == -1); xml_node arg_end(_root); return walker.end(arg_end); } PUGI_IMPL_FN size_t xml_node::hash_value() const { return static_cast(reinterpret_cast(_root) / sizeof(xml_node_struct)); } PUGI_IMPL_FN xml_node_struct* xml_node::internal_object() const { return _root; } PUGI_IMPL_FN void xml_node::print(xml_writer& writer, const char_t* indent, unsigned int flags, xml_encoding encoding, unsigned int depth) const { if (!_root) return; impl::xml_buffered_writer buffered_writer(writer, encoding); impl::node_output(buffered_writer, _root, indent, flags, depth); buffered_writer.flush(); } #ifndef PUGIXML_NO_STL PUGI_IMPL_FN void xml_node::print(std::basic_ostream >& stream, const char_t* indent, unsigned int flags, xml_encoding encoding, unsigned int depth) const { xml_writer_stream writer(stream); print(writer, indent, flags, encoding, depth); } PUGI_IMPL_FN void xml_node::print(std::basic_ostream >& stream, const char_t* indent, unsigned int flags, unsigned int depth) const { xml_writer_stream writer(stream); print(writer, indent, flags, encoding_wchar, depth); } #endif PUGI_IMPL_FN ptrdiff_t xml_node::offset_debug() const { if (!_root) return -1; impl::xml_document_struct& doc = impl::get_document(_root); // we can determine the offset reliably only if there is exactly once parse buffer if (!doc.buffer || doc.extra_buffers) return -1; switch (type()) { case node_document: return 0; case node_element: case node_declaration: case node_pi: return _root->name && (_root->header & impl::xml_memory_page_name_allocated_or_shared_mask) == 0 ? _root->name - doc.buffer : -1; case node_pcdata: case node_cdata: case node_comment: case node_doctype: return _root->value && (_root->header & impl::xml_memory_page_value_allocated_or_shared_mask) == 0 ? _root->value - doc.buffer : -1; default: assert(false && "Invalid node type"); // unreachable return -1; } } #ifdef __BORLANDC__ PUGI_IMPL_FN bool operator&&(const xml_node& lhs, bool rhs) { return (bool)lhs && rhs; } PUGI_IMPL_FN bool operator||(const xml_node& lhs, bool rhs) { return (bool)lhs || rhs; } #endif PUGI_IMPL_FN xml_text::xml_text(xml_node_struct* root): _root(root) { } PUGI_IMPL_FN xml_node_struct* xml_text::_data() const { if (!_root || impl::is_text_node(_root)) return _root; // element nodes can have value if parse_embed_pcdata was used if (PUGI_IMPL_NODETYPE(_root) == node_element && _root->value) return _root; for (xml_node_struct* node = _root->first_child; node; node = node->next_sibling) if (impl::is_text_node(node)) return node; return 0; } PUGI_IMPL_FN xml_node_struct* xml_text::_data_new() { xml_node_struct* d = _data(); if (d) return d; return xml_node(_root).append_child(node_pcdata).internal_object(); } PUGI_IMPL_FN xml_text::xml_text(): _root(0) { } PUGI_IMPL_FN static void unspecified_bool_xml_text(xml_text***) { } PUGI_IMPL_FN xml_text::operator xml_text::unspecified_bool_type() const { return _data() ? unspecified_bool_xml_text : 0; } PUGI_IMPL_FN bool xml_text::operator!() const { return !_data(); } PUGI_IMPL_FN bool xml_text::empty() const { return _data() == 0; } PUGI_IMPL_FN const char_t* xml_text::get() const { xml_node_struct* d = _data(); if (!d) return PUGIXML_TEXT(""); const char_t* value = d->value; return value ? value : PUGIXML_TEXT(""); } PUGI_IMPL_FN const char_t* xml_text::as_string(const char_t* def) const { xml_node_struct* d = _data(); if (!d) return def; const char_t* value = d->value; return value ? value : def; } PUGI_IMPL_FN int xml_text::as_int(int def) const { xml_node_struct* d = _data(); if (!d) return def; const char_t* value = d->value; return value ? impl::get_value_int(value) : def; } PUGI_IMPL_FN unsigned int xml_text::as_uint(unsigned int def) const { xml_node_struct* d = _data(); if (!d) return def; const char_t* value = d->value; return value ? impl::get_value_uint(value) : def; } PUGI_IMPL_FN double xml_text::as_double(double def) const { xml_node_struct* d = _data(); if (!d) return def; const char_t* value = d->value; return value ? impl::get_value_double(value) : def; } PUGI_IMPL_FN float xml_text::as_float(float def) const { xml_node_struct* d = _data(); if (!d) return def; const char_t* value = d->value; return value ? impl::get_value_float(value) : def; } PUGI_IMPL_FN bool xml_text::as_bool(bool def) const { xml_node_struct* d = _data(); if (!d) return def; const char_t* value = d->value; return value ? impl::get_value_bool(value) : def; } #ifdef PUGIXML_HAS_LONG_LONG PUGI_IMPL_FN long long xml_text::as_llong(long long def) const { xml_node_struct* d = _data(); if (!d) return def; const char_t* value = d->value; return value ? impl::get_value_llong(value) : def; } PUGI_IMPL_FN unsigned long long xml_text::as_ullong(unsigned long long def) const { xml_node_struct* d = _data(); if (!d) return def; const char_t* value = d->value; return value ? impl::get_value_ullong(value) : def; } #endif PUGI_IMPL_FN bool xml_text::set(const char_t* rhs) { xml_node_struct* dn = _data_new(); return dn ? impl::strcpy_insitu(dn->value, dn->header, impl::xml_memory_page_value_allocated_mask, rhs, impl::strlength(rhs)) : false; } PUGI_IMPL_FN bool xml_text::set(const char_t* rhs, size_t size) { xml_node_struct* dn = _data_new(); return dn ? impl::strcpy_insitu(dn->value, dn->header, impl::xml_memory_page_value_allocated_mask, rhs, size) : false; } PUGI_IMPL_FN bool xml_text::set(int rhs) { xml_node_struct* dn = _data_new(); return dn ? impl::set_value_integer(dn->value, dn->header, impl::xml_memory_page_value_allocated_mask, rhs, rhs < 0) : false; } PUGI_IMPL_FN bool xml_text::set(unsigned int rhs) { xml_node_struct* dn = _data_new(); return dn ? impl::set_value_integer(dn->value, dn->header, impl::xml_memory_page_value_allocated_mask, rhs, false) : false; } PUGI_IMPL_FN bool xml_text::set(long rhs) { xml_node_struct* dn = _data_new(); return dn ? impl::set_value_integer(dn->value, dn->header, impl::xml_memory_page_value_allocated_mask, rhs, rhs < 0) : false; } PUGI_IMPL_FN bool xml_text::set(unsigned long rhs) { xml_node_struct* dn = _data_new(); return dn ? impl::set_value_integer(dn->value, dn->header, impl::xml_memory_page_value_allocated_mask, rhs, false) : false; } PUGI_IMPL_FN bool xml_text::set(float rhs) { xml_node_struct* dn = _data_new(); return dn ? impl::set_value_convert(dn->value, dn->header, impl::xml_memory_page_value_allocated_mask, rhs, default_float_precision) : false; } PUGI_IMPL_FN bool xml_text::set(float rhs, int precision) { xml_node_struct* dn = _data_new(); return dn ? impl::set_value_convert(dn->value, dn->header, impl::xml_memory_page_value_allocated_mask, rhs, precision) : false; } PUGI_IMPL_FN bool xml_text::set(double rhs) { xml_node_struct* dn = _data_new(); return dn ? impl::set_value_convert(dn->value, dn->header, impl::xml_memory_page_value_allocated_mask, rhs, default_double_precision) : false; } PUGI_IMPL_FN bool xml_text::set(double rhs, int precision) { xml_node_struct* dn = _data_new(); return dn ? impl::set_value_convert(dn->value, dn->header, impl::xml_memory_page_value_allocated_mask, rhs, precision) : false; } PUGI_IMPL_FN bool xml_text::set(bool rhs) { xml_node_struct* dn = _data_new(); return dn ? impl::set_value_bool(dn->value, dn->header, impl::xml_memory_page_value_allocated_mask, rhs) : false; } #ifdef PUGIXML_HAS_LONG_LONG PUGI_IMPL_FN bool xml_text::set(long long rhs) { xml_node_struct* dn = _data_new(); return dn ? impl::set_value_integer(dn->value, dn->header, impl::xml_memory_page_value_allocated_mask, rhs, rhs < 0) : false; } PUGI_IMPL_FN bool xml_text::set(unsigned long long rhs) { xml_node_struct* dn = _data_new(); return dn ? impl::set_value_integer(dn->value, dn->header, impl::xml_memory_page_value_allocated_mask, rhs, false) : false; } #endif PUGI_IMPL_FN xml_text& xml_text::operator=(const char_t* rhs) { set(rhs); return *this; } PUGI_IMPL_FN xml_text& xml_text::operator=(int rhs) { set(rhs); return *this; } PUGI_IMPL_FN xml_text& xml_text::operator=(unsigned int rhs) { set(rhs); return *this; } PUGI_IMPL_FN xml_text& xml_text::operator=(long rhs) { set(rhs); return *this; } PUGI_IMPL_FN xml_text& xml_text::operator=(unsigned long rhs) { set(rhs); return *this; } PUGI_IMPL_FN xml_text& xml_text::operator=(double rhs) { set(rhs); return *this; } PUGI_IMPL_FN xml_text& xml_text::operator=(float rhs) { set(rhs); return *this; } PUGI_IMPL_FN xml_text& xml_text::operator=(bool rhs) { set(rhs); return *this; } #ifdef PUGIXML_HAS_LONG_LONG PUGI_IMPL_FN xml_text& xml_text::operator=(long long rhs) { set(rhs); return *this; } PUGI_IMPL_FN xml_text& xml_text::operator=(unsigned long long rhs) { set(rhs); return *this; } #endif PUGI_IMPL_FN xml_node xml_text::data() const { return xml_node(_data()); } #ifdef __BORLANDC__ PUGI_IMPL_FN bool operator&&(const xml_text& lhs, bool rhs) { return (bool)lhs && rhs; } PUGI_IMPL_FN bool operator||(const xml_text& lhs, bool rhs) { return (bool)lhs || rhs; } #endif PUGI_IMPL_FN xml_node_iterator::xml_node_iterator() { } PUGI_IMPL_FN xml_node_iterator::xml_node_iterator(const xml_node& node): _wrap(node), _parent(node.parent()) { } PUGI_IMPL_FN xml_node_iterator::xml_node_iterator(xml_node_struct* ref, xml_node_struct* parent): _wrap(ref), _parent(parent) { } PUGI_IMPL_FN bool xml_node_iterator::operator==(const xml_node_iterator& rhs) const { return _wrap._root == rhs._wrap._root && _parent._root == rhs._parent._root; } PUGI_IMPL_FN bool xml_node_iterator::operator!=(const xml_node_iterator& rhs) const { return _wrap._root != rhs._wrap._root || _parent._root != rhs._parent._root; } PUGI_IMPL_FN xml_node& xml_node_iterator::operator*() const { assert(_wrap._root); return _wrap; } PUGI_IMPL_FN xml_node* xml_node_iterator::operator->() const { assert(_wrap._root); return const_cast(&_wrap); // BCC5 workaround } PUGI_IMPL_FN xml_node_iterator& xml_node_iterator::operator++() { assert(_wrap._root); _wrap._root = _wrap._root->next_sibling; return *this; } PUGI_IMPL_FN xml_node_iterator xml_node_iterator::operator++(int) { xml_node_iterator temp = *this; ++*this; return temp; } PUGI_IMPL_FN xml_node_iterator& xml_node_iterator::operator--() { _wrap = _wrap._root ? _wrap.previous_sibling() : _parent.last_child(); return *this; } PUGI_IMPL_FN xml_node_iterator xml_node_iterator::operator--(int) { xml_node_iterator temp = *this; --*this; return temp; } PUGI_IMPL_FN xml_attribute_iterator::xml_attribute_iterator() { } PUGI_IMPL_FN xml_attribute_iterator::xml_attribute_iterator(const xml_attribute& attr, const xml_node& parent): _wrap(attr), _parent(parent) { } PUGI_IMPL_FN xml_attribute_iterator::xml_attribute_iterator(xml_attribute_struct* ref, xml_node_struct* parent): _wrap(ref), _parent(parent) { } PUGI_IMPL_FN bool xml_attribute_iterator::operator==(const xml_attribute_iterator& rhs) const { return _wrap._attr == rhs._wrap._attr && _parent._root == rhs._parent._root; } PUGI_IMPL_FN bool xml_attribute_iterator::operator!=(const xml_attribute_iterator& rhs) const { return _wrap._attr != rhs._wrap._attr || _parent._root != rhs._parent._root; } PUGI_IMPL_FN xml_attribute& xml_attribute_iterator::operator*() const { assert(_wrap._attr); return _wrap; } PUGI_IMPL_FN xml_attribute* xml_attribute_iterator::operator->() const { assert(_wrap._attr); return const_cast(&_wrap); // BCC5 workaround } PUGI_IMPL_FN xml_attribute_iterator& xml_attribute_iterator::operator++() { assert(_wrap._attr); _wrap._attr = _wrap._attr->next_attribute; return *this; } PUGI_IMPL_FN xml_attribute_iterator xml_attribute_iterator::operator++(int) { xml_attribute_iterator temp = *this; ++*this; return temp; } PUGI_IMPL_FN xml_attribute_iterator& xml_attribute_iterator::operator--() { _wrap = _wrap._attr ? _wrap.previous_attribute() : _parent.last_attribute(); return *this; } PUGI_IMPL_FN xml_attribute_iterator xml_attribute_iterator::operator--(int) { xml_attribute_iterator temp = *this; --*this; return temp; } PUGI_IMPL_FN xml_named_node_iterator::xml_named_node_iterator(): _name(0) { } PUGI_IMPL_FN xml_named_node_iterator::xml_named_node_iterator(const xml_node& node, const char_t* name): _wrap(node), _parent(node.parent()), _name(name) { } PUGI_IMPL_FN xml_named_node_iterator::xml_named_node_iterator(xml_node_struct* ref, xml_node_struct* parent, const char_t* name): _wrap(ref), _parent(parent), _name(name) { } PUGI_IMPL_FN bool xml_named_node_iterator::operator==(const xml_named_node_iterator& rhs) const { return _wrap._root == rhs._wrap._root && _parent._root == rhs._parent._root; } PUGI_IMPL_FN bool xml_named_node_iterator::operator!=(const xml_named_node_iterator& rhs) const { return _wrap._root != rhs._wrap._root || _parent._root != rhs._parent._root; } PUGI_IMPL_FN xml_node& xml_named_node_iterator::operator*() const { assert(_wrap._root); return _wrap; } PUGI_IMPL_FN xml_node* xml_named_node_iterator::operator->() const { assert(_wrap._root); return const_cast(&_wrap); // BCC5 workaround } PUGI_IMPL_FN xml_named_node_iterator& xml_named_node_iterator::operator++() { assert(_wrap._root); _wrap = _wrap.next_sibling(_name); return *this; } PUGI_IMPL_FN xml_named_node_iterator xml_named_node_iterator::operator++(int) { xml_named_node_iterator temp = *this; ++*this; return temp; } PUGI_IMPL_FN xml_named_node_iterator& xml_named_node_iterator::operator--() { if (_wrap._root) _wrap = _wrap.previous_sibling(_name); else { _wrap = _parent.last_child(); if (!impl::strequal(_wrap.name(), _name)) _wrap = _wrap.previous_sibling(_name); } return *this; } PUGI_IMPL_FN xml_named_node_iterator xml_named_node_iterator::operator--(int) { xml_named_node_iterator temp = *this; --*this; return temp; } PUGI_IMPL_FN xml_parse_result::xml_parse_result(): status(status_internal_error), offset(0), encoding(encoding_auto) { } PUGI_IMPL_FN xml_parse_result::operator bool() const { return status == status_ok; } PUGI_IMPL_FN const char* xml_parse_result::description() const { switch (status) { case status_ok: return "No error"; case status_file_not_found: return "File was not found"; case status_io_error: return "Error reading from file/stream"; case status_out_of_memory: return "Could not allocate memory"; case status_internal_error: return "Internal error occurred"; case status_unrecognized_tag: return "Could not determine tag type"; case status_bad_pi: return "Error parsing document declaration/processing instruction"; case status_bad_comment: return "Error parsing comment"; case status_bad_cdata: return "Error parsing CDATA section"; case status_bad_doctype: return "Error parsing document type declaration"; case status_bad_pcdata: return "Error parsing PCDATA section"; case status_bad_start_element: return "Error parsing start element tag"; case status_bad_attribute: return "Error parsing element attribute"; case status_bad_end_element: return "Error parsing end element tag"; case status_end_element_mismatch: return "Start-end tags mismatch"; case status_append_invalid_root: return "Unable to append nodes: root is not an element or document"; case status_no_document_element: return "No document element found"; default: return "Unknown error"; } } PUGI_IMPL_FN xml_document::xml_document(): _buffer(0) { _create(); } PUGI_IMPL_FN xml_document::~xml_document() { _destroy(); } #ifdef PUGIXML_HAS_MOVE PUGI_IMPL_FN xml_document::xml_document(xml_document&& rhs) PUGIXML_NOEXCEPT_IF_NOT_COMPACT: _buffer(0) { _create(); _move(rhs); } PUGI_IMPL_FN xml_document& xml_document::operator=(xml_document&& rhs) PUGIXML_NOEXCEPT_IF_NOT_COMPACT { if (this == &rhs) return *this; _destroy(); _create(); _move(rhs); return *this; } #endif PUGI_IMPL_FN void xml_document::reset() { _destroy(); _create(); } PUGI_IMPL_FN void xml_document::reset(const xml_document& proto) { reset(); impl::node_copy_tree(_root, proto._root); } PUGI_IMPL_FN void xml_document::_create() { assert(!_root); #ifdef PUGIXML_COMPACT // space for page marker for the first page (uint32_t), rounded up to pointer size; assumes pointers are at least 32-bit const size_t page_offset = sizeof(void*); #else const size_t page_offset = 0; #endif // initialize sentinel page PUGI_IMPL_STATIC_ASSERT(sizeof(impl::xml_memory_page) + sizeof(impl::xml_document_struct) + page_offset <= sizeof(_memory)); // prepare page structure impl::xml_memory_page* page = impl::xml_memory_page::construct(_memory); assert(page); page->busy_size = impl::xml_memory_page_size; // setup first page marker #ifdef PUGIXML_COMPACT // round-trip through void* to avoid 'cast increases required alignment of target type' warning page->compact_page_marker = reinterpret_cast(static_cast(reinterpret_cast(page) + sizeof(impl::xml_memory_page))); *page->compact_page_marker = sizeof(impl::xml_memory_page); #endif // allocate new root _root = new (reinterpret_cast(page) + sizeof(impl::xml_memory_page) + page_offset) impl::xml_document_struct(page); _root->prev_sibling_c = _root; // setup sentinel page page->allocator = static_cast(_root); // setup hash table pointer in allocator #ifdef PUGIXML_COMPACT page->allocator->_hash = &static_cast(_root)->hash; #endif // verify the document allocation assert(reinterpret_cast(_root) + sizeof(impl::xml_document_struct) <= _memory + sizeof(_memory)); } PUGI_IMPL_FN void xml_document::_destroy() { assert(_root); // destroy static storage if (_buffer) { impl::xml_memory::deallocate(_buffer); _buffer = 0; } // destroy extra buffers (note: no need to destroy linked list nodes, they're allocated using document allocator) for (impl::xml_extra_buffer* extra = static_cast(_root)->extra_buffers; extra; extra = extra->next) { if (extra->buffer) impl::xml_memory::deallocate(extra->buffer); } // destroy dynamic storage, leave sentinel page (it's in static memory) impl::xml_memory_page* root_page = PUGI_IMPL_GETPAGE(_root); assert(root_page && !root_page->prev); assert(reinterpret_cast(root_page) >= _memory && reinterpret_cast(root_page) < _memory + sizeof(_memory)); for (impl::xml_memory_page* page = root_page->next; page; ) { impl::xml_memory_page* next = page->next; impl::xml_allocator::deallocate_page(page); page = next; } #ifdef PUGIXML_COMPACT // destroy hash table static_cast(_root)->hash.clear(); #endif _root = 0; } #ifdef PUGIXML_HAS_MOVE PUGI_IMPL_FN void xml_document::_move(xml_document& rhs) PUGIXML_NOEXCEPT_IF_NOT_COMPACT { impl::xml_document_struct* doc = static_cast(_root); impl::xml_document_struct* other = static_cast(rhs._root); // save first child pointer for later; this needs hash access xml_node_struct* other_first_child = other->first_child; #ifdef PUGIXML_COMPACT // reserve space for the hash table up front; this is the only operation that can fail // if it does, we have no choice but to throw (if we have exceptions) if (other_first_child) { size_t other_children = 0; for (xml_node_struct* node = other_first_child; node; node = node->next_sibling) other_children++; // in compact mode, each pointer assignment could result in a hash table request // during move, we have to relocate document first_child and parents of all children // normally there's just one child and its parent has a pointerless encoding but // we assume the worst here if (!other->_hash->reserve(other_children + 1)) { #ifdef PUGIXML_NO_EXCEPTIONS return; #else throw std::bad_alloc(); #endif } } #endif // move allocation state // note that other->_root may point to the embedded document page, in which case we should keep original (empty) state if (other->_root != PUGI_IMPL_GETPAGE(other)) { doc->_root = other->_root; doc->_busy_size = other->_busy_size; } // move buffer state doc->buffer = other->buffer; doc->extra_buffers = other->extra_buffers; _buffer = rhs._buffer; #ifdef PUGIXML_COMPACT // move compact hash; note that the hash table can have pointers to other but they will be "inactive", similarly to nodes removed with remove_child doc->hash = other->hash; doc->_hash = &doc->hash; // make sure we don't access other hash up until the end when we reinitialize other document other->_hash = 0; #endif // move page structure impl::xml_memory_page* doc_page = PUGI_IMPL_GETPAGE(doc); assert(doc_page && !doc_page->prev && !doc_page->next); impl::xml_memory_page* other_page = PUGI_IMPL_GETPAGE(other); assert(other_page && !other_page->prev); // relink pages since root page is embedded into xml_document if (impl::xml_memory_page* page = other_page->next) { assert(page->prev == other_page); page->prev = doc_page; doc_page->next = page; other_page->next = 0; } // make sure pages point to the correct document state for (impl::xml_memory_page* page = doc_page->next; page; page = page->next) { assert(page->allocator == other); page->allocator = doc; #ifdef PUGIXML_COMPACT // this automatically migrates most children between documents and prevents ->parent assignment from allocating if (page->compact_shared_parent == other) page->compact_shared_parent = doc; #endif } // move tree structure assert(!doc->first_child); doc->first_child = other_first_child; for (xml_node_struct* node = other_first_child; node; node = node->next_sibling) { #ifdef PUGIXML_COMPACT // most children will have migrated when we reassigned compact_shared_parent assert(node->parent == other || node->parent == doc); node->parent = doc; #else assert(node->parent == other); node->parent = doc; #endif } // reset other document new (other) impl::xml_document_struct(PUGI_IMPL_GETPAGE(other)); rhs._buffer = 0; } #endif #ifndef PUGIXML_NO_STL PUGI_IMPL_FN xml_parse_result xml_document::load(std::basic_istream >& stream, unsigned int options, xml_encoding encoding) { reset(); return impl::load_stream_impl(static_cast(_root), stream, options, encoding, &_buffer); } PUGI_IMPL_FN xml_parse_result xml_document::load(std::basic_istream >& stream, unsigned int options) { reset(); return impl::load_stream_impl(static_cast(_root), stream, options, encoding_wchar, &_buffer); } #endif PUGI_IMPL_FN xml_parse_result xml_document::load_string(const char_t* contents, unsigned int options) { // Force native encoding (skip autodetection) #ifdef PUGIXML_WCHAR_MODE xml_encoding encoding = encoding_wchar; #else xml_encoding encoding = encoding_utf8; #endif return load_buffer(contents, impl::strlength(contents) * sizeof(char_t), options, encoding); } PUGI_IMPL_FN xml_parse_result xml_document::load(const char_t* contents, unsigned int options) { return load_string(contents, options); } PUGI_IMPL_FN xml_parse_result xml_document::load_file(const char* path_, unsigned int options, xml_encoding encoding) { reset(); using impl::auto_deleter; // MSVC7 workaround auto_deleter file(impl::open_file(path_, "rb"), impl::close_file); return impl::load_file_impl(static_cast(_root), file.data, options, encoding, &_buffer); } PUGI_IMPL_FN xml_parse_result xml_document::load_file(const wchar_t* path_, unsigned int options, xml_encoding encoding) { reset(); using impl::auto_deleter; // MSVC7 workaround auto_deleter file(impl::open_file_wide(path_, L"rb"), impl::close_file); return impl::load_file_impl(static_cast(_root), file.data, options, encoding, &_buffer); } PUGI_IMPL_FN xml_parse_result xml_document::load_buffer(const void* contents, size_t size, unsigned int options, xml_encoding encoding) { reset(); return impl::load_buffer_impl(static_cast(_root), _root, const_cast(contents), size, options, encoding, false, false, &_buffer); } PUGI_IMPL_FN xml_parse_result xml_document::load_buffer_inplace(void* contents, size_t size, unsigned int options, xml_encoding encoding) { reset(); return impl::load_buffer_impl(static_cast(_root), _root, contents, size, options, encoding, true, false, &_buffer); } PUGI_IMPL_FN xml_parse_result xml_document::load_buffer_inplace_own(void* contents, size_t size, unsigned int options, xml_encoding encoding) { reset(); return impl::load_buffer_impl(static_cast(_root), _root, contents, size, options, encoding, true, true, &_buffer); } PUGI_IMPL_FN void xml_document::save(xml_writer& writer, const char_t* indent, unsigned int flags, xml_encoding encoding) const { impl::xml_buffered_writer buffered_writer(writer, encoding); if ((flags & format_write_bom) && encoding != encoding_latin1) { // BOM always represents the codepoint U+FEFF, so just write it in native encoding #ifdef PUGIXML_WCHAR_MODE unsigned int bom = 0xfeff; buffered_writer.write(static_cast(bom)); #else buffered_writer.write('\xef', '\xbb', '\xbf'); #endif } if (!(flags & format_no_declaration) && !impl::has_declaration(_root)) { buffered_writer.write_string(PUGIXML_TEXT("'); if (!(flags & format_raw)) buffered_writer.write('\n'); } impl::node_output(buffered_writer, _root, indent, flags, 0); buffered_writer.flush(); } #ifndef PUGIXML_NO_STL PUGI_IMPL_FN void xml_document::save(std::basic_ostream >& stream, const char_t* indent, unsigned int flags, xml_encoding encoding) const { xml_writer_stream writer(stream); save(writer, indent, flags, encoding); } PUGI_IMPL_FN void xml_document::save(std::basic_ostream >& stream, const char_t* indent, unsigned int flags) const { xml_writer_stream writer(stream); save(writer, indent, flags, encoding_wchar); } #endif PUGI_IMPL_FN bool xml_document::save_file(const char* path_, const char_t* indent, unsigned int flags, xml_encoding encoding) const { using impl::auto_deleter; // MSVC7 workaround auto_deleter file(impl::open_file(path_, (flags & format_save_file_text) ? "w" : "wb"), impl::close_file); return impl::save_file_impl(*this, file.data, indent, flags, encoding) && fclose(file.release()) == 0; } PUGI_IMPL_FN bool xml_document::save_file(const wchar_t* path_, const char_t* indent, unsigned int flags, xml_encoding encoding) const { using impl::auto_deleter; // MSVC7 workaround auto_deleter file(impl::open_file_wide(path_, (flags & format_save_file_text) ? L"w" : L"wb"), impl::close_file); return impl::save_file_impl(*this, file.data, indent, flags, encoding) && fclose(file.release()) == 0; } PUGI_IMPL_FN xml_node xml_document::document_element() const { assert(_root); for (xml_node_struct* i = _root->first_child; i; i = i->next_sibling) if (PUGI_IMPL_NODETYPE(i) == node_element) return xml_node(i); return xml_node(); } #ifndef PUGIXML_NO_STL PUGI_IMPL_FN std::string PUGIXML_FUNCTION as_utf8(const wchar_t* str) { assert(str); return impl::as_utf8_impl(str, impl::strlength_wide(str)); } PUGI_IMPL_FN std::string PUGIXML_FUNCTION as_utf8(const std::basic_string& str) { return impl::as_utf8_impl(str.c_str(), str.size()); } PUGI_IMPL_FN std::basic_string PUGIXML_FUNCTION as_wide(const char* str) { assert(str); return impl::as_wide_impl(str, strlen(str)); } PUGI_IMPL_FN std::basic_string PUGIXML_FUNCTION as_wide(const std::string& str) { return impl::as_wide_impl(str.c_str(), str.size()); } #endif PUGI_IMPL_FN void PUGIXML_FUNCTION set_memory_management_functions(allocation_function allocate, deallocation_function deallocate) { impl::xml_memory::allocate = allocate; impl::xml_memory::deallocate = deallocate; } PUGI_IMPL_FN allocation_function PUGIXML_FUNCTION get_memory_allocation_function() { return impl::xml_memory::allocate; } PUGI_IMPL_FN deallocation_function PUGIXML_FUNCTION get_memory_deallocation_function() { return impl::xml_memory::deallocate; } } #if !defined(PUGIXML_NO_STL) && (defined(_MSC_VER) || defined(__ICC)) namespace std { // Workarounds for (non-standard) iterator category detection for older versions (MSVC7/IC8 and earlier) PUGI_IMPL_FN std::bidirectional_iterator_tag _Iter_cat(const pugi::xml_node_iterator&) { return std::bidirectional_iterator_tag(); } PUGI_IMPL_FN std::bidirectional_iterator_tag _Iter_cat(const pugi::xml_attribute_iterator&) { return std::bidirectional_iterator_tag(); } PUGI_IMPL_FN std::bidirectional_iterator_tag _Iter_cat(const pugi::xml_named_node_iterator&) { return std::bidirectional_iterator_tag(); } } #endif #if !defined(PUGIXML_NO_STL) && defined(__SUNPRO_CC) namespace std { // Workarounds for (non-standard) iterator category detection PUGI_IMPL_FN std::bidirectional_iterator_tag __iterator_category(const pugi::xml_node_iterator&) { return std::bidirectional_iterator_tag(); } PUGI_IMPL_FN std::bidirectional_iterator_tag __iterator_category(const pugi::xml_attribute_iterator&) { return std::bidirectional_iterator_tag(); } PUGI_IMPL_FN std::bidirectional_iterator_tag __iterator_category(const pugi::xml_named_node_iterator&) { return std::bidirectional_iterator_tag(); } } #endif #ifndef PUGIXML_NO_XPATH // STL replacements PUGI_IMPL_NS_BEGIN struct equal_to { template bool operator()(const T& lhs, const T& rhs) const { return lhs == rhs; } }; struct not_equal_to { template bool operator()(const T& lhs, const T& rhs) const { return lhs != rhs; } }; struct less { template bool operator()(const T& lhs, const T& rhs) const { return lhs < rhs; } }; struct less_equal { template bool operator()(const T& lhs, const T& rhs) const { return lhs <= rhs; } }; template inline void swap(T& lhs, T& rhs) { T temp = lhs; lhs = rhs; rhs = temp; } template PUGI_IMPL_FN I min_element(I begin, I end, const Pred& pred) { I result = begin; for (I it = begin + 1; it != end; ++it) if (pred(*it, *result)) result = it; return result; } template PUGI_IMPL_FN void reverse(I begin, I end) { while (end - begin > 1) swap(*begin++, *--end); } template PUGI_IMPL_FN I unique(I begin, I end) { // fast skip head while (end - begin > 1 && *begin != *(begin + 1)) begin++; if (begin == end) return begin; // last written element I write = begin++; // merge unique elements while (begin != end) { if (*begin != *write) *++write = *begin++; else begin++; } // past-the-end (write points to live element) return write + 1; } template PUGI_IMPL_FN void insertion_sort(T* begin, T* end, const Pred& pred) { if (begin == end) return; for (T* it = begin + 1; it != end; ++it) { T val = *it; T* hole = it; // move hole backwards while (hole > begin && pred(val, *(hole - 1))) { *hole = *(hole - 1); hole--; } // fill hole with element *hole = val; } } template inline I median3(I first, I middle, I last, const Pred& pred) { if (pred(*middle, *first)) swap(middle, first); if (pred(*last, *middle)) swap(last, middle); if (pred(*middle, *first)) swap(middle, first); return middle; } template PUGI_IMPL_FN void partition3(T* begin, T* end, T pivot, const Pred& pred, T** out_eqbeg, T** out_eqend) { // invariant: array is split into 4 groups: = < ? > (each variable denotes the boundary between the groups) T* eq = begin; T* lt = begin; T* gt = end; while (lt < gt) { if (pred(*lt, pivot)) lt++; else if (*lt == pivot) swap(*eq++, *lt++); else swap(*lt, *--gt); } // we now have just 4 groups: = < >; move equal elements to the middle T* eqbeg = gt; for (T* it = begin; it != eq; ++it) swap(*it, *--eqbeg); *out_eqbeg = eqbeg; *out_eqend = gt; } template PUGI_IMPL_FN void sort(I begin, I end, const Pred& pred) { // sort large chunks while (end - begin > 16) { // find median element I middle = begin + (end - begin) / 2; I median = median3(begin, middle, end - 1, pred); // partition in three chunks (< = >) I eqbeg, eqend; partition3(begin, end, *median, pred, &eqbeg, &eqend); // loop on larger half if (eqbeg - begin > end - eqend) { sort(eqend, end, pred); end = eqbeg; } else { sort(begin, eqbeg, pred); begin = eqend; } } // insertion sort small chunk insertion_sort(begin, end, pred); } PUGI_IMPL_FN bool hash_insert(const void** table, size_t size, const void* key) { assert(key); unsigned int h = static_cast(reinterpret_cast(key)); // MurmurHash3 32-bit finalizer h ^= h >> 16; h *= 0x85ebca6bu; h ^= h >> 13; h *= 0xc2b2ae35u; h ^= h >> 16; size_t hashmod = size - 1; size_t bucket = h & hashmod; for (size_t probe = 0; probe <= hashmod; ++probe) { if (table[bucket] == 0) { table[bucket] = key; return true; } if (table[bucket] == key) return false; // hash collision, quadratic probing bucket = (bucket + probe + 1) & hashmod; } assert(false && "Hash table is full"); // unreachable return false; } PUGI_IMPL_NS_END // Allocator used for AST and evaluation stacks PUGI_IMPL_NS_BEGIN static const size_t xpath_memory_page_size = #ifdef PUGIXML_MEMORY_XPATH_PAGE_SIZE PUGIXML_MEMORY_XPATH_PAGE_SIZE #else 4096 #endif ; static const uintptr_t xpath_memory_block_alignment = sizeof(double) > sizeof(void*) ? sizeof(double) : sizeof(void*); struct xpath_memory_block { xpath_memory_block* next; size_t capacity; union { char data[xpath_memory_page_size]; double alignment; }; }; struct xpath_allocator { xpath_memory_block* _root; size_t _root_size; bool* _error; xpath_allocator(xpath_memory_block* root, bool* error = 0): _root(root), _root_size(0), _error(error) { } void* allocate(size_t size) { // round size up to block alignment boundary size = (size + xpath_memory_block_alignment - 1) & ~(xpath_memory_block_alignment - 1); if (_root_size + size <= _root->capacity) { void* buf = &_root->data[0] + _root_size; _root_size += size; return buf; } else { // make sure we have at least 1/4th of the page free after allocation to satisfy subsequent allocation requests size_t block_capacity_base = sizeof(_root->data); size_t block_capacity_req = size + block_capacity_base / 4; size_t block_capacity = (block_capacity_base > block_capacity_req) ? block_capacity_base : block_capacity_req; size_t block_size = block_capacity + offsetof(xpath_memory_block, data); xpath_memory_block* block = static_cast(xml_memory::allocate(block_size)); if (!block) { if (_error) *_error = true; return 0; } block->next = _root; block->capacity = block_capacity; _root = block; _root_size = size; return block->data; } } void* reallocate(void* ptr, size_t old_size, size_t new_size) { // round size up to block alignment boundary old_size = (old_size + xpath_memory_block_alignment - 1) & ~(xpath_memory_block_alignment - 1); new_size = (new_size + xpath_memory_block_alignment - 1) & ~(xpath_memory_block_alignment - 1); // we can only reallocate the last object assert(ptr == 0 || static_cast(ptr) + old_size == &_root->data[0] + _root_size); // try to reallocate the object inplace if (ptr && _root_size - old_size + new_size <= _root->capacity) { _root_size = _root_size - old_size + new_size; return ptr; } // allocate a new block void* result = allocate(new_size); if (!result) return 0; // we have a new block if (ptr) { // copy old data (we only support growing) assert(new_size >= old_size); memcpy(result, ptr, old_size); // free the previous page if it had no other objects assert(_root->data == result); assert(_root->next); if (_root->next->data == ptr) { // deallocate the whole page, unless it was the first one xpath_memory_block* next = _root->next->next; if (next) { xml_memory::deallocate(_root->next); _root->next = next; } } } return result; } void revert(const xpath_allocator& state) { // free all new pages xpath_memory_block* cur = _root; while (cur != state._root) { xpath_memory_block* next = cur->next; xml_memory::deallocate(cur); cur = next; } // restore state _root = state._root; _root_size = state._root_size; } void release() { xpath_memory_block* cur = _root; assert(cur); while (cur->next) { xpath_memory_block* next = cur->next; xml_memory::deallocate(cur); cur = next; } } }; struct xpath_allocator_capture { xpath_allocator_capture(xpath_allocator* alloc): _target(alloc), _state(*alloc) { } ~xpath_allocator_capture() { _target->revert(_state); } xpath_allocator* _target; xpath_allocator _state; }; struct xpath_stack { xpath_allocator* result; xpath_allocator* temp; }; struct xpath_stack_data { xpath_memory_block blocks[2]; xpath_allocator result; xpath_allocator temp; xpath_stack stack; bool oom; xpath_stack_data(): result(blocks + 0, &oom), temp(blocks + 1, &oom), oom(false) { blocks[0].next = blocks[1].next = 0; blocks[0].capacity = blocks[1].capacity = sizeof(blocks[0].data); stack.result = &result; stack.temp = &temp; } ~xpath_stack_data() { result.release(); temp.release(); } }; PUGI_IMPL_NS_END // String class PUGI_IMPL_NS_BEGIN class xpath_string { const char_t* _buffer; bool _uses_heap; size_t _length_heap; static char_t* duplicate_string(const char_t* string, size_t length, xpath_allocator* alloc) { char_t* result = static_cast(alloc->allocate((length + 1) * sizeof(char_t))); if (!result) return 0; memcpy(result, string, length * sizeof(char_t)); result[length] = 0; return result; } xpath_string(const char_t* buffer, bool uses_heap_, size_t length_heap): _buffer(buffer), _uses_heap(uses_heap_), _length_heap(length_heap) { } public: static xpath_string from_const(const char_t* str) { return xpath_string(str, false, 0); } static xpath_string from_heap_preallocated(const char_t* begin, const char_t* end) { assert(begin <= end && *end == 0); return xpath_string(begin, true, static_cast(end - begin)); } static xpath_string from_heap(const char_t* begin, const char_t* end, xpath_allocator* alloc) { assert(begin <= end); if (begin == end) return xpath_string(); size_t length = static_cast(end - begin); const char_t* data = duplicate_string(begin, length, alloc); return data ? xpath_string(data, true, length) : xpath_string(); } xpath_string(): _buffer(PUGIXML_TEXT("")), _uses_heap(false), _length_heap(0) { } void append(const xpath_string& o, xpath_allocator* alloc) { // skip empty sources if (!*o._buffer) return; // fast append for constant empty target and constant source if (!*_buffer && !_uses_heap && !o._uses_heap) { _buffer = o._buffer; } else { // need to make heap copy size_t target_length = length(); size_t source_length = o.length(); size_t result_length = target_length + source_length; // allocate new buffer char_t* result = static_cast(alloc->reallocate(_uses_heap ? const_cast(_buffer) : 0, (target_length + 1) * sizeof(char_t), (result_length + 1) * sizeof(char_t))); if (!result) return; // append first string to the new buffer in case there was no reallocation if (!_uses_heap) memcpy(result, _buffer, target_length * sizeof(char_t)); // append second string to the new buffer memcpy(result + target_length, o._buffer, source_length * sizeof(char_t)); result[result_length] = 0; // finalize _buffer = result; _uses_heap = true; _length_heap = result_length; } } const char_t* c_str() const { return _buffer; } size_t length() const { return _uses_heap ? _length_heap : strlength(_buffer); } char_t* data(xpath_allocator* alloc) { // make private heap copy if (!_uses_heap) { size_t length_ = strlength(_buffer); const char_t* data_ = duplicate_string(_buffer, length_, alloc); if (!data_) return 0; _buffer = data_; _uses_heap = true; _length_heap = length_; } return const_cast(_buffer); } bool empty() const { return *_buffer == 0; } bool operator==(const xpath_string& o) const { return strequal(_buffer, o._buffer); } bool operator!=(const xpath_string& o) const { return !strequal(_buffer, o._buffer); } bool uses_heap() const { return _uses_heap; } }; PUGI_IMPL_NS_END PUGI_IMPL_NS_BEGIN PUGI_IMPL_FN bool starts_with(const char_t* string, const char_t* pattern) { while (*pattern && *string == *pattern) { string++; pattern++; } return *pattern == 0; } PUGI_IMPL_FN const char_t* find_char(const char_t* s, char_t c) { #ifdef PUGIXML_WCHAR_MODE return wcschr(s, c); #else return strchr(s, c); #endif } PUGI_IMPL_FN const char_t* find_substring(const char_t* s, const char_t* p) { #ifdef PUGIXML_WCHAR_MODE // MSVC6 wcsstr bug workaround (if s is empty it always returns 0) return (*p == 0) ? s : wcsstr(s, p); #else return strstr(s, p); #endif } // Converts symbol to lower case, if it is an ASCII one PUGI_IMPL_FN char_t tolower_ascii(char_t ch) { return static_cast(ch - 'A') < 26 ? static_cast(ch | ' ') : ch; } PUGI_IMPL_FN xpath_string string_value(const xpath_node& na, xpath_allocator* alloc) { if (na.attribute()) return xpath_string::from_const(na.attribute().value()); else { xml_node n = na.node(); switch (n.type()) { case node_pcdata: case node_cdata: case node_comment: case node_pi: return xpath_string::from_const(n.value()); case node_document: case node_element: { xpath_string result; // element nodes can have value if parse_embed_pcdata was used if (n.value()[0]) result.append(xpath_string::from_const(n.value()), alloc); xml_node cur = n.first_child(); while (cur && cur != n) { if (cur.type() == node_pcdata || cur.type() == node_cdata) result.append(xpath_string::from_const(cur.value()), alloc); if (cur.first_child()) cur = cur.first_child(); else if (cur.next_sibling()) cur = cur.next_sibling(); else { while (!cur.next_sibling() && cur != n) cur = cur.parent(); if (cur != n) cur = cur.next_sibling(); } } return result; } default: return xpath_string(); } } } PUGI_IMPL_FN bool node_is_before_sibling(xml_node_struct* ln, xml_node_struct* rn) { assert(ln->parent == rn->parent); // there is no common ancestor (the shared parent is null), nodes are from different documents if (!ln->parent) return ln < rn; // determine sibling order xml_node_struct* ls = ln; xml_node_struct* rs = rn; while (ls && rs) { if (ls == rn) return true; if (rs == ln) return false; ls = ls->next_sibling; rs = rs->next_sibling; } // if rn sibling chain ended ln must be before rn return !rs; } PUGI_IMPL_FN bool node_is_before(xml_node_struct* ln, xml_node_struct* rn) { // find common ancestor at the same depth, if any xml_node_struct* lp = ln; xml_node_struct* rp = rn; while (lp && rp && lp->parent != rp->parent) { lp = lp->parent; rp = rp->parent; } // parents are the same! if (lp && rp) return node_is_before_sibling(lp, rp); // nodes are at different depths, need to normalize heights bool left_higher = !lp; while (lp) { lp = lp->parent; ln = ln->parent; } while (rp) { rp = rp->parent; rn = rn->parent; } // one node is the ancestor of the other if (ln == rn) return left_higher; // find common ancestor... again while (ln->parent != rn->parent) { ln = ln->parent; rn = rn->parent; } return node_is_before_sibling(ln, rn); } PUGI_IMPL_FN bool node_is_ancestor(xml_node_struct* parent, xml_node_struct* node) { while (node && node != parent) node = node->parent; return parent && node == parent; } PUGI_IMPL_FN const void* document_buffer_order(const xpath_node& xnode) { xml_node_struct* node = xnode.node().internal_object(); if (node) { if ((get_document(node).header & xml_memory_page_contents_shared_mask) == 0) { if (node->name && (node->header & impl::xml_memory_page_name_allocated_or_shared_mask) == 0) return node->name; if (node->value && (node->header & impl::xml_memory_page_value_allocated_or_shared_mask) == 0) return node->value; } return 0; } xml_attribute_struct* attr = xnode.attribute().internal_object(); if (attr) { if ((get_document(attr).header & xml_memory_page_contents_shared_mask) == 0) { if ((attr->header & impl::xml_memory_page_name_allocated_or_shared_mask) == 0) return attr->name; if ((attr->header & impl::xml_memory_page_value_allocated_or_shared_mask) == 0) return attr->value; } return 0; } return 0; } struct document_order_comparator { bool operator()(const xpath_node& lhs, const xpath_node& rhs) const { // optimized document order based check const void* lo = document_buffer_order(lhs); const void* ro = document_buffer_order(rhs); if (lo && ro) return lo < ro; // slow comparison xml_node ln = lhs.node(), rn = rhs.node(); // compare attributes if (lhs.attribute() && rhs.attribute()) { // shared parent if (lhs.parent() == rhs.parent()) { // determine sibling order for (xml_attribute a = lhs.attribute(); a; a = a.next_attribute()) if (a == rhs.attribute()) return true; return false; } // compare attribute parents ln = lhs.parent(); rn = rhs.parent(); } else if (lhs.attribute()) { // attributes go after the parent element if (lhs.parent() == rhs.node()) return false; ln = lhs.parent(); } else if (rhs.attribute()) { // attributes go after the parent element if (rhs.parent() == lhs.node()) return true; rn = rhs.parent(); } if (ln == rn) return false; if (!ln || !rn) return ln < rn; return node_is_before(ln.internal_object(), rn.internal_object()); } }; PUGI_IMPL_FN double gen_nan() { #if defined(__STDC_IEC_559__) || ((FLT_RADIX - 0 == 2) && (FLT_MAX_EXP - 0 == 128) && (FLT_MANT_DIG - 0 == 24)) PUGI_IMPL_STATIC_ASSERT(sizeof(float) == sizeof(uint32_t)); typedef uint32_t UI; // BCC5 workaround union { float f; UI i; } u; u.i = 0x7fc00000; return double(u.f); #else // fallback const volatile double zero = 0.0; return zero / zero; #endif } PUGI_IMPL_FN bool is_nan(double value) { #if defined(PUGI_IMPL_MSVC_CRT_VERSION) || defined(__BORLANDC__) return !!_isnan(value); #elif defined(fpclassify) && defined(FP_NAN) return fpclassify(value) == FP_NAN; #else // fallback const volatile double v = value; return v != v; #endif } PUGI_IMPL_FN const char_t* convert_number_to_string_special(double value) { #if defined(PUGI_IMPL_MSVC_CRT_VERSION) || defined(__BORLANDC__) if (_finite(value)) return (value == 0) ? PUGIXML_TEXT("0") : 0; if (_isnan(value)) return PUGIXML_TEXT("NaN"); return value > 0 ? PUGIXML_TEXT("Infinity") : PUGIXML_TEXT("-Infinity"); #elif defined(fpclassify) && defined(FP_NAN) && defined(FP_INFINITE) && defined(FP_ZERO) switch (fpclassify(value)) { case FP_NAN: return PUGIXML_TEXT("NaN"); case FP_INFINITE: return value > 0 ? PUGIXML_TEXT("Infinity") : PUGIXML_TEXT("-Infinity"); case FP_ZERO: return PUGIXML_TEXT("0"); default: return 0; } #else // fallback const volatile double v = value; if (v == 0) return PUGIXML_TEXT("0"); if (v != v) return PUGIXML_TEXT("NaN"); if (v * 2 == v) return value > 0 ? PUGIXML_TEXT("Infinity") : PUGIXML_TEXT("-Infinity"); return 0; #endif } PUGI_IMPL_FN bool convert_number_to_boolean(double value) { return (value != 0 && !is_nan(value)); } PUGI_IMPL_FN void truncate_zeros(char* begin, char* end) { while (begin != end && end[-1] == '0') end--; *end = 0; } // gets mantissa digits in the form of 0.xxxxx with 0. implied and the exponent #if defined(PUGI_IMPL_MSVC_CRT_VERSION) && PUGI_IMPL_MSVC_CRT_VERSION >= 1400 PUGI_IMPL_FN void convert_number_to_mantissa_exponent(double value, char (&buffer)[32], char** out_mantissa, int* out_exponent) { // get base values int sign, exponent; _ecvt_s(buffer, sizeof(buffer), value, DBL_DIG + 1, &exponent, &sign); // truncate redundant zeros truncate_zeros(buffer, buffer + strlen(buffer)); // fill results *out_mantissa = buffer; *out_exponent = exponent; } #else PUGI_IMPL_FN void convert_number_to_mantissa_exponent(double value, char (&buffer)[32], char** out_mantissa, int* out_exponent) { // get a scientific notation value with IEEE DBL_DIG decimals PUGI_IMPL_SNPRINTF(buffer, "%.*e", DBL_DIG, value); // get the exponent (possibly negative) char* exponent_string = strchr(buffer, 'e'); assert(exponent_string); int exponent = atoi(exponent_string + 1); // extract mantissa string: skip sign char* mantissa = buffer[0] == '-' ? buffer + 1 : buffer; assert(mantissa[0] != '0' && (mantissa[1] == '.' || mantissa[1] == ',')); // divide mantissa by 10 to eliminate integer part mantissa[1] = mantissa[0]; mantissa++; exponent++; // remove extra mantissa digits and zero-terminate mantissa truncate_zeros(mantissa, exponent_string); // fill results *out_mantissa = mantissa; *out_exponent = exponent; } #endif PUGI_IMPL_FN xpath_string convert_number_to_string(double value, xpath_allocator* alloc) { // try special number conversion const char_t* special = convert_number_to_string_special(value); if (special) return xpath_string::from_const(special); // get mantissa + exponent form char mantissa_buffer[32]; char* mantissa; int exponent; convert_number_to_mantissa_exponent(value, mantissa_buffer, &mantissa, &exponent); // allocate a buffer of suitable length for the number size_t result_size = strlen(mantissa_buffer) + (exponent > 0 ? exponent : -exponent) + 4; char_t* result = static_cast(alloc->allocate(sizeof(char_t) * result_size)); if (!result) return xpath_string(); // make the number! char_t* s = result; // sign if (value < 0) *s++ = '-'; // integer part if (exponent <= 0) { *s++ = '0'; } else { while (exponent > 0) { assert(*mantissa == 0 || static_cast(*mantissa - '0') <= 9); *s++ = *mantissa ? *mantissa++ : '0'; exponent--; } } // fractional part if (*mantissa) { // decimal point *s++ = '.'; // extra zeroes from negative exponent while (exponent < 0) { *s++ = '0'; exponent++; } // extra mantissa digits while (*mantissa) { assert(static_cast(*mantissa - '0') <= 9); *s++ = *mantissa++; } } // zero-terminate assert(s < result + result_size); *s = 0; return xpath_string::from_heap_preallocated(result, s); } PUGI_IMPL_FN bool check_string_to_number_format(const char_t* string) { // parse leading whitespace while (PUGI_IMPL_IS_CHARTYPE(*string, ct_space)) ++string; // parse sign if (*string == '-') ++string; if (!*string) return false; // if there is no integer part, there should be a decimal part with at least one digit if (!PUGI_IMPL_IS_CHARTYPEX(string[0], ctx_digit) && (string[0] != '.' || !PUGI_IMPL_IS_CHARTYPEX(string[1], ctx_digit))) return false; // parse integer part while (PUGI_IMPL_IS_CHARTYPEX(*string, ctx_digit)) ++string; // parse decimal part if (*string == '.') { ++string; while (PUGI_IMPL_IS_CHARTYPEX(*string, ctx_digit)) ++string; } // parse trailing whitespace while (PUGI_IMPL_IS_CHARTYPE(*string, ct_space)) ++string; return *string == 0; } PUGI_IMPL_FN double convert_string_to_number(const char_t* string) { // check string format if (!check_string_to_number_format(string)) return gen_nan(); // parse string #ifdef PUGIXML_WCHAR_MODE return wcstod(string, 0); #else return strtod(string, 0); #endif } PUGI_IMPL_FN bool convert_string_to_number_scratch(char_t (&buffer)[32], const char_t* begin, const char_t* end, double* out_result) { size_t length = static_cast(end - begin); char_t* scratch = buffer; if (length >= sizeof(buffer) / sizeof(buffer[0])) { // need to make dummy on-heap copy scratch = static_cast(xml_memory::allocate((length + 1) * sizeof(char_t))); if (!scratch) return false; } // copy string to zero-terminated buffer and perform conversion memcpy(scratch, begin, length * sizeof(char_t)); scratch[length] = 0; *out_result = convert_string_to_number(scratch); // free dummy buffer if (scratch != buffer) xml_memory::deallocate(scratch); return true; } PUGI_IMPL_FN double round_nearest(double value) { return floor(value + 0.5); } PUGI_IMPL_FN double round_nearest_nzero(double value) { // same as round_nearest, but returns -0 for [-0.5, -0] // ceil is used to differentiate between +0 and -0 (we return -0 for [-0.5, -0] and +0 for +0) return (value >= -0.5 && value <= 0) ? ceil(value) : floor(value + 0.5); } PUGI_IMPL_FN const char_t* qualified_name(const xpath_node& node) { return node.attribute() ? node.attribute().name() : node.node().name(); } PUGI_IMPL_FN const char_t* local_name(const xpath_node& node) { const char_t* name = qualified_name(node); const char_t* p = find_char(name, ':'); return p ? p + 1 : name; } struct namespace_uri_predicate { const char_t* prefix; size_t prefix_length; namespace_uri_predicate(const char_t* name) { const char_t* pos = find_char(name, ':'); prefix = pos ? name : 0; prefix_length = pos ? static_cast(pos - name) : 0; } bool operator()(xml_attribute a) const { const char_t* name = a.name(); if (!starts_with(name, PUGIXML_TEXT("xmlns"))) return false; return prefix ? name[5] == ':' && strequalrange(name + 6, prefix, prefix_length) : name[5] == 0; } }; PUGI_IMPL_FN const char_t* namespace_uri(xml_node node) { namespace_uri_predicate pred = node.name(); xml_node p = node; while (p) { xml_attribute a = p.find_attribute(pred); if (a) return a.value(); p = p.parent(); } return PUGIXML_TEXT(""); } PUGI_IMPL_FN const char_t* namespace_uri(xml_attribute attr, xml_node parent) { namespace_uri_predicate pred = attr.name(); // Default namespace does not apply to attributes if (!pred.prefix) return PUGIXML_TEXT(""); xml_node p = parent; while (p) { xml_attribute a = p.find_attribute(pred); if (a) return a.value(); p = p.parent(); } return PUGIXML_TEXT(""); } PUGI_IMPL_FN const char_t* namespace_uri(const xpath_node& node) { return node.attribute() ? namespace_uri(node.attribute(), node.parent()) : namespace_uri(node.node()); } PUGI_IMPL_FN char_t* normalize_space(char_t* buffer) { char_t* write = buffer; for (char_t* it = buffer; *it; ) { char_t ch = *it++; if (PUGI_IMPL_IS_CHARTYPE(ch, ct_space)) { // replace whitespace sequence with single space while (PUGI_IMPL_IS_CHARTYPE(*it, ct_space)) it++; // avoid leading spaces if (write != buffer) *write++ = ' '; } else *write++ = ch; } // remove trailing space if (write != buffer && PUGI_IMPL_IS_CHARTYPE(write[-1], ct_space)) write--; // zero-terminate *write = 0; return write; } PUGI_IMPL_FN char_t* translate(char_t* buffer, const char_t* from, const char_t* to, size_t to_length) { char_t* write = buffer; while (*buffer) { PUGI_IMPL_DMC_VOLATILE char_t ch = *buffer++; const char_t* pos = find_char(from, ch); if (!pos) *write++ = ch; // do not process else if (static_cast(pos - from) < to_length) *write++ = to[pos - from]; // replace } // zero-terminate *write = 0; return write; } PUGI_IMPL_FN unsigned char* translate_table_generate(xpath_allocator* alloc, const char_t* from, const char_t* to) { unsigned char table[128] = {0}; while (*from) { unsigned int fc = static_cast(*from); unsigned int tc = static_cast(*to); if (fc >= 128 || tc >= 128) return 0; // code=128 means "skip character" if (!table[fc]) table[fc] = static_cast(tc ? tc : 128); from++; if (tc) to++; } for (int i = 0; i < 128; ++i) if (!table[i]) table[i] = static_cast(i); void* result = alloc->allocate(sizeof(table)); if (!result) return 0; memcpy(result, table, sizeof(table)); return static_cast(result); } PUGI_IMPL_FN char_t* translate_table(char_t* buffer, const unsigned char* table) { char_t* write = buffer; while (*buffer) { char_t ch = *buffer++; unsigned int index = static_cast(ch); if (index < 128) { unsigned char code = table[index]; // code=128 means "skip character" (table size is 128 so 128 can be a special value) // this code skips these characters without extra branches *write = static_cast(code); write += 1 - (code >> 7); } else { *write++ = ch; } } // zero-terminate *write = 0; return write; } inline bool is_xpath_attribute(const char_t* name) { return !(starts_with(name, PUGIXML_TEXT("xmlns")) && (name[5] == 0 || name[5] == ':')); } struct xpath_variable_boolean: xpath_variable { xpath_variable_boolean(): xpath_variable(xpath_type_boolean), value(false) { } bool value; char_t name[1]; }; struct xpath_variable_number: xpath_variable { xpath_variable_number(): xpath_variable(xpath_type_number), value(0) { } double value; char_t name[1]; }; struct xpath_variable_string: xpath_variable { xpath_variable_string(): xpath_variable(xpath_type_string), value(0) { } ~xpath_variable_string() { if (value) xml_memory::deallocate(value); } char_t* value; char_t name[1]; }; struct xpath_variable_node_set: xpath_variable { xpath_variable_node_set(): xpath_variable(xpath_type_node_set) { } xpath_node_set value; char_t name[1]; }; static const xpath_node_set dummy_node_set; PUGI_IMPL_FN PUGI_IMPL_UNSIGNED_OVERFLOW unsigned int hash_string(const char_t* str) { // Jenkins one-at-a-time hash (http://en.wikipedia.org/wiki/Jenkins_hash_function#one-at-a-time) unsigned int result = 0; while (*str) { result += static_cast(*str++); result += result << 10; result ^= result >> 6; } result += result << 3; result ^= result >> 11; result += result << 15; return result; } template PUGI_IMPL_FN T* new_xpath_variable(const char_t* name) { size_t length = strlength(name); if (length == 0) return 0; // empty variable names are invalid // $$ we can't use offsetof(T, name) because T is non-POD, so we just allocate additional length characters void* memory = xml_memory::allocate(sizeof(T) + length * sizeof(char_t)); if (!memory) return 0; T* result = new (memory) T(); memcpy(result->name, name, (length + 1) * sizeof(char_t)); return result; } PUGI_IMPL_FN xpath_variable* new_xpath_variable(xpath_value_type type, const char_t* name) { switch (type) { case xpath_type_node_set: return new_xpath_variable(name); case xpath_type_number: return new_xpath_variable(name); case xpath_type_string: return new_xpath_variable(name); case xpath_type_boolean: return new_xpath_variable(name); default: return 0; } } template PUGI_IMPL_FN void delete_xpath_variable(T* var) { var->~T(); xml_memory::deallocate(var); } PUGI_IMPL_FN void delete_xpath_variable(xpath_value_type type, xpath_variable* var) { switch (type) { case xpath_type_node_set: delete_xpath_variable(static_cast(var)); break; case xpath_type_number: delete_xpath_variable(static_cast(var)); break; case xpath_type_string: delete_xpath_variable(static_cast(var)); break; case xpath_type_boolean: delete_xpath_variable(static_cast(var)); break; default: assert(false && "Invalid variable type"); // unreachable } } PUGI_IMPL_FN bool copy_xpath_variable(xpath_variable* lhs, const xpath_variable* rhs) { switch (rhs->type()) { case xpath_type_node_set: return lhs->set(static_cast(rhs)->value); case xpath_type_number: return lhs->set(static_cast(rhs)->value); case xpath_type_string: return lhs->set(static_cast(rhs)->value); case xpath_type_boolean: return lhs->set(static_cast(rhs)->value); default: assert(false && "Invalid variable type"); // unreachable return false; } } PUGI_IMPL_FN bool get_variable_scratch(char_t (&buffer)[32], xpath_variable_set* set, const char_t* begin, const char_t* end, xpath_variable** out_result) { size_t length = static_cast(end - begin); char_t* scratch = buffer; if (length >= sizeof(buffer) / sizeof(buffer[0])) { // need to make dummy on-heap copy scratch = static_cast(xml_memory::allocate((length + 1) * sizeof(char_t))); if (!scratch) return false; } // copy string to zero-terminated buffer and perform lookup memcpy(scratch, begin, length * sizeof(char_t)); scratch[length] = 0; *out_result = set->get(scratch); // free dummy buffer if (scratch != buffer) xml_memory::deallocate(scratch); return true; } PUGI_IMPL_NS_END // Internal node set class PUGI_IMPL_NS_BEGIN PUGI_IMPL_FN xpath_node_set::type_t xpath_get_order(const xpath_node* begin, const xpath_node* end) { if (end - begin < 2) return xpath_node_set::type_sorted; document_order_comparator cmp; bool first = cmp(begin[0], begin[1]); for (const xpath_node* it = begin + 1; it + 1 < end; ++it) if (cmp(it[0], it[1]) != first) return xpath_node_set::type_unsorted; return first ? xpath_node_set::type_sorted : xpath_node_set::type_sorted_reverse; } PUGI_IMPL_FN xpath_node_set::type_t xpath_sort(xpath_node* begin, xpath_node* end, xpath_node_set::type_t type, bool rev) { xpath_node_set::type_t order = rev ? xpath_node_set::type_sorted_reverse : xpath_node_set::type_sorted; if (type == xpath_node_set::type_unsorted) { xpath_node_set::type_t sorted = xpath_get_order(begin, end); if (sorted == xpath_node_set::type_unsorted) { sort(begin, end, document_order_comparator()); type = xpath_node_set::type_sorted; } else type = sorted; } if (type != order) reverse(begin, end); return order; } PUGI_IMPL_FN xpath_node xpath_first(const xpath_node* begin, const xpath_node* end, xpath_node_set::type_t type) { if (begin == end) return xpath_node(); switch (type) { case xpath_node_set::type_sorted: return *begin; case xpath_node_set::type_sorted_reverse: return *(end - 1); case xpath_node_set::type_unsorted: return *min_element(begin, end, document_order_comparator()); default: assert(false && "Invalid node set type"); // unreachable return xpath_node(); } } class xpath_node_set_raw { xpath_node_set::type_t _type; xpath_node* _begin; xpath_node* _end; xpath_node* _eos; public: xpath_node_set_raw(): _type(xpath_node_set::type_unsorted), _begin(0), _end(0), _eos(0) { } xpath_node* begin() const { return _begin; } xpath_node* end() const { return _end; } bool empty() const { return _begin == _end; } size_t size() const { return static_cast(_end - _begin); } xpath_node first() const { return xpath_first(_begin, _end, _type); } void push_back_grow(const xpath_node& node, xpath_allocator* alloc); void push_back(const xpath_node& node, xpath_allocator* alloc) { if (_end != _eos) *_end++ = node; else push_back_grow(node, alloc); } void append(const xpath_node* begin_, const xpath_node* end_, xpath_allocator* alloc) { if (begin_ == end_) return; size_t size_ = static_cast(_end - _begin); size_t capacity = static_cast(_eos - _begin); size_t count = static_cast(end_ - begin_); if (size_ + count > capacity) { // reallocate the old array or allocate a new one xpath_node* data = static_cast(alloc->reallocate(_begin, capacity * sizeof(xpath_node), (size_ + count) * sizeof(xpath_node))); if (!data) return; // finalize _begin = data; _end = data + size_; _eos = data + size_ + count; } memcpy(_end, begin_, count * sizeof(xpath_node)); _end += count; } void sort_do() { _type = xpath_sort(_begin, _end, _type, false); } void truncate(xpath_node* pos) { assert(_begin <= pos && pos <= _end); _end = pos; } void remove_duplicates(xpath_allocator* alloc) { if (_type == xpath_node_set::type_unsorted && _end - _begin > 2) { xpath_allocator_capture cr(alloc); size_t size_ = static_cast(_end - _begin); size_t hash_size = 1; while (hash_size < size_ + size_ / 2) hash_size *= 2; const void** hash_data = static_cast(alloc->allocate(hash_size * sizeof(void**))); if (!hash_data) return; memset(hash_data, 0, hash_size * sizeof(const void**)); xpath_node* write = _begin; for (xpath_node* it = _begin; it != _end; ++it) { const void* attr = it->attribute().internal_object(); const void* node = it->node().internal_object(); const void* key = attr ? attr : node; if (key && hash_insert(hash_data, hash_size, key)) { *write++ = *it; } } _end = write; } else { _end = unique(_begin, _end); } } xpath_node_set::type_t type() const { return _type; } void set_type(xpath_node_set::type_t value) { _type = value; } }; PUGI_IMPL_FN_NO_INLINE void xpath_node_set_raw::push_back_grow(const xpath_node& node, xpath_allocator* alloc) { size_t capacity = static_cast(_eos - _begin); // get new capacity (1.5x rule) size_t new_capacity = capacity + capacity / 2 + 1; // reallocate the old array or allocate a new one xpath_node* data = static_cast(alloc->reallocate(_begin, capacity * sizeof(xpath_node), new_capacity * sizeof(xpath_node))); if (!data) return; // finalize _begin = data; _end = data + capacity; _eos = data + new_capacity; // push *_end++ = node; } PUGI_IMPL_NS_END PUGI_IMPL_NS_BEGIN struct xpath_context { xpath_node n; size_t position, size; xpath_context(const xpath_node& n_, size_t position_, size_t size_): n(n_), position(position_), size(size_) { } }; enum lexeme_t { lex_none = 0, lex_equal, lex_not_equal, lex_less, lex_greater, lex_less_or_equal, lex_greater_or_equal, lex_plus, lex_minus, lex_multiply, lex_union, lex_var_ref, lex_open_brace, lex_close_brace, lex_quoted_string, lex_number, lex_slash, lex_double_slash, lex_open_square_brace, lex_close_square_brace, lex_string, lex_comma, lex_axis_attribute, lex_dot, lex_double_dot, lex_double_colon, lex_eof }; struct xpath_lexer_string { const char_t* begin; const char_t* end; xpath_lexer_string(): begin(0), end(0) { } bool operator==(const char_t* other) const { size_t length = static_cast(end - begin); return strequalrange(other, begin, length); } }; class xpath_lexer { const char_t* _cur; const char_t* _cur_lexeme_pos; xpath_lexer_string _cur_lexeme_contents; lexeme_t _cur_lexeme; public: explicit xpath_lexer(const char_t* query): _cur(query) { next(); } const char_t* state() const { return _cur; } void next() { const char_t* cur = _cur; while (PUGI_IMPL_IS_CHARTYPE(*cur, ct_space)) ++cur; // save lexeme position for error reporting _cur_lexeme_pos = cur; switch (*cur) { case 0: _cur_lexeme = lex_eof; break; case '>': if (*(cur+1) == '=') { cur += 2; _cur_lexeme = lex_greater_or_equal; } else { cur += 1; _cur_lexeme = lex_greater; } break; case '<': if (*(cur+1) == '=') { cur += 2; _cur_lexeme = lex_less_or_equal; } else { cur += 1; _cur_lexeme = lex_less; } break; case '!': if (*(cur+1) == '=') { cur += 2; _cur_lexeme = lex_not_equal; } else { _cur_lexeme = lex_none; } break; case '=': cur += 1; _cur_lexeme = lex_equal; break; case '+': cur += 1; _cur_lexeme = lex_plus; break; case '-': cur += 1; _cur_lexeme = lex_minus; break; case '*': cur += 1; _cur_lexeme = lex_multiply; break; case '|': cur += 1; _cur_lexeme = lex_union; break; case '$': cur += 1; if (PUGI_IMPL_IS_CHARTYPEX(*cur, ctx_start_symbol)) { _cur_lexeme_contents.begin = cur; while (PUGI_IMPL_IS_CHARTYPEX(*cur, ctx_symbol)) cur++; if (cur[0] == ':' && PUGI_IMPL_IS_CHARTYPEX(cur[1], ctx_symbol)) // qname { cur++; // : while (PUGI_IMPL_IS_CHARTYPEX(*cur, ctx_symbol)) cur++; } _cur_lexeme_contents.end = cur; _cur_lexeme = lex_var_ref; } else { _cur_lexeme = lex_none; } break; case '(': cur += 1; _cur_lexeme = lex_open_brace; break; case ')': cur += 1; _cur_lexeme = lex_close_brace; break; case '[': cur += 1; _cur_lexeme = lex_open_square_brace; break; case ']': cur += 1; _cur_lexeme = lex_close_square_brace; break; case ',': cur += 1; _cur_lexeme = lex_comma; break; case '/': if (*(cur+1) == '/') { cur += 2; _cur_lexeme = lex_double_slash; } else { cur += 1; _cur_lexeme = lex_slash; } break; case '.': if (*(cur+1) == '.') { cur += 2; _cur_lexeme = lex_double_dot; } else if (PUGI_IMPL_IS_CHARTYPEX(*(cur+1), ctx_digit)) { _cur_lexeme_contents.begin = cur; // . ++cur; while (PUGI_IMPL_IS_CHARTYPEX(*cur, ctx_digit)) cur++; _cur_lexeme_contents.end = cur; _cur_lexeme = lex_number; } else { cur += 1; _cur_lexeme = lex_dot; } break; case '@': cur += 1; _cur_lexeme = lex_axis_attribute; break; case '"': case '\'': { char_t terminator = *cur; ++cur; _cur_lexeme_contents.begin = cur; while (*cur && *cur != terminator) cur++; _cur_lexeme_contents.end = cur; if (!*cur) _cur_lexeme = lex_none; else { cur += 1; _cur_lexeme = lex_quoted_string; } break; } case ':': if (*(cur+1) == ':') { cur += 2; _cur_lexeme = lex_double_colon; } else { _cur_lexeme = lex_none; } break; default: if (PUGI_IMPL_IS_CHARTYPEX(*cur, ctx_digit)) { _cur_lexeme_contents.begin = cur; while (PUGI_IMPL_IS_CHARTYPEX(*cur, ctx_digit)) cur++; if (*cur == '.') { cur++; while (PUGI_IMPL_IS_CHARTYPEX(*cur, ctx_digit)) cur++; } _cur_lexeme_contents.end = cur; _cur_lexeme = lex_number; } else if (PUGI_IMPL_IS_CHARTYPEX(*cur, ctx_start_symbol)) { _cur_lexeme_contents.begin = cur; while (PUGI_IMPL_IS_CHARTYPEX(*cur, ctx_symbol)) cur++; if (cur[0] == ':') { if (cur[1] == '*') // namespace test ncname:* { cur += 2; // :* } else if (PUGI_IMPL_IS_CHARTYPEX(cur[1], ctx_symbol)) // namespace test qname { cur++; // : while (PUGI_IMPL_IS_CHARTYPEX(*cur, ctx_symbol)) cur++; } } _cur_lexeme_contents.end = cur; _cur_lexeme = lex_string; } else { _cur_lexeme = lex_none; } } _cur = cur; } lexeme_t current() const { return _cur_lexeme; } const char_t* current_pos() const { return _cur_lexeme_pos; } const xpath_lexer_string& contents() const { assert(_cur_lexeme == lex_var_ref || _cur_lexeme == lex_number || _cur_lexeme == lex_string || _cur_lexeme == lex_quoted_string); return _cur_lexeme_contents; } }; enum ast_type_t { ast_unknown, ast_op_or, // left or right ast_op_and, // left and right ast_op_equal, // left = right ast_op_not_equal, // left != right ast_op_less, // left < right ast_op_greater, // left > right ast_op_less_or_equal, // left <= right ast_op_greater_or_equal, // left >= right ast_op_add, // left + right ast_op_subtract, // left - right ast_op_multiply, // left * right ast_op_divide, // left / right ast_op_mod, // left % right ast_op_negate, // left - right ast_op_union, // left | right ast_predicate, // apply predicate to set; next points to next predicate ast_filter, // select * from left where right ast_string_constant, // string constant ast_number_constant, // number constant ast_variable, // variable ast_func_last, // last() ast_func_position, // position() ast_func_count, // count(left) ast_func_id, // id(left) ast_func_local_name_0, // local-name() ast_func_local_name_1, // local-name(left) ast_func_namespace_uri_0, // namespace-uri() ast_func_namespace_uri_1, // namespace-uri(left) ast_func_name_0, // name() ast_func_name_1, // name(left) ast_func_string_0, // string() ast_func_string_1, // string(left) ast_func_concat, // concat(left, right, siblings) ast_func_starts_with, // starts_with(left, right) ast_func_contains, // contains(left, right) ast_func_substring_before, // substring-before(left, right) ast_func_substring_after, // substring-after(left, right) ast_func_substring_2, // substring(left, right) ast_func_substring_3, // substring(left, right, third) ast_func_string_length_0, // string-length() ast_func_string_length_1, // string-length(left) ast_func_normalize_space_0, // normalize-space() ast_func_normalize_space_1, // normalize-space(left) ast_func_translate, // translate(left, right, third) ast_func_boolean, // boolean(left) ast_func_not, // not(left) ast_func_true, // true() ast_func_false, // false() ast_func_lang, // lang(left) ast_func_number_0, // number() ast_func_number_1, // number(left) ast_func_sum, // sum(left) ast_func_floor, // floor(left) ast_func_ceiling, // ceiling(left) ast_func_round, // round(left) ast_step, // process set left with step ast_step_root, // select root node ast_opt_translate_table, // translate(left, right, third) where right/third are constants ast_opt_compare_attribute // @name = 'string' }; enum axis_t { axis_ancestor, axis_ancestor_or_self, axis_attribute, axis_child, axis_descendant, axis_descendant_or_self, axis_following, axis_following_sibling, axis_namespace, axis_parent, axis_preceding, axis_preceding_sibling, axis_self }; enum nodetest_t { nodetest_none, nodetest_name, nodetest_type_node, nodetest_type_comment, nodetest_type_pi, nodetest_type_text, nodetest_pi, nodetest_all, nodetest_all_in_namespace }; enum predicate_t { predicate_default, predicate_posinv, predicate_constant, predicate_constant_one }; enum nodeset_eval_t { nodeset_eval_all, nodeset_eval_any, nodeset_eval_first }; template struct axis_to_type { static const axis_t axis; }; template const axis_t axis_to_type::axis = N; class xpath_ast_node { private: // node type char _type; char _rettype; // for ast_step char _axis; // for ast_step/ast_predicate/ast_filter char _test; // tree node structure xpath_ast_node* _left; xpath_ast_node* _right; xpath_ast_node* _next; union { // value for ast_string_constant const char_t* string; // value for ast_number_constant double number; // variable for ast_variable xpath_variable* variable; // node test for ast_step (node name/namespace/node type/pi target) const char_t* nodetest; // table for ast_opt_translate_table const unsigned char* table; } _data; xpath_ast_node(const xpath_ast_node&); xpath_ast_node& operator=(const xpath_ast_node&); template static bool compare_eq(xpath_ast_node* lhs, xpath_ast_node* rhs, const xpath_context& c, const xpath_stack& stack, const Comp& comp) { xpath_value_type lt = lhs->rettype(), rt = rhs->rettype(); if (lt != xpath_type_node_set && rt != xpath_type_node_set) { if (lt == xpath_type_boolean || rt == xpath_type_boolean) return comp(lhs->eval_boolean(c, stack), rhs->eval_boolean(c, stack)); else if (lt == xpath_type_number || rt == xpath_type_number) return comp(lhs->eval_number(c, stack), rhs->eval_number(c, stack)); else if (lt == xpath_type_string || rt == xpath_type_string) { xpath_allocator_capture cr(stack.result); xpath_string ls = lhs->eval_string(c, stack); xpath_string rs = rhs->eval_string(c, stack); return comp(ls, rs); } } else if (lt == xpath_type_node_set && rt == xpath_type_node_set) { xpath_allocator_capture cr(stack.result); xpath_node_set_raw ls = lhs->eval_node_set(c, stack, nodeset_eval_all); xpath_node_set_raw rs = rhs->eval_node_set(c, stack, nodeset_eval_all); for (const xpath_node* li = ls.begin(); li != ls.end(); ++li) for (const xpath_node* ri = rs.begin(); ri != rs.end(); ++ri) { xpath_allocator_capture cri(stack.result); if (comp(string_value(*li, stack.result), string_value(*ri, stack.result))) return true; } return false; } else { if (lt == xpath_type_node_set) { swap(lhs, rhs); swap(lt, rt); } if (lt == xpath_type_boolean) return comp(lhs->eval_boolean(c, stack), rhs->eval_boolean(c, stack)); else if (lt == xpath_type_number) { xpath_allocator_capture cr(stack.result); double l = lhs->eval_number(c, stack); xpath_node_set_raw rs = rhs->eval_node_set(c, stack, nodeset_eval_all); for (const xpath_node* ri = rs.begin(); ri != rs.end(); ++ri) { xpath_allocator_capture cri(stack.result); if (comp(l, convert_string_to_number(string_value(*ri, stack.result).c_str()))) return true; } return false; } else if (lt == xpath_type_string) { xpath_allocator_capture cr(stack.result); xpath_string l = lhs->eval_string(c, stack); xpath_node_set_raw rs = rhs->eval_node_set(c, stack, nodeset_eval_all); for (const xpath_node* ri = rs.begin(); ri != rs.end(); ++ri) { xpath_allocator_capture cri(stack.result); if (comp(l, string_value(*ri, stack.result))) return true; } return false; } } assert(false && "Wrong types"); // unreachable return false; } static bool eval_once(xpath_node_set::type_t type, nodeset_eval_t eval) { return type == xpath_node_set::type_sorted ? eval != nodeset_eval_all : eval == nodeset_eval_any; } template static bool compare_rel(xpath_ast_node* lhs, xpath_ast_node* rhs, const xpath_context& c, const xpath_stack& stack, const Comp& comp) { xpath_value_type lt = lhs->rettype(), rt = rhs->rettype(); if (lt != xpath_type_node_set && rt != xpath_type_node_set) return comp(lhs->eval_number(c, stack), rhs->eval_number(c, stack)); else if (lt == xpath_type_node_set && rt == xpath_type_node_set) { xpath_allocator_capture cr(stack.result); xpath_node_set_raw ls = lhs->eval_node_set(c, stack, nodeset_eval_all); xpath_node_set_raw rs = rhs->eval_node_set(c, stack, nodeset_eval_all); for (const xpath_node* li = ls.begin(); li != ls.end(); ++li) { xpath_allocator_capture cri(stack.result); double l = convert_string_to_number(string_value(*li, stack.result).c_str()); for (const xpath_node* ri = rs.begin(); ri != rs.end(); ++ri) { xpath_allocator_capture crii(stack.result); if (comp(l, convert_string_to_number(string_value(*ri, stack.result).c_str()))) return true; } } return false; } else if (lt != xpath_type_node_set && rt == xpath_type_node_set) { xpath_allocator_capture cr(stack.result); double l = lhs->eval_number(c, stack); xpath_node_set_raw rs = rhs->eval_node_set(c, stack, nodeset_eval_all); for (const xpath_node* ri = rs.begin(); ri != rs.end(); ++ri) { xpath_allocator_capture cri(stack.result); if (comp(l, convert_string_to_number(string_value(*ri, stack.result).c_str()))) return true; } return false; } else if (lt == xpath_type_node_set && rt != xpath_type_node_set) { xpath_allocator_capture cr(stack.result); xpath_node_set_raw ls = lhs->eval_node_set(c, stack, nodeset_eval_all); double r = rhs->eval_number(c, stack); for (const xpath_node* li = ls.begin(); li != ls.end(); ++li) { xpath_allocator_capture cri(stack.result); if (comp(convert_string_to_number(string_value(*li, stack.result).c_str()), r)) return true; } return false; } else { assert(false && "Wrong types"); // unreachable return false; } } static void apply_predicate_boolean(xpath_node_set_raw& ns, size_t first, xpath_ast_node* expr, const xpath_stack& stack, bool once) { assert(ns.size() >= first); assert(expr->rettype() != xpath_type_number); size_t i = 1; size_t size = ns.size() - first; xpath_node* last = ns.begin() + first; // remove_if... or well, sort of for (xpath_node* it = last; it != ns.end(); ++it, ++i) { xpath_context c(*it, i, size); if (expr->eval_boolean(c, stack)) { *last++ = *it; if (once) break; } } ns.truncate(last); } static void apply_predicate_number(xpath_node_set_raw& ns, size_t first, xpath_ast_node* expr, const xpath_stack& stack, bool once) { assert(ns.size() >= first); assert(expr->rettype() == xpath_type_number); size_t i = 1; size_t size = ns.size() - first; xpath_node* last = ns.begin() + first; // remove_if... or well, sort of for (xpath_node* it = last; it != ns.end(); ++it, ++i) { xpath_context c(*it, i, size); if (expr->eval_number(c, stack) == static_cast(i)) { *last++ = *it; if (once) break; } } ns.truncate(last); } static void apply_predicate_number_const(xpath_node_set_raw& ns, size_t first, xpath_ast_node* expr, const xpath_stack& stack) { assert(ns.size() >= first); assert(expr->rettype() == xpath_type_number); size_t size = ns.size() - first; xpath_node* last = ns.begin() + first; xpath_node cn; xpath_context c(cn, 1, size); double er = expr->eval_number(c, stack); if (er >= 1.0 && er <= static_cast(size)) { size_t eri = static_cast(er); if (er == static_cast(eri)) { xpath_node r = last[eri - 1]; *last++ = r; } } ns.truncate(last); } void apply_predicate(xpath_node_set_raw& ns, size_t first, const xpath_stack& stack, bool once) { if (ns.size() == first) return; assert(_type == ast_filter || _type == ast_predicate); if (_test == predicate_constant || _test == predicate_constant_one) apply_predicate_number_const(ns, first, _right, stack); else if (_right->rettype() == xpath_type_number) apply_predicate_number(ns, first, _right, stack, once); else apply_predicate_boolean(ns, first, _right, stack, once); } void apply_predicates(xpath_node_set_raw& ns, size_t first, const xpath_stack& stack, nodeset_eval_t eval) { if (ns.size() == first) return; bool last_once = eval_once(ns.type(), eval); for (xpath_ast_node* pred = _right; pred; pred = pred->_next) pred->apply_predicate(ns, first, stack, !pred->_next && last_once); } bool step_push(xpath_node_set_raw& ns, xml_attribute_struct* a, xml_node_struct* parent, xpath_allocator* alloc) { assert(a); const char_t* name = a->name ? a->name + 0 : PUGIXML_TEXT(""); switch (_test) { case nodetest_name: if (strequal(name, _data.nodetest) && is_xpath_attribute(name)) { ns.push_back(xpath_node(xml_attribute(a), xml_node(parent)), alloc); return true; } break; case nodetest_type_node: case nodetest_all: if (is_xpath_attribute(name)) { ns.push_back(xpath_node(xml_attribute(a), xml_node(parent)), alloc); return true; } break; case nodetest_all_in_namespace: if (starts_with(name, _data.nodetest) && is_xpath_attribute(name)) { ns.push_back(xpath_node(xml_attribute(a), xml_node(parent)), alloc); return true; } break; default: ; } return false; } bool step_push(xpath_node_set_raw& ns, xml_node_struct* n, xpath_allocator* alloc) { assert(n); xml_node_type type = PUGI_IMPL_NODETYPE(n); switch (_test) { case nodetest_name: if (type == node_element && n->name && strequal(n->name, _data.nodetest)) { ns.push_back(xml_node(n), alloc); return true; } break; case nodetest_type_node: ns.push_back(xml_node(n), alloc); return true; case nodetest_type_comment: if (type == node_comment) { ns.push_back(xml_node(n), alloc); return true; } break; case nodetest_type_text: if (type == node_pcdata || type == node_cdata) { ns.push_back(xml_node(n), alloc); return true; } break; case nodetest_type_pi: if (type == node_pi) { ns.push_back(xml_node(n), alloc); return true; } break; case nodetest_pi: if (type == node_pi && n->name && strequal(n->name, _data.nodetest)) { ns.push_back(xml_node(n), alloc); return true; } break; case nodetest_all: if (type == node_element) { ns.push_back(xml_node(n), alloc); return true; } break; case nodetest_all_in_namespace: if (type == node_element && n->name && starts_with(n->name, _data.nodetest)) { ns.push_back(xml_node(n), alloc); return true; } break; default: assert(false && "Unknown axis"); // unreachable } return false; } template void step_fill(xpath_node_set_raw& ns, xml_node_struct* n, xpath_allocator* alloc, bool once, T) { const axis_t axis = T::axis; switch (axis) { case axis_attribute: { for (xml_attribute_struct* a = n->first_attribute; a; a = a->next_attribute) if (step_push(ns, a, n, alloc) & once) return; break; } case axis_child: { for (xml_node_struct* c = n->first_child; c; c = c->next_sibling) if (step_push(ns, c, alloc) & once) return; break; } case axis_descendant: case axis_descendant_or_self: { if (axis == axis_descendant_or_self) if (step_push(ns, n, alloc) & once) return; xml_node_struct* cur = n->first_child; while (cur) { if (step_push(ns, cur, alloc) & once) return; if (cur->first_child) cur = cur->first_child; else { while (!cur->next_sibling) { cur = cur->parent; if (cur == n) return; } cur = cur->next_sibling; } } break; } case axis_following_sibling: { for (xml_node_struct* c = n->next_sibling; c; c = c->next_sibling) if (step_push(ns, c, alloc) & once) return; break; } case axis_preceding_sibling: { for (xml_node_struct* c = n->prev_sibling_c; c->next_sibling; c = c->prev_sibling_c) if (step_push(ns, c, alloc) & once) return; break; } case axis_following: { xml_node_struct* cur = n; // exit from this node so that we don't include descendants while (!cur->next_sibling) { cur = cur->parent; if (!cur) return; } cur = cur->next_sibling; while (cur) { if (step_push(ns, cur, alloc) & once) return; if (cur->first_child) cur = cur->first_child; else { while (!cur->next_sibling) { cur = cur->parent; if (!cur) return; } cur = cur->next_sibling; } } break; } case axis_preceding: { xml_node_struct* cur = n; // exit from this node so that we don't include descendants while (!cur->prev_sibling_c->next_sibling) { cur = cur->parent; if (!cur) return; } cur = cur->prev_sibling_c; while (cur) { if (cur->first_child) cur = cur->first_child->prev_sibling_c; else { // leaf node, can't be ancestor if (step_push(ns, cur, alloc) & once) return; while (!cur->prev_sibling_c->next_sibling) { cur = cur->parent; if (!cur) return; if (!node_is_ancestor(cur, n)) if (step_push(ns, cur, alloc) & once) return; } cur = cur->prev_sibling_c; } } break; } case axis_ancestor: case axis_ancestor_or_self: { if (axis == axis_ancestor_or_self) if (step_push(ns, n, alloc) & once) return; xml_node_struct* cur = n->parent; while (cur) { if (step_push(ns, cur, alloc) & once) return; cur = cur->parent; } break; } case axis_self: { step_push(ns, n, alloc); break; } case axis_parent: { if (n->parent) step_push(ns, n->parent, alloc); break; } default: assert(false && "Unimplemented axis"); // unreachable } } template void step_fill(xpath_node_set_raw& ns, xml_attribute_struct* a, xml_node_struct* p, xpath_allocator* alloc, bool once, T v) { const axis_t axis = T::axis; switch (axis) { case axis_ancestor: case axis_ancestor_or_self: { if (axis == axis_ancestor_or_self && _test == nodetest_type_node) // reject attributes based on principal node type test if (step_push(ns, a, p, alloc) & once) return; xml_node_struct* cur = p; while (cur) { if (step_push(ns, cur, alloc) & once) return; cur = cur->parent; } break; } case axis_descendant_or_self: case axis_self: { if (_test == nodetest_type_node) // reject attributes based on principal node type test step_push(ns, a, p, alloc); break; } case axis_following: { xml_node_struct* cur = p; while (cur) { if (cur->first_child) cur = cur->first_child; else { while (!cur->next_sibling) { cur = cur->parent; if (!cur) return; } cur = cur->next_sibling; } if (step_push(ns, cur, alloc) & once) return; } break; } case axis_parent: { step_push(ns, p, alloc); break; } case axis_preceding: { // preceding:: axis does not include attribute nodes and attribute ancestors (they are the same as parent's ancestors), so we can reuse node preceding step_fill(ns, p, alloc, once, v); break; } default: assert(false && "Unimplemented axis"); // unreachable } } template void step_fill(xpath_node_set_raw& ns, const xpath_node& xn, xpath_allocator* alloc, bool once, T v) { const axis_t axis = T::axis; const bool axis_has_attributes = (axis == axis_ancestor || axis == axis_ancestor_or_self || axis == axis_descendant_or_self || axis == axis_following || axis == axis_parent || axis == axis_preceding || axis == axis_self); if (xn.node()) step_fill(ns, xn.node().internal_object(), alloc, once, v); else if (axis_has_attributes && xn.attribute() && xn.parent()) step_fill(ns, xn.attribute().internal_object(), xn.parent().internal_object(), alloc, once, v); } template xpath_node_set_raw step_do(const xpath_context& c, const xpath_stack& stack, nodeset_eval_t eval, T v) { const axis_t axis = T::axis; const bool axis_reverse = (axis == axis_ancestor || axis == axis_ancestor_or_self || axis == axis_preceding || axis == axis_preceding_sibling); const xpath_node_set::type_t axis_type = axis_reverse ? xpath_node_set::type_sorted_reverse : xpath_node_set::type_sorted; bool once = (axis == axis_attribute && _test == nodetest_name) || (!_right && eval_once(axis_type, eval)) || // coverity[mixed_enums] (_right && !_right->_next && _right->_test == predicate_constant_one); xpath_node_set_raw ns; ns.set_type(axis_type); if (_left) { xpath_node_set_raw s = _left->eval_node_set(c, stack, nodeset_eval_all); // self axis preserves the original order if (axis == axis_self) ns.set_type(s.type()); for (const xpath_node* it = s.begin(); it != s.end(); ++it) { size_t size = ns.size(); // in general, all axes generate elements in a particular order, but there is no order guarantee if axis is applied to two nodes if (axis != axis_self && size != 0) ns.set_type(xpath_node_set::type_unsorted); step_fill(ns, *it, stack.result, once, v); if (_right) apply_predicates(ns, size, stack, eval); } } else { step_fill(ns, c.n, stack.result, once, v); if (_right) apply_predicates(ns, 0, stack, eval); } // child, attribute and self axes always generate unique set of nodes // for other axis, if the set stayed sorted, it stayed unique because the traversal algorithms do not visit the same node twice if (axis != axis_child && axis != axis_attribute && axis != axis_self && ns.type() == xpath_node_set::type_unsorted) ns.remove_duplicates(stack.temp); return ns; } public: xpath_ast_node(ast_type_t type, xpath_value_type rettype_, const char_t* value): _type(static_cast(type)), _rettype(static_cast(rettype_)), _axis(0), _test(0), _left(0), _right(0), _next(0) { assert(type == ast_string_constant); _data.string = value; } xpath_ast_node(ast_type_t type, xpath_value_type rettype_, double value): _type(static_cast(type)), _rettype(static_cast(rettype_)), _axis(0), _test(0), _left(0), _right(0), _next(0) { assert(type == ast_number_constant); _data.number = value; } xpath_ast_node(ast_type_t type, xpath_value_type rettype_, xpath_variable* value): _type(static_cast(type)), _rettype(static_cast(rettype_)), _axis(0), _test(0), _left(0), _right(0), _next(0) { assert(type == ast_variable); _data.variable = value; } xpath_ast_node(ast_type_t type, xpath_value_type rettype_, xpath_ast_node* left = 0, xpath_ast_node* right = 0): _type(static_cast(type)), _rettype(static_cast(rettype_)), _axis(0), _test(0), _left(left), _right(right), _next(0) { } xpath_ast_node(ast_type_t type, xpath_ast_node* left, axis_t axis, nodetest_t test, const char_t* contents): _type(static_cast(type)), _rettype(xpath_type_node_set), _axis(static_cast(axis)), _test(static_cast(test)), _left(left), _right(0), _next(0) { assert(type == ast_step); _data.nodetest = contents; } xpath_ast_node(ast_type_t type, xpath_ast_node* left, xpath_ast_node* right, predicate_t test): _type(static_cast(type)), _rettype(xpath_type_node_set), _axis(0), _test(static_cast(test)), _left(left), _right(right), _next(0) { assert(type == ast_filter || type == ast_predicate); } void set_next(xpath_ast_node* value) { _next = value; } void set_right(xpath_ast_node* value) { _right = value; } bool eval_boolean(const xpath_context& c, const xpath_stack& stack) { switch (_type) { case ast_op_or: return _left->eval_boolean(c, stack) || _right->eval_boolean(c, stack); case ast_op_and: return _left->eval_boolean(c, stack) && _right->eval_boolean(c, stack); case ast_op_equal: return compare_eq(_left, _right, c, stack, equal_to()); case ast_op_not_equal: return compare_eq(_left, _right, c, stack, not_equal_to()); case ast_op_less: return compare_rel(_left, _right, c, stack, less()); case ast_op_greater: return compare_rel(_right, _left, c, stack, less()); case ast_op_less_or_equal: return compare_rel(_left, _right, c, stack, less_equal()); case ast_op_greater_or_equal: return compare_rel(_right, _left, c, stack, less_equal()); case ast_func_starts_with: { xpath_allocator_capture cr(stack.result); xpath_string lr = _left->eval_string(c, stack); xpath_string rr = _right->eval_string(c, stack); return starts_with(lr.c_str(), rr.c_str()); } case ast_func_contains: { xpath_allocator_capture cr(stack.result); xpath_string lr = _left->eval_string(c, stack); xpath_string rr = _right->eval_string(c, stack); return find_substring(lr.c_str(), rr.c_str()) != 0; } case ast_func_boolean: return _left->eval_boolean(c, stack); case ast_func_not: return !_left->eval_boolean(c, stack); case ast_func_true: return true; case ast_func_false: return false; case ast_func_lang: { if (c.n.attribute()) return false; xpath_allocator_capture cr(stack.result); xpath_string lang = _left->eval_string(c, stack); for (xml_node n = c.n.node(); n; n = n.parent()) { xml_attribute a = n.attribute(PUGIXML_TEXT("xml:lang")); if (a) { const char_t* value = a.value(); // strnicmp / strncasecmp is not portable for (const char_t* lit = lang.c_str(); *lit; ++lit) { if (tolower_ascii(*lit) != tolower_ascii(*value)) return false; ++value; } return *value == 0 || *value == '-'; } } return false; } case ast_opt_compare_attribute: { const char_t* value = (_right->_type == ast_string_constant) ? _right->_data.string : _right->_data.variable->get_string(); xml_attribute attr = c.n.node().attribute(_left->_data.nodetest); return attr && strequal(attr.value(), value) && is_xpath_attribute(attr.name()); } case ast_variable: { assert(_rettype == _data.variable->type()); if (_rettype == xpath_type_boolean) return _data.variable->get_boolean(); // variable needs to be converted to the correct type, this is handled by the fallthrough block below break; } default: ; } // none of the ast types that return the value directly matched, we need to perform type conversion switch (_rettype) { case xpath_type_number: return convert_number_to_boolean(eval_number(c, stack)); case xpath_type_string: { xpath_allocator_capture cr(stack.result); return !eval_string(c, stack).empty(); } case xpath_type_node_set: { xpath_allocator_capture cr(stack.result); return !eval_node_set(c, stack, nodeset_eval_any).empty(); } default: assert(false && "Wrong expression for return type boolean"); // unreachable return false; } } double eval_number(const xpath_context& c, const xpath_stack& stack) { switch (_type) { case ast_op_add: return _left->eval_number(c, stack) + _right->eval_number(c, stack); case ast_op_subtract: return _left->eval_number(c, stack) - _right->eval_number(c, stack); case ast_op_multiply: return _left->eval_number(c, stack) * _right->eval_number(c, stack); case ast_op_divide: return _left->eval_number(c, stack) / _right->eval_number(c, stack); case ast_op_mod: return fmod(_left->eval_number(c, stack), _right->eval_number(c, stack)); case ast_op_negate: return -_left->eval_number(c, stack); case ast_number_constant: return _data.number; case ast_func_last: return static_cast(c.size); case ast_func_position: return static_cast(c.position); case ast_func_count: { xpath_allocator_capture cr(stack.result); return static_cast(_left->eval_node_set(c, stack, nodeset_eval_all).size()); } case ast_func_string_length_0: { xpath_allocator_capture cr(stack.result); return static_cast(string_value(c.n, stack.result).length()); } case ast_func_string_length_1: { xpath_allocator_capture cr(stack.result); return static_cast(_left->eval_string(c, stack).length()); } case ast_func_number_0: { xpath_allocator_capture cr(stack.result); return convert_string_to_number(string_value(c.n, stack.result).c_str()); } case ast_func_number_1: return _left->eval_number(c, stack); case ast_func_sum: { xpath_allocator_capture cr(stack.result); double r = 0; xpath_node_set_raw ns = _left->eval_node_set(c, stack, nodeset_eval_all); for (const xpath_node* it = ns.begin(); it != ns.end(); ++it) { xpath_allocator_capture cri(stack.result); r += convert_string_to_number(string_value(*it, stack.result).c_str()); } return r; } case ast_func_floor: { double r = _left->eval_number(c, stack); return r == r ? floor(r) : r; } case ast_func_ceiling: { double r = _left->eval_number(c, stack); return r == r ? ceil(r) : r; } case ast_func_round: return round_nearest_nzero(_left->eval_number(c, stack)); case ast_variable: { assert(_rettype == _data.variable->type()); if (_rettype == xpath_type_number) return _data.variable->get_number(); // variable needs to be converted to the correct type, this is handled by the fallthrough block below break; } default: ; } // none of the ast types that return the value directly matched, we need to perform type conversion switch (_rettype) { case xpath_type_boolean: return eval_boolean(c, stack) ? 1 : 0; case xpath_type_string: { xpath_allocator_capture cr(stack.result); return convert_string_to_number(eval_string(c, stack).c_str()); } case xpath_type_node_set: { xpath_allocator_capture cr(stack.result); return convert_string_to_number(eval_string(c, stack).c_str()); } default: assert(false && "Wrong expression for return type number"); // unreachable return 0; } } xpath_string eval_string_concat(const xpath_context& c, const xpath_stack& stack) { assert(_type == ast_func_concat); xpath_allocator_capture ct(stack.temp); // count the string number size_t count = 1; for (xpath_ast_node* nc = _right; nc; nc = nc->_next) count++; // allocate a buffer for temporary string objects xpath_string* buffer = static_cast(stack.temp->allocate(count * sizeof(xpath_string))); if (!buffer) return xpath_string(); // evaluate all strings to temporary stack xpath_stack swapped_stack = {stack.temp, stack.result}; buffer[0] = _left->eval_string(c, swapped_stack); size_t pos = 1; for (xpath_ast_node* n = _right; n; n = n->_next, ++pos) buffer[pos] = n->eval_string(c, swapped_stack); assert(pos == count); // get total length size_t length = 0; for (size_t i = 0; i < count; ++i) length += buffer[i].length(); // create final string char_t* result = static_cast(stack.result->allocate((length + 1) * sizeof(char_t))); if (!result) return xpath_string(); char_t* ri = result; for (size_t j = 0; j < count; ++j) for (const char_t* bi = buffer[j].c_str(); *bi; ++bi) *ri++ = *bi; *ri = 0; return xpath_string::from_heap_preallocated(result, ri); } xpath_string eval_string(const xpath_context& c, const xpath_stack& stack) { switch (_type) { case ast_string_constant: return xpath_string::from_const(_data.string); case ast_func_local_name_0: { xpath_node na = c.n; return xpath_string::from_const(local_name(na)); } case ast_func_local_name_1: { xpath_allocator_capture cr(stack.result); xpath_node_set_raw ns = _left->eval_node_set(c, stack, nodeset_eval_first); xpath_node na = ns.first(); return xpath_string::from_const(local_name(na)); } case ast_func_name_0: { xpath_node na = c.n; return xpath_string::from_const(qualified_name(na)); } case ast_func_name_1: { xpath_allocator_capture cr(stack.result); xpath_node_set_raw ns = _left->eval_node_set(c, stack, nodeset_eval_first); xpath_node na = ns.first(); return xpath_string::from_const(qualified_name(na)); } case ast_func_namespace_uri_0: { xpath_node na = c.n; return xpath_string::from_const(namespace_uri(na)); } case ast_func_namespace_uri_1: { xpath_allocator_capture cr(stack.result); xpath_node_set_raw ns = _left->eval_node_set(c, stack, nodeset_eval_first); xpath_node na = ns.first(); return xpath_string::from_const(namespace_uri(na)); } case ast_func_string_0: return string_value(c.n, stack.result); case ast_func_string_1: return _left->eval_string(c, stack); case ast_func_concat: return eval_string_concat(c, stack); case ast_func_substring_before: { xpath_allocator_capture cr(stack.temp); xpath_stack swapped_stack = {stack.temp, stack.result}; xpath_string s = _left->eval_string(c, swapped_stack); xpath_string p = _right->eval_string(c, swapped_stack); const char_t* pos = find_substring(s.c_str(), p.c_str()); return pos ? xpath_string::from_heap(s.c_str(), pos, stack.result) : xpath_string(); } case ast_func_substring_after: { xpath_allocator_capture cr(stack.temp); xpath_stack swapped_stack = {stack.temp, stack.result}; xpath_string s = _left->eval_string(c, swapped_stack); xpath_string p = _right->eval_string(c, swapped_stack); const char_t* pos = find_substring(s.c_str(), p.c_str()); if (!pos) return xpath_string(); const char_t* rbegin = pos + p.length(); const char_t* rend = s.c_str() + s.length(); return s.uses_heap() ? xpath_string::from_heap(rbegin, rend, stack.result) : xpath_string::from_const(rbegin); } case ast_func_substring_2: { xpath_allocator_capture cr(stack.temp); xpath_stack swapped_stack = {stack.temp, stack.result}; xpath_string s = _left->eval_string(c, swapped_stack); size_t s_length = s.length(); double first = round_nearest(_right->eval_number(c, stack)); if (is_nan(first)) return xpath_string(); // NaN else if (first >= static_cast(s_length + 1)) return xpath_string(); size_t pos = first < 1 ? 1 : static_cast(first); assert(1 <= pos && pos <= s_length + 1); const char_t* rbegin = s.c_str() + (pos - 1); const char_t* rend = s.c_str() + s.length(); return s.uses_heap() ? xpath_string::from_heap(rbegin, rend, stack.result) : xpath_string::from_const(rbegin); } case ast_func_substring_3: { xpath_allocator_capture cr(stack.temp); xpath_stack swapped_stack = {stack.temp, stack.result}; xpath_string s = _left->eval_string(c, swapped_stack); size_t s_length = s.length(); double first = round_nearest(_right->eval_number(c, stack)); double last = first + round_nearest(_right->_next->eval_number(c, stack)); if (is_nan(first) || is_nan(last)) return xpath_string(); else if (first >= static_cast(s_length + 1)) return xpath_string(); else if (first >= last) return xpath_string(); else if (last < 1) return xpath_string(); size_t pos = first < 1 ? 1 : static_cast(first); size_t end = last >= static_cast(s_length + 1) ? s_length + 1 : static_cast(last); assert(1 <= pos && pos <= end && end <= s_length + 1); const char_t* rbegin = s.c_str() + (pos - 1); const char_t* rend = s.c_str() + (end - 1); return (end == s_length + 1 && !s.uses_heap()) ? xpath_string::from_const(rbegin) : xpath_string::from_heap(rbegin, rend, stack.result); } case ast_func_normalize_space_0: { xpath_string s = string_value(c.n, stack.result); char_t* begin = s.data(stack.result); if (!begin) return xpath_string(); char_t* end = normalize_space(begin); return xpath_string::from_heap_preallocated(begin, end); } case ast_func_normalize_space_1: { xpath_string s = _left->eval_string(c, stack); char_t* begin = s.data(stack.result); if (!begin) return xpath_string(); char_t* end = normalize_space(begin); return xpath_string::from_heap_preallocated(begin, end); } case ast_func_translate: { xpath_allocator_capture cr(stack.temp); xpath_stack swapped_stack = {stack.temp, stack.result}; xpath_string s = _left->eval_string(c, stack); xpath_string from = _right->eval_string(c, swapped_stack); xpath_string to = _right->_next->eval_string(c, swapped_stack); char_t* begin = s.data(stack.result); if (!begin) return xpath_string(); char_t* end = translate(begin, from.c_str(), to.c_str(), to.length()); return xpath_string::from_heap_preallocated(begin, end); } case ast_opt_translate_table: { xpath_string s = _left->eval_string(c, stack); char_t* begin = s.data(stack.result); if (!begin) return xpath_string(); char_t* end = translate_table(begin, _data.table); return xpath_string::from_heap_preallocated(begin, end); } case ast_variable: { assert(_rettype == _data.variable->type()); if (_rettype == xpath_type_string) return xpath_string::from_const(_data.variable->get_string()); // variable needs to be converted to the correct type, this is handled by the fallthrough block below break; } default: ; } // none of the ast types that return the value directly matched, we need to perform type conversion switch (_rettype) { case xpath_type_boolean: return xpath_string::from_const(eval_boolean(c, stack) ? PUGIXML_TEXT("true") : PUGIXML_TEXT("false")); case xpath_type_number: return convert_number_to_string(eval_number(c, stack), stack.result); case xpath_type_node_set: { xpath_allocator_capture cr(stack.temp); xpath_stack swapped_stack = {stack.temp, stack.result}; xpath_node_set_raw ns = eval_node_set(c, swapped_stack, nodeset_eval_first); return ns.empty() ? xpath_string() : string_value(ns.first(), stack.result); } default: assert(false && "Wrong expression for return type string"); // unreachable return xpath_string(); } } xpath_node_set_raw eval_node_set(const xpath_context& c, const xpath_stack& stack, nodeset_eval_t eval) { switch (_type) { case ast_op_union: { xpath_allocator_capture cr(stack.temp); xpath_stack swapped_stack = {stack.temp, stack.result}; xpath_node_set_raw ls = _left->eval_node_set(c, stack, eval); xpath_node_set_raw rs = _right->eval_node_set(c, swapped_stack, eval); // we can optimize merging two sorted sets, but this is a very rare operation, so don't bother ls.set_type(xpath_node_set::type_unsorted); ls.append(rs.begin(), rs.end(), stack.result); ls.remove_duplicates(stack.temp); return ls; } case ast_filter: { xpath_node_set_raw set = _left->eval_node_set(c, stack, _test == predicate_constant_one ? nodeset_eval_first : nodeset_eval_all); // either expression is a number or it contains position() call; sort by document order if (_test != predicate_posinv) set.sort_do(); bool once = eval_once(set.type(), eval); apply_predicate(set, 0, stack, once); return set; } case ast_func_id: return xpath_node_set_raw(); case ast_step: { switch (_axis) { case axis_ancestor: return step_do(c, stack, eval, axis_to_type()); case axis_ancestor_or_self: return step_do(c, stack, eval, axis_to_type()); case axis_attribute: return step_do(c, stack, eval, axis_to_type()); case axis_child: return step_do(c, stack, eval, axis_to_type()); case axis_descendant: return step_do(c, stack, eval, axis_to_type()); case axis_descendant_or_self: return step_do(c, stack, eval, axis_to_type()); case axis_following: return step_do(c, stack, eval, axis_to_type()); case axis_following_sibling: return step_do(c, stack, eval, axis_to_type()); case axis_namespace: // namespaced axis is not supported return xpath_node_set_raw(); case axis_parent: return step_do(c, stack, eval, axis_to_type()); case axis_preceding: return step_do(c, stack, eval, axis_to_type()); case axis_preceding_sibling: return step_do(c, stack, eval, axis_to_type()); case axis_self: return step_do(c, stack, eval, axis_to_type()); default: assert(false && "Unknown axis"); // unreachable return xpath_node_set_raw(); } } case ast_step_root: { assert(!_right); // root step can't have any predicates xpath_node_set_raw ns; ns.set_type(xpath_node_set::type_sorted); if (c.n.node()) ns.push_back(c.n.node().root(), stack.result); else if (c.n.attribute()) ns.push_back(c.n.parent().root(), stack.result); return ns; } case ast_variable: { assert(_rettype == _data.variable->type()); if (_rettype == xpath_type_node_set) { const xpath_node_set& s = _data.variable->get_node_set(); xpath_node_set_raw ns; ns.set_type(s.type()); ns.append(s.begin(), s.end(), stack.result); return ns; } // variable needs to be converted to the correct type, this is handled by the fallthrough block below break; } default: ; } // none of the ast types that return the value directly matched, but conversions to node set are invalid assert(false && "Wrong expression for return type node set"); // unreachable return xpath_node_set_raw(); } void optimize(xpath_allocator* alloc) { if (_left) _left->optimize(alloc); if (_right) _right->optimize(alloc); if (_next) _next->optimize(alloc); // coverity[var_deref_model] optimize_self(alloc); } void optimize_self(xpath_allocator* alloc) { // Rewrite [position()=expr] with [expr] // Note that this step has to go before classification to recognize [position()=1] if ((_type == ast_filter || _type == ast_predicate) && _right && // workaround for clang static analyzer (_right is never null for ast_filter/ast_predicate) _right->_type == ast_op_equal && _right->_left->_type == ast_func_position && _right->_right->_rettype == xpath_type_number) { _right = _right->_right; } // Classify filter/predicate ops to perform various optimizations during evaluation if ((_type == ast_filter || _type == ast_predicate) && _right) // workaround for clang static analyzer (_right is never null for ast_filter/ast_predicate) { assert(_test == predicate_default); if (_right->_type == ast_number_constant && _right->_data.number == 1.0) _test = predicate_constant_one; else if (_right->_rettype == xpath_type_number && (_right->_type == ast_number_constant || _right->_type == ast_variable || _right->_type == ast_func_last)) _test = predicate_constant; else if (_right->_rettype != xpath_type_number && _right->is_posinv_expr()) _test = predicate_posinv; } // Rewrite descendant-or-self::node()/child::foo with descendant::foo // The former is a full form of //foo, the latter is much faster since it executes the node test immediately // Do a similar kind of rewrite for self/descendant/descendant-or-self axes // Note that we only rewrite positionally invariant steps (//foo[1] != /descendant::foo[1]) if (_type == ast_step && (_axis == axis_child || _axis == axis_self || _axis == axis_descendant || _axis == axis_descendant_or_self) && _left && _left->_type == ast_step && _left->_axis == axis_descendant_or_self && _left->_test == nodetest_type_node && !_left->_right && is_posinv_step()) { if (_axis == axis_child || _axis == axis_descendant) _axis = axis_descendant; else _axis = axis_descendant_or_self; _left = _left->_left; } // Use optimized lookup table implementation for translate() with constant arguments if (_type == ast_func_translate && _right && // workaround for clang static analyzer (_right is never null for ast_func_translate) _right->_type == ast_string_constant && _right->_next->_type == ast_string_constant) { unsigned char* table = translate_table_generate(alloc, _right->_data.string, _right->_next->_data.string); if (table) { _type = ast_opt_translate_table; _data.table = table; } } // Use optimized path for @attr = 'value' or @attr = $value if (_type == ast_op_equal && _left && _right && // workaround for clang static analyzer and Coverity (_left and _right are never null for ast_op_equal) // coverity[mixed_enums] _left->_type == ast_step && _left->_axis == axis_attribute && _left->_test == nodetest_name && !_left->_left && !_left->_right && (_right->_type == ast_string_constant || (_right->_type == ast_variable && _right->_rettype == xpath_type_string))) { _type = ast_opt_compare_attribute; } } bool is_posinv_expr() const { switch (_type) { case ast_func_position: case ast_func_last: return false; case ast_string_constant: case ast_number_constant: case ast_variable: return true; case ast_step: case ast_step_root: return true; case ast_predicate: case ast_filter: return true; default: if (_left && !_left->is_posinv_expr()) return false; for (xpath_ast_node* n = _right; n; n = n->_next) if (!n->is_posinv_expr()) return false; return true; } } bool is_posinv_step() const { assert(_type == ast_step); for (xpath_ast_node* n = _right; n; n = n->_next) { assert(n->_type == ast_predicate); if (n->_test != predicate_posinv) return false; } return true; } xpath_value_type rettype() const { return static_cast(_rettype); } }; static const size_t xpath_ast_depth_limit = #ifdef PUGIXML_XPATH_DEPTH_LIMIT PUGIXML_XPATH_DEPTH_LIMIT #else 1024 #endif ; struct xpath_parser { xpath_allocator* _alloc; xpath_lexer _lexer; const char_t* _query; xpath_variable_set* _variables; xpath_parse_result* _result; char_t _scratch[32]; size_t _depth; xpath_ast_node* error(const char* message) { _result->error = message; _result->offset = _lexer.current_pos() - _query; return 0; } xpath_ast_node* error_oom() { assert(_alloc->_error); *_alloc->_error = true; return 0; } xpath_ast_node* error_rec() { return error("Exceeded maximum allowed query depth"); } void* alloc_node() { return _alloc->allocate(sizeof(xpath_ast_node)); } xpath_ast_node* alloc_node(ast_type_t type, xpath_value_type rettype, const char_t* value) { void* memory = alloc_node(); return memory ? new (memory) xpath_ast_node(type, rettype, value) : 0; } xpath_ast_node* alloc_node(ast_type_t type, xpath_value_type rettype, double value) { void* memory = alloc_node(); return memory ? new (memory) xpath_ast_node(type, rettype, value) : 0; } xpath_ast_node* alloc_node(ast_type_t type, xpath_value_type rettype, xpath_variable* value) { void* memory = alloc_node(); return memory ? new (memory) xpath_ast_node(type, rettype, value) : 0; } xpath_ast_node* alloc_node(ast_type_t type, xpath_value_type rettype, xpath_ast_node* left = 0, xpath_ast_node* right = 0) { void* memory = alloc_node(); return memory ? new (memory) xpath_ast_node(type, rettype, left, right) : 0; } xpath_ast_node* alloc_node(ast_type_t type, xpath_ast_node* left, axis_t axis, nodetest_t test, const char_t* contents) { void* memory = alloc_node(); return memory ? new (memory) xpath_ast_node(type, left, axis, test, contents) : 0; } xpath_ast_node* alloc_node(ast_type_t type, xpath_ast_node* left, xpath_ast_node* right, predicate_t test) { void* memory = alloc_node(); return memory ? new (memory) xpath_ast_node(type, left, right, test) : 0; } const char_t* alloc_string(const xpath_lexer_string& value) { if (!value.begin) return PUGIXML_TEXT(""); size_t length = static_cast(value.end - value.begin); char_t* c = static_cast(_alloc->allocate((length + 1) * sizeof(char_t))); if (!c) return 0; memcpy(c, value.begin, length * sizeof(char_t)); c[length] = 0; return c; } xpath_ast_node* parse_function(const xpath_lexer_string& name, size_t argc, xpath_ast_node* args[2]) { switch (name.begin[0]) { case 'b': if (name == PUGIXML_TEXT("boolean") && argc == 1) return alloc_node(ast_func_boolean, xpath_type_boolean, args[0]); break; case 'c': if (name == PUGIXML_TEXT("count") && argc == 1) { if (args[0]->rettype() != xpath_type_node_set) return error("Function has to be applied to node set"); return alloc_node(ast_func_count, xpath_type_number, args[0]); } else if (name == PUGIXML_TEXT("contains") && argc == 2) return alloc_node(ast_func_contains, xpath_type_boolean, args[0], args[1]); else if (name == PUGIXML_TEXT("concat") && argc >= 2) return alloc_node(ast_func_concat, xpath_type_string, args[0], args[1]); else if (name == PUGIXML_TEXT("ceiling") && argc == 1) return alloc_node(ast_func_ceiling, xpath_type_number, args[0]); break; case 'f': if (name == PUGIXML_TEXT("false") && argc == 0) return alloc_node(ast_func_false, xpath_type_boolean); else if (name == PUGIXML_TEXT("floor") && argc == 1) return alloc_node(ast_func_floor, xpath_type_number, args[0]); break; case 'i': if (name == PUGIXML_TEXT("id") && argc == 1) return alloc_node(ast_func_id, xpath_type_node_set, args[0]); break; case 'l': if (name == PUGIXML_TEXT("last") && argc == 0) return alloc_node(ast_func_last, xpath_type_number); else if (name == PUGIXML_TEXT("lang") && argc == 1) return alloc_node(ast_func_lang, xpath_type_boolean, args[0]); else if (name == PUGIXML_TEXT("local-name") && argc <= 1) { if (argc == 1 && args[0]->rettype() != xpath_type_node_set) return error("Function has to be applied to node set"); return alloc_node(argc == 0 ? ast_func_local_name_0 : ast_func_local_name_1, xpath_type_string, args[0]); } break; case 'n': if (name == PUGIXML_TEXT("name") && argc <= 1) { if (argc == 1 && args[0]->rettype() != xpath_type_node_set) return error("Function has to be applied to node set"); return alloc_node(argc == 0 ? ast_func_name_0 : ast_func_name_1, xpath_type_string, args[0]); } else if (name == PUGIXML_TEXT("namespace-uri") && argc <= 1) { if (argc == 1 && args[0]->rettype() != xpath_type_node_set) return error("Function has to be applied to node set"); return alloc_node(argc == 0 ? ast_func_namespace_uri_0 : ast_func_namespace_uri_1, xpath_type_string, args[0]); } else if (name == PUGIXML_TEXT("normalize-space") && argc <= 1) return alloc_node(argc == 0 ? ast_func_normalize_space_0 : ast_func_normalize_space_1, xpath_type_string, args[0], args[1]); else if (name == PUGIXML_TEXT("not") && argc == 1) return alloc_node(ast_func_not, xpath_type_boolean, args[0]); else if (name == PUGIXML_TEXT("number") && argc <= 1) return alloc_node(argc == 0 ? ast_func_number_0 : ast_func_number_1, xpath_type_number, args[0]); break; case 'p': if (name == PUGIXML_TEXT("position") && argc == 0) return alloc_node(ast_func_position, xpath_type_number); break; case 'r': if (name == PUGIXML_TEXT("round") && argc == 1) return alloc_node(ast_func_round, xpath_type_number, args[0]); break; case 's': if (name == PUGIXML_TEXT("string") && argc <= 1) return alloc_node(argc == 0 ? ast_func_string_0 : ast_func_string_1, xpath_type_string, args[0]); else if (name == PUGIXML_TEXT("string-length") && argc <= 1) return alloc_node(argc == 0 ? ast_func_string_length_0 : ast_func_string_length_1, xpath_type_number, args[0]); else if (name == PUGIXML_TEXT("starts-with") && argc == 2) return alloc_node(ast_func_starts_with, xpath_type_boolean, args[0], args[1]); else if (name == PUGIXML_TEXT("substring-before") && argc == 2) return alloc_node(ast_func_substring_before, xpath_type_string, args[0], args[1]); else if (name == PUGIXML_TEXT("substring-after") && argc == 2) return alloc_node(ast_func_substring_after, xpath_type_string, args[0], args[1]); else if (name == PUGIXML_TEXT("substring") && (argc == 2 || argc == 3)) return alloc_node(argc == 2 ? ast_func_substring_2 : ast_func_substring_3, xpath_type_string, args[0], args[1]); else if (name == PUGIXML_TEXT("sum") && argc == 1) { if (args[0]->rettype() != xpath_type_node_set) return error("Function has to be applied to node set"); return alloc_node(ast_func_sum, xpath_type_number, args[0]); } break; case 't': if (name == PUGIXML_TEXT("translate") && argc == 3) return alloc_node(ast_func_translate, xpath_type_string, args[0], args[1]); else if (name == PUGIXML_TEXT("true") && argc == 0) return alloc_node(ast_func_true, xpath_type_boolean); break; default: break; } return error("Unrecognized function or wrong parameter count"); } axis_t parse_axis_name(const xpath_lexer_string& name, bool& specified) { specified = true; switch (name.begin[0]) { case 'a': if (name == PUGIXML_TEXT("ancestor")) return axis_ancestor; else if (name == PUGIXML_TEXT("ancestor-or-self")) return axis_ancestor_or_self; else if (name == PUGIXML_TEXT("attribute")) return axis_attribute; break; case 'c': if (name == PUGIXML_TEXT("child")) return axis_child; break; case 'd': if (name == PUGIXML_TEXT("descendant")) return axis_descendant; else if (name == PUGIXML_TEXT("descendant-or-self")) return axis_descendant_or_self; break; case 'f': if (name == PUGIXML_TEXT("following")) return axis_following; else if (name == PUGIXML_TEXT("following-sibling")) return axis_following_sibling; break; case 'n': if (name == PUGIXML_TEXT("namespace")) return axis_namespace; break; case 'p': if (name == PUGIXML_TEXT("parent")) return axis_parent; else if (name == PUGIXML_TEXT("preceding")) return axis_preceding; else if (name == PUGIXML_TEXT("preceding-sibling")) return axis_preceding_sibling; break; case 's': if (name == PUGIXML_TEXT("self")) return axis_self; break; default: break; } specified = false; return axis_child; } nodetest_t parse_node_test_type(const xpath_lexer_string& name) { switch (name.begin[0]) { case 'c': if (name == PUGIXML_TEXT("comment")) return nodetest_type_comment; break; case 'n': if (name == PUGIXML_TEXT("node")) return nodetest_type_node; break; case 'p': if (name == PUGIXML_TEXT("processing-instruction")) return nodetest_type_pi; break; case 't': if (name == PUGIXML_TEXT("text")) return nodetest_type_text; break; default: break; } return nodetest_none; } // PrimaryExpr ::= VariableReference | '(' Expr ')' | Literal | Number | FunctionCall xpath_ast_node* parse_primary_expression() { switch (_lexer.current()) { case lex_var_ref: { xpath_lexer_string name = _lexer.contents(); if (!_variables) return error("Unknown variable: variable set is not provided"); xpath_variable* var = 0; if (!get_variable_scratch(_scratch, _variables, name.begin, name.end, &var)) return error_oom(); if (!var) return error("Unknown variable: variable set does not contain the given name"); _lexer.next(); return alloc_node(ast_variable, var->type(), var); } case lex_open_brace: { _lexer.next(); xpath_ast_node* n = parse_expression(); if (!n) return 0; if (_lexer.current() != lex_close_brace) return error("Expected ')' to match an opening '('"); _lexer.next(); return n; } case lex_quoted_string: { const char_t* value = alloc_string(_lexer.contents()); if (!value) return 0; _lexer.next(); return alloc_node(ast_string_constant, xpath_type_string, value); } case lex_number: { double value = 0; if (!convert_string_to_number_scratch(_scratch, _lexer.contents().begin, _lexer.contents().end, &value)) return error_oom(); _lexer.next(); return alloc_node(ast_number_constant, xpath_type_number, value); } case lex_string: { xpath_ast_node* args[2] = {0}; size_t argc = 0; xpath_lexer_string function = _lexer.contents(); _lexer.next(); xpath_ast_node* last_arg = 0; if (_lexer.current() != lex_open_brace) return error("Unrecognized function call"); _lexer.next(); size_t old_depth = _depth; while (_lexer.current() != lex_close_brace) { if (argc > 0) { if (_lexer.current() != lex_comma) return error("No comma between function arguments"); _lexer.next(); } if (++_depth > xpath_ast_depth_limit) return error_rec(); xpath_ast_node* n = parse_expression(); if (!n) return 0; if (argc < 2) args[argc] = n; else last_arg->set_next(n); argc++; last_arg = n; } _lexer.next(); _depth = old_depth; return parse_function(function, argc, args); } default: return error("Unrecognizable primary expression"); } } // FilterExpr ::= PrimaryExpr | FilterExpr Predicate // Predicate ::= '[' PredicateExpr ']' // PredicateExpr ::= Expr xpath_ast_node* parse_filter_expression() { xpath_ast_node* n = parse_primary_expression(); if (!n) return 0; size_t old_depth = _depth; while (_lexer.current() == lex_open_square_brace) { _lexer.next(); if (++_depth > xpath_ast_depth_limit) return error_rec(); if (n->rettype() != xpath_type_node_set) return error("Predicate has to be applied to node set"); xpath_ast_node* expr = parse_expression(); if (!expr) return 0; n = alloc_node(ast_filter, n, expr, predicate_default); if (!n) return 0; if (_lexer.current() != lex_close_square_brace) return error("Expected ']' to match an opening '['"); _lexer.next(); } _depth = old_depth; return n; } // Step ::= AxisSpecifier NodeTest Predicate* | AbbreviatedStep // AxisSpecifier ::= AxisName '::' | '@'? // NodeTest ::= NameTest | NodeType '(' ')' | 'processing-instruction' '(' Literal ')' // NameTest ::= '*' | NCName ':' '*' | QName // AbbreviatedStep ::= '.' | '..' xpath_ast_node* parse_step(xpath_ast_node* set) { if (set && set->rettype() != xpath_type_node_set) return error("Step has to be applied to node set"); bool axis_specified = false; axis_t axis = axis_child; // implied child axis if (_lexer.current() == lex_axis_attribute) { axis = axis_attribute; axis_specified = true; _lexer.next(); } else if (_lexer.current() == lex_dot) { _lexer.next(); if (_lexer.current() == lex_open_square_brace) return error("Predicates are not allowed after an abbreviated step"); return alloc_node(ast_step, set, axis_self, nodetest_type_node, 0); } else if (_lexer.current() == lex_double_dot) { _lexer.next(); if (_lexer.current() == lex_open_square_brace) return error("Predicates are not allowed after an abbreviated step"); return alloc_node(ast_step, set, axis_parent, nodetest_type_node, 0); } nodetest_t nt_type = nodetest_none; xpath_lexer_string nt_name; if (_lexer.current() == lex_string) { // node name test nt_name = _lexer.contents(); _lexer.next(); // was it an axis name? if (_lexer.current() == lex_double_colon) { // parse axis name if (axis_specified) return error("Two axis specifiers in one step"); axis = parse_axis_name(nt_name, axis_specified); if (!axis_specified) return error("Unknown axis"); // read actual node test _lexer.next(); if (_lexer.current() == lex_multiply) { nt_type = nodetest_all; nt_name = xpath_lexer_string(); _lexer.next(); } else if (_lexer.current() == lex_string) { nt_name = _lexer.contents(); _lexer.next(); } else { return error("Unrecognized node test"); } } if (nt_type == nodetest_none) { // node type test or processing-instruction if (_lexer.current() == lex_open_brace) { _lexer.next(); if (_lexer.current() == lex_close_brace) { _lexer.next(); nt_type = parse_node_test_type(nt_name); if (nt_type == nodetest_none) return error("Unrecognized node type"); nt_name = xpath_lexer_string(); } else if (nt_name == PUGIXML_TEXT("processing-instruction")) { if (_lexer.current() != lex_quoted_string) return error("Only literals are allowed as arguments to processing-instruction()"); nt_type = nodetest_pi; nt_name = _lexer.contents(); _lexer.next(); if (_lexer.current() != lex_close_brace) return error("Unmatched brace near processing-instruction()"); _lexer.next(); } else { return error("Unmatched brace near node type test"); } } // QName or NCName:* else { if (nt_name.end - nt_name.begin > 2 && nt_name.end[-2] == ':' && nt_name.end[-1] == '*') // NCName:* { nt_name.end--; // erase * nt_type = nodetest_all_in_namespace; } else { nt_type = nodetest_name; } } } } else if (_lexer.current() == lex_multiply) { nt_type = nodetest_all; _lexer.next(); } else { return error("Unrecognized node test"); } const char_t* nt_name_copy = alloc_string(nt_name); if (!nt_name_copy) return 0; xpath_ast_node* n = alloc_node(ast_step, set, axis, nt_type, nt_name_copy); if (!n) return 0; size_t old_depth = _depth; xpath_ast_node* last = 0; while (_lexer.current() == lex_open_square_brace) { _lexer.next(); if (++_depth > xpath_ast_depth_limit) return error_rec(); xpath_ast_node* expr = parse_expression(); if (!expr) return 0; xpath_ast_node* pred = alloc_node(ast_predicate, 0, expr, predicate_default); if (!pred) return 0; if (_lexer.current() != lex_close_square_brace) return error("Expected ']' to match an opening '['"); _lexer.next(); if (last) last->set_next(pred); else n->set_right(pred); last = pred; } _depth = old_depth; return n; } // RelativeLocationPath ::= Step | RelativeLocationPath '/' Step | RelativeLocationPath '//' Step xpath_ast_node* parse_relative_location_path(xpath_ast_node* set) { xpath_ast_node* n = parse_step(set); if (!n) return 0; size_t old_depth = _depth; while (_lexer.current() == lex_slash || _lexer.current() == lex_double_slash) { lexeme_t l = _lexer.current(); _lexer.next(); if (l == lex_double_slash) { n = alloc_node(ast_step, n, axis_descendant_or_self, nodetest_type_node, 0); if (!n) return 0; ++_depth; } if (++_depth > xpath_ast_depth_limit) return error_rec(); n = parse_step(n); if (!n) return 0; } _depth = old_depth; return n; } // LocationPath ::= RelativeLocationPath | AbsoluteLocationPath // AbsoluteLocationPath ::= '/' RelativeLocationPath? | '//' RelativeLocationPath xpath_ast_node* parse_location_path() { if (_lexer.current() == lex_slash) { _lexer.next(); xpath_ast_node* n = alloc_node(ast_step_root, xpath_type_node_set); if (!n) return 0; // relative location path can start from axis_attribute, dot, double_dot, multiply and string lexemes; any other lexeme means standalone root path lexeme_t l = _lexer.current(); if (l == lex_string || l == lex_axis_attribute || l == lex_dot || l == lex_double_dot || l == lex_multiply) return parse_relative_location_path(n); else return n; } else if (_lexer.current() == lex_double_slash) { _lexer.next(); xpath_ast_node* n = alloc_node(ast_step_root, xpath_type_node_set); if (!n) return 0; n = alloc_node(ast_step, n, axis_descendant_or_self, nodetest_type_node, 0); if (!n) return 0; return parse_relative_location_path(n); } // else clause moved outside of if because of bogus warning 'control may reach end of non-void function being inlined' in gcc 4.0.1 return parse_relative_location_path(0); } // PathExpr ::= LocationPath // | FilterExpr // | FilterExpr '/' RelativeLocationPath // | FilterExpr '//' RelativeLocationPath // UnionExpr ::= PathExpr | UnionExpr '|' PathExpr // UnaryExpr ::= UnionExpr | '-' UnaryExpr xpath_ast_node* parse_path_or_unary_expression() { // Clarification. // PathExpr begins with either LocationPath or FilterExpr. // FilterExpr begins with PrimaryExpr // PrimaryExpr begins with '$' in case of it being a variable reference, // '(' in case of it being an expression, string literal, number constant or // function call. if (_lexer.current() == lex_var_ref || _lexer.current() == lex_open_brace || _lexer.current() == lex_quoted_string || _lexer.current() == lex_number || _lexer.current() == lex_string) { if (_lexer.current() == lex_string) { // This is either a function call, or not - if not, we shall proceed with location path const char_t* state = _lexer.state(); while (PUGI_IMPL_IS_CHARTYPE(*state, ct_space)) ++state; if (*state != '(') return parse_location_path(); // This looks like a function call; however this still can be a node-test. Check it. if (parse_node_test_type(_lexer.contents()) != nodetest_none) return parse_location_path(); } xpath_ast_node* n = parse_filter_expression(); if (!n) return 0; if (_lexer.current() == lex_slash || _lexer.current() == lex_double_slash) { lexeme_t l = _lexer.current(); _lexer.next(); if (l == lex_double_slash) { if (n->rettype() != xpath_type_node_set) return error("Step has to be applied to node set"); n = alloc_node(ast_step, n, axis_descendant_or_self, nodetest_type_node, 0); if (!n) return 0; } // select from location path return parse_relative_location_path(n); } return n; } else if (_lexer.current() == lex_minus) { _lexer.next(); // precedence 7+ - only parses union expressions xpath_ast_node* n = parse_expression(7); if (!n) return 0; return alloc_node(ast_op_negate, xpath_type_number, n); } else { return parse_location_path(); } } struct binary_op_t { ast_type_t asttype; xpath_value_type rettype; int precedence; binary_op_t(): asttype(ast_unknown), rettype(xpath_type_none), precedence(0) { } binary_op_t(ast_type_t asttype_, xpath_value_type rettype_, int precedence_): asttype(asttype_), rettype(rettype_), precedence(precedence_) { } static binary_op_t parse(xpath_lexer& lexer) { switch (lexer.current()) { case lex_string: if (lexer.contents() == PUGIXML_TEXT("or")) return binary_op_t(ast_op_or, xpath_type_boolean, 1); else if (lexer.contents() == PUGIXML_TEXT("and")) return binary_op_t(ast_op_and, xpath_type_boolean, 2); else if (lexer.contents() == PUGIXML_TEXT("div")) return binary_op_t(ast_op_divide, xpath_type_number, 6); else if (lexer.contents() == PUGIXML_TEXT("mod")) return binary_op_t(ast_op_mod, xpath_type_number, 6); else return binary_op_t(); case lex_equal: return binary_op_t(ast_op_equal, xpath_type_boolean, 3); case lex_not_equal: return binary_op_t(ast_op_not_equal, xpath_type_boolean, 3); case lex_less: return binary_op_t(ast_op_less, xpath_type_boolean, 4); case lex_greater: return binary_op_t(ast_op_greater, xpath_type_boolean, 4); case lex_less_or_equal: return binary_op_t(ast_op_less_or_equal, xpath_type_boolean, 4); case lex_greater_or_equal: return binary_op_t(ast_op_greater_or_equal, xpath_type_boolean, 4); case lex_plus: return binary_op_t(ast_op_add, xpath_type_number, 5); case lex_minus: return binary_op_t(ast_op_subtract, xpath_type_number, 5); case lex_multiply: return binary_op_t(ast_op_multiply, xpath_type_number, 6); case lex_union: return binary_op_t(ast_op_union, xpath_type_node_set, 7); default: return binary_op_t(); } } }; xpath_ast_node* parse_expression_rec(xpath_ast_node* lhs, int limit) { binary_op_t op = binary_op_t::parse(_lexer); while (op.asttype != ast_unknown && op.precedence >= limit) { _lexer.next(); if (++_depth > xpath_ast_depth_limit) return error_rec(); xpath_ast_node* rhs = parse_path_or_unary_expression(); if (!rhs) return 0; binary_op_t nextop = binary_op_t::parse(_lexer); while (nextop.asttype != ast_unknown && nextop.precedence > op.precedence) { rhs = parse_expression_rec(rhs, nextop.precedence); if (!rhs) return 0; nextop = binary_op_t::parse(_lexer); } if (op.asttype == ast_op_union && (lhs->rettype() != xpath_type_node_set || rhs->rettype() != xpath_type_node_set)) return error("Union operator has to be applied to node sets"); lhs = alloc_node(op.asttype, op.rettype, lhs, rhs); if (!lhs) return 0; op = binary_op_t::parse(_lexer); } return lhs; } // Expr ::= OrExpr // OrExpr ::= AndExpr | OrExpr 'or' AndExpr // AndExpr ::= EqualityExpr | AndExpr 'and' EqualityExpr // EqualityExpr ::= RelationalExpr // | EqualityExpr '=' RelationalExpr // | EqualityExpr '!=' RelationalExpr // RelationalExpr ::= AdditiveExpr // | RelationalExpr '<' AdditiveExpr // | RelationalExpr '>' AdditiveExpr // | RelationalExpr '<=' AdditiveExpr // | RelationalExpr '>=' AdditiveExpr // AdditiveExpr ::= MultiplicativeExpr // | AdditiveExpr '+' MultiplicativeExpr // | AdditiveExpr '-' MultiplicativeExpr // MultiplicativeExpr ::= UnaryExpr // | MultiplicativeExpr '*' UnaryExpr // | MultiplicativeExpr 'div' UnaryExpr // | MultiplicativeExpr 'mod' UnaryExpr xpath_ast_node* parse_expression(int limit = 0) { size_t old_depth = _depth; if (++_depth > xpath_ast_depth_limit) return error_rec(); xpath_ast_node* n = parse_path_or_unary_expression(); if (!n) return 0; n = parse_expression_rec(n, limit); _depth = old_depth; return n; } xpath_parser(const char_t* query, xpath_variable_set* variables, xpath_allocator* alloc, xpath_parse_result* result): _alloc(alloc), _lexer(query), _query(query), _variables(variables), _result(result), _depth(0) { } xpath_ast_node* parse() { xpath_ast_node* n = parse_expression(); if (!n) return 0; assert(_depth == 0); // check if there are unparsed tokens left if (_lexer.current() != lex_eof) return error("Incorrect query"); return n; } static xpath_ast_node* parse(const char_t* query, xpath_variable_set* variables, xpath_allocator* alloc, xpath_parse_result* result) { xpath_parser parser(query, variables, alloc, result); return parser.parse(); } }; struct xpath_query_impl { static xpath_query_impl* create() { void* memory = xml_memory::allocate(sizeof(xpath_query_impl)); if (!memory) return 0; return new (memory) xpath_query_impl(); } static void destroy(xpath_query_impl* impl) { // free all allocated pages impl->alloc.release(); // free allocator memory (with the first page) xml_memory::deallocate(impl); } xpath_query_impl(): root(0), alloc(&block, &oom), oom(false) { block.next = 0; block.capacity = sizeof(block.data); } xpath_ast_node* root; xpath_allocator alloc; xpath_memory_block block; bool oom; }; PUGI_IMPL_FN impl::xpath_ast_node* evaluate_node_set_prepare(xpath_query_impl* impl) { if (!impl) return 0; if (impl->root->rettype() != xpath_type_node_set) { #ifdef PUGIXML_NO_EXCEPTIONS return 0; #else xpath_parse_result res; res.error = "Expression does not evaluate to node set"; throw xpath_exception(res); #endif } return impl->root; } PUGI_IMPL_NS_END namespace pugi { #ifndef PUGIXML_NO_EXCEPTIONS PUGI_IMPL_FN xpath_exception::xpath_exception(const xpath_parse_result& result_): _result(result_) { assert(_result.error); } PUGI_IMPL_FN const char* xpath_exception::what() const throw() { return _result.error; } PUGI_IMPL_FN const xpath_parse_result& xpath_exception::result() const { return _result; } #endif PUGI_IMPL_FN xpath_node::xpath_node() { } PUGI_IMPL_FN xpath_node::xpath_node(const xml_node& node_): _node(node_) { } PUGI_IMPL_FN xpath_node::xpath_node(const xml_attribute& attribute_, const xml_node& parent_): _node(attribute_ ? parent_ : xml_node()), _attribute(attribute_) { } PUGI_IMPL_FN xml_node xpath_node::node() const { return _attribute ? xml_node() : _node; } PUGI_IMPL_FN xml_attribute xpath_node::attribute() const { return _attribute; } PUGI_IMPL_FN xml_node xpath_node::parent() const { return _attribute ? _node : _node.parent(); } PUGI_IMPL_FN static void unspecified_bool_xpath_node(xpath_node***) { } PUGI_IMPL_FN xpath_node::operator xpath_node::unspecified_bool_type() const { return (_node || _attribute) ? unspecified_bool_xpath_node : 0; } PUGI_IMPL_FN bool xpath_node::operator!() const { return !(_node || _attribute); } PUGI_IMPL_FN bool xpath_node::operator==(const xpath_node& n) const { return _node == n._node && _attribute == n._attribute; } PUGI_IMPL_FN bool xpath_node::operator!=(const xpath_node& n) const { return _node != n._node || _attribute != n._attribute; } #ifdef __BORLANDC__ PUGI_IMPL_FN bool operator&&(const xpath_node& lhs, bool rhs) { return (bool)lhs && rhs; } PUGI_IMPL_FN bool operator||(const xpath_node& lhs, bool rhs) { return (bool)lhs || rhs; } #endif PUGI_IMPL_FN void xpath_node_set::_assign(const_iterator begin_, const_iterator end_, type_t type_) { assert(begin_ <= end_); size_t size_ = static_cast(end_ - begin_); // use internal buffer for 0 or 1 elements, heap buffer otherwise xpath_node* storage = (size_ <= 1) ? _storage : static_cast(impl::xml_memory::allocate(size_ * sizeof(xpath_node))); if (!storage) { #ifdef PUGIXML_NO_EXCEPTIONS return; #else throw std::bad_alloc(); #endif } // deallocate old buffer if (_begin != _storage) impl::xml_memory::deallocate(_begin); // size check is necessary because for begin_ = end_ = nullptr, memcpy is UB if (size_) memcpy(storage, begin_, size_ * sizeof(xpath_node)); _begin = storage; _end = storage + size_; _type = type_; } #ifdef PUGIXML_HAS_MOVE PUGI_IMPL_FN void xpath_node_set::_move(xpath_node_set& rhs) PUGIXML_NOEXCEPT { _type = rhs._type; _storage[0] = rhs._storage[0]; _begin = (rhs._begin == rhs._storage) ? _storage : rhs._begin; _end = _begin + (rhs._end - rhs._begin); rhs._type = type_unsorted; rhs._begin = rhs._storage; rhs._end = rhs._storage; } #endif PUGI_IMPL_FN xpath_node_set::xpath_node_set(): _type(type_unsorted), _begin(_storage), _end(_storage) { } PUGI_IMPL_FN xpath_node_set::xpath_node_set(const_iterator begin_, const_iterator end_, type_t type_): _type(type_unsorted), _begin(_storage), _end(_storage) { _assign(begin_, end_, type_); } PUGI_IMPL_FN xpath_node_set::~xpath_node_set() { if (_begin != _storage) impl::xml_memory::deallocate(_begin); } PUGI_IMPL_FN xpath_node_set::xpath_node_set(const xpath_node_set& ns): _type(type_unsorted), _begin(_storage), _end(_storage) { _assign(ns._begin, ns._end, ns._type); } PUGI_IMPL_FN xpath_node_set& xpath_node_set::operator=(const xpath_node_set& ns) { if (this == &ns) return *this; _assign(ns._begin, ns._end, ns._type); return *this; } #ifdef PUGIXML_HAS_MOVE PUGI_IMPL_FN xpath_node_set::xpath_node_set(xpath_node_set&& rhs) PUGIXML_NOEXCEPT: _type(type_unsorted), _begin(_storage), _end(_storage) { _move(rhs); } PUGI_IMPL_FN xpath_node_set& xpath_node_set::operator=(xpath_node_set&& rhs) PUGIXML_NOEXCEPT { if (this == &rhs) return *this; if (_begin != _storage) impl::xml_memory::deallocate(_begin); _move(rhs); return *this; } #endif PUGI_IMPL_FN xpath_node_set::type_t xpath_node_set::type() const { return _type; } PUGI_IMPL_FN size_t xpath_node_set::size() const { return _end - _begin; } PUGI_IMPL_FN bool xpath_node_set::empty() const { return _begin == _end; } PUGI_IMPL_FN const xpath_node& xpath_node_set::operator[](size_t index) const { assert(index < size()); return _begin[index]; } PUGI_IMPL_FN xpath_node_set::const_iterator xpath_node_set::begin() const { return _begin; } PUGI_IMPL_FN xpath_node_set::const_iterator xpath_node_set::end() const { return _end; } PUGI_IMPL_FN void xpath_node_set::sort(bool reverse) { _type = impl::xpath_sort(_begin, _end, _type, reverse); } PUGI_IMPL_FN xpath_node xpath_node_set::first() const { return impl::xpath_first(_begin, _end, _type); } PUGI_IMPL_FN xpath_parse_result::xpath_parse_result(): error("Internal error"), offset(0) { } PUGI_IMPL_FN xpath_parse_result::operator bool() const { return error == 0; } PUGI_IMPL_FN const char* xpath_parse_result::description() const { return error ? error : "No error"; } PUGI_IMPL_FN xpath_variable::xpath_variable(xpath_value_type type_): _type(type_), _next(0) { } PUGI_IMPL_FN const char_t* xpath_variable::name() const { switch (_type) { case xpath_type_node_set: return static_cast(this)->name; case xpath_type_number: return static_cast(this)->name; case xpath_type_string: return static_cast(this)->name; case xpath_type_boolean: return static_cast(this)->name; default: assert(false && "Invalid variable type"); // unreachable return 0; } } PUGI_IMPL_FN xpath_value_type xpath_variable::type() const { return _type; } PUGI_IMPL_FN bool xpath_variable::get_boolean() const { return (_type == xpath_type_boolean) ? static_cast(this)->value : false; } PUGI_IMPL_FN double xpath_variable::get_number() const { return (_type == xpath_type_number) ? static_cast(this)->value : impl::gen_nan(); } PUGI_IMPL_FN const char_t* xpath_variable::get_string() const { const char_t* value = (_type == xpath_type_string) ? static_cast(this)->value : 0; return value ? value : PUGIXML_TEXT(""); } PUGI_IMPL_FN const xpath_node_set& xpath_variable::get_node_set() const { return (_type == xpath_type_node_set) ? static_cast(this)->value : impl::dummy_node_set; } PUGI_IMPL_FN bool xpath_variable::set(bool value) { if (_type != xpath_type_boolean) return false; static_cast(this)->value = value; return true; } PUGI_IMPL_FN bool xpath_variable::set(double value) { if (_type != xpath_type_number) return false; static_cast(this)->value = value; return true; } PUGI_IMPL_FN bool xpath_variable::set(const char_t* value) { if (_type != xpath_type_string) return false; impl::xpath_variable_string* var = static_cast(this); // duplicate string size_t size = (impl::strlength(value) + 1) * sizeof(char_t); char_t* copy = static_cast(impl::xml_memory::allocate(size)); if (!copy) return false; memcpy(copy, value, size); // replace old string if (var->value) impl::xml_memory::deallocate(var->value); var->value = copy; return true; } PUGI_IMPL_FN bool xpath_variable::set(const xpath_node_set& value) { if (_type != xpath_type_node_set) return false; static_cast(this)->value = value; return true; } PUGI_IMPL_FN xpath_variable_set::xpath_variable_set() { for (size_t i = 0; i < sizeof(_data) / sizeof(_data[0]); ++i) _data[i] = 0; } PUGI_IMPL_FN xpath_variable_set::~xpath_variable_set() { for (size_t i = 0; i < sizeof(_data) / sizeof(_data[0]); ++i) _destroy(_data[i]); } PUGI_IMPL_FN xpath_variable_set::xpath_variable_set(const xpath_variable_set& rhs) { for (size_t i = 0; i < sizeof(_data) / sizeof(_data[0]); ++i) _data[i] = 0; _assign(rhs); } PUGI_IMPL_FN xpath_variable_set& xpath_variable_set::operator=(const xpath_variable_set& rhs) { if (this == &rhs) return *this; _assign(rhs); return *this; } #ifdef PUGIXML_HAS_MOVE PUGI_IMPL_FN xpath_variable_set::xpath_variable_set(xpath_variable_set&& rhs) PUGIXML_NOEXCEPT { for (size_t i = 0; i < sizeof(_data) / sizeof(_data[0]); ++i) { _data[i] = rhs._data[i]; rhs._data[i] = 0; } } PUGI_IMPL_FN xpath_variable_set& xpath_variable_set::operator=(xpath_variable_set&& rhs) PUGIXML_NOEXCEPT { for (size_t i = 0; i < sizeof(_data) / sizeof(_data[0]); ++i) { _destroy(_data[i]); _data[i] = rhs._data[i]; rhs._data[i] = 0; } return *this; } #endif PUGI_IMPL_FN void xpath_variable_set::_assign(const xpath_variable_set& rhs) { xpath_variable_set temp; for (size_t i = 0; i < sizeof(_data) / sizeof(_data[0]); ++i) if (rhs._data[i] && !_clone(rhs._data[i], &temp._data[i])) return; _swap(temp); } PUGI_IMPL_FN void xpath_variable_set::_swap(xpath_variable_set& rhs) { for (size_t i = 0; i < sizeof(_data) / sizeof(_data[0]); ++i) { xpath_variable* chain = _data[i]; _data[i] = rhs._data[i]; rhs._data[i] = chain; } } PUGI_IMPL_FN xpath_variable* xpath_variable_set::_find(const char_t* name) const { const size_t hash_size = sizeof(_data) / sizeof(_data[0]); size_t hash = impl::hash_string(name) % hash_size; // look for existing variable for (xpath_variable* var = _data[hash]; var; var = var->_next) if (impl::strequal(var->name(), name)) return var; return 0; } PUGI_IMPL_FN bool xpath_variable_set::_clone(xpath_variable* var, xpath_variable** out_result) { xpath_variable* last = 0; while (var) { // allocate storage for new variable xpath_variable* nvar = impl::new_xpath_variable(var->_type, var->name()); if (!nvar) return false; // link the variable to the result immediately to handle failures gracefully if (last) last->_next = nvar; else *out_result = nvar; last = nvar; // copy the value; this can fail due to out-of-memory conditions if (!impl::copy_xpath_variable(nvar, var)) return false; var = var->_next; } return true; } PUGI_IMPL_FN void xpath_variable_set::_destroy(xpath_variable* var) { while (var) { xpath_variable* next = var->_next; impl::delete_xpath_variable(var->_type, var); var = next; } } PUGI_IMPL_FN xpath_variable* xpath_variable_set::add(const char_t* name, xpath_value_type type) { const size_t hash_size = sizeof(_data) / sizeof(_data[0]); size_t hash = impl::hash_string(name) % hash_size; // look for existing variable for (xpath_variable* var = _data[hash]; var; var = var->_next) if (impl::strequal(var->name(), name)) return var->type() == type ? var : 0; // add new variable xpath_variable* result = impl::new_xpath_variable(type, name); if (result) { result->_next = _data[hash]; _data[hash] = result; } return result; } PUGI_IMPL_FN bool xpath_variable_set::set(const char_t* name, bool value) { xpath_variable* var = add(name, xpath_type_boolean); return var ? var->set(value) : false; } PUGI_IMPL_FN bool xpath_variable_set::set(const char_t* name, double value) { xpath_variable* var = add(name, xpath_type_number); return var ? var->set(value) : false; } PUGI_IMPL_FN bool xpath_variable_set::set(const char_t* name, const char_t* value) { xpath_variable* var = add(name, xpath_type_string); return var ? var->set(value) : false; } PUGI_IMPL_FN bool xpath_variable_set::set(const char_t* name, const xpath_node_set& value) { xpath_variable* var = add(name, xpath_type_node_set); return var ? var->set(value) : false; } PUGI_IMPL_FN xpath_variable* xpath_variable_set::get(const char_t* name) { return _find(name); } PUGI_IMPL_FN const xpath_variable* xpath_variable_set::get(const char_t* name) const { return _find(name); } PUGI_IMPL_FN xpath_query::xpath_query(const char_t* query, xpath_variable_set* variables): _impl(0) { impl::xpath_query_impl* qimpl = impl::xpath_query_impl::create(); if (!qimpl) { #ifdef PUGIXML_NO_EXCEPTIONS _result.error = "Out of memory"; #else throw std::bad_alloc(); #endif } else { using impl::auto_deleter; // MSVC7 workaround auto_deleter impl(qimpl, impl::xpath_query_impl::destroy); qimpl->root = impl::xpath_parser::parse(query, variables, &qimpl->alloc, &_result); if (qimpl->root) { qimpl->root->optimize(&qimpl->alloc); _impl = impl.release(); _result.error = 0; } else { #ifdef PUGIXML_NO_EXCEPTIONS if (qimpl->oom) _result.error = "Out of memory"; #else if (qimpl->oom) throw std::bad_alloc(); throw xpath_exception(_result); #endif } } } PUGI_IMPL_FN xpath_query::xpath_query(): _impl(0) { } PUGI_IMPL_FN xpath_query::~xpath_query() { if (_impl) impl::xpath_query_impl::destroy(static_cast(_impl)); } #ifdef PUGIXML_HAS_MOVE PUGI_IMPL_FN xpath_query::xpath_query(xpath_query&& rhs) PUGIXML_NOEXCEPT { _impl = rhs._impl; _result = rhs._result; rhs._impl = 0; rhs._result = xpath_parse_result(); } PUGI_IMPL_FN xpath_query& xpath_query::operator=(xpath_query&& rhs) PUGIXML_NOEXCEPT { if (this == &rhs) return *this; if (_impl) impl::xpath_query_impl::destroy(static_cast(_impl)); _impl = rhs._impl; _result = rhs._result; rhs._impl = 0; rhs._result = xpath_parse_result(); return *this; } #endif PUGI_IMPL_FN xpath_value_type xpath_query::return_type() const { if (!_impl) return xpath_type_none; return static_cast(_impl)->root->rettype(); } PUGI_IMPL_FN bool xpath_query::evaluate_boolean(const xpath_node& n) const { if (!_impl) return false; impl::xpath_context c(n, 1, 1); impl::xpath_stack_data sd; bool r = static_cast(_impl)->root->eval_boolean(c, sd.stack); if (sd.oom) { #ifdef PUGIXML_NO_EXCEPTIONS return false; #else throw std::bad_alloc(); #endif } return r; } PUGI_IMPL_FN double xpath_query::evaluate_number(const xpath_node& n) const { if (!_impl) return impl::gen_nan(); impl::xpath_context c(n, 1, 1); impl::xpath_stack_data sd; double r = static_cast(_impl)->root->eval_number(c, sd.stack); if (sd.oom) { #ifdef PUGIXML_NO_EXCEPTIONS return impl::gen_nan(); #else throw std::bad_alloc(); #endif } return r; } #ifndef PUGIXML_NO_STL PUGI_IMPL_FN string_t xpath_query::evaluate_string(const xpath_node& n) const { if (!_impl) return string_t(); impl::xpath_context c(n, 1, 1); impl::xpath_stack_data sd; impl::xpath_string r = static_cast(_impl)->root->eval_string(c, sd.stack); if (sd.oom) { #ifdef PUGIXML_NO_EXCEPTIONS return string_t(); #else throw std::bad_alloc(); #endif } return string_t(r.c_str(), r.length()); } #endif PUGI_IMPL_FN size_t xpath_query::evaluate_string(char_t* buffer, size_t capacity, const xpath_node& n) const { impl::xpath_context c(n, 1, 1); impl::xpath_stack_data sd; impl::xpath_string r = _impl ? static_cast(_impl)->root->eval_string(c, sd.stack) : impl::xpath_string(); if (sd.oom) { #ifdef PUGIXML_NO_EXCEPTIONS r = impl::xpath_string(); #else throw std::bad_alloc(); #endif } size_t full_size = r.length() + 1; if (capacity > 0) { size_t size = (full_size < capacity) ? full_size : capacity; assert(size > 0); memcpy(buffer, r.c_str(), (size - 1) * sizeof(char_t)); buffer[size - 1] = 0; } return full_size; } PUGI_IMPL_FN xpath_node_set xpath_query::evaluate_node_set(const xpath_node& n) const { impl::xpath_ast_node* root = impl::evaluate_node_set_prepare(static_cast(_impl)); if (!root) return xpath_node_set(); impl::xpath_context c(n, 1, 1); impl::xpath_stack_data sd; impl::xpath_node_set_raw r = root->eval_node_set(c, sd.stack, impl::nodeset_eval_all); if (sd.oom) { #ifdef PUGIXML_NO_EXCEPTIONS return xpath_node_set(); #else throw std::bad_alloc(); #endif } return xpath_node_set(r.begin(), r.end(), r.type()); } PUGI_IMPL_FN xpath_node xpath_query::evaluate_node(const xpath_node& n) const { impl::xpath_ast_node* root = impl::evaluate_node_set_prepare(static_cast(_impl)); if (!root) return xpath_node(); impl::xpath_context c(n, 1, 1); impl::xpath_stack_data sd; impl::xpath_node_set_raw r = root->eval_node_set(c, sd.stack, impl::nodeset_eval_first); if (sd.oom) { #ifdef PUGIXML_NO_EXCEPTIONS return xpath_node(); #else throw std::bad_alloc(); #endif } return r.first(); } PUGI_IMPL_FN const xpath_parse_result& xpath_query::result() const { return _result; } PUGI_IMPL_FN static void unspecified_bool_xpath_query(xpath_query***) { } PUGI_IMPL_FN xpath_query::operator xpath_query::unspecified_bool_type() const { return _impl ? unspecified_bool_xpath_query : 0; } PUGI_IMPL_FN bool xpath_query::operator!() const { return !_impl; } PUGI_IMPL_FN xpath_node xml_node::select_node(const char_t* query, xpath_variable_set* variables) const { xpath_query q(query, variables); return q.evaluate_node(*this); } PUGI_IMPL_FN xpath_node xml_node::select_node(const xpath_query& query) const { return query.evaluate_node(*this); } PUGI_IMPL_FN xpath_node_set xml_node::select_nodes(const char_t* query, xpath_variable_set* variables) const { xpath_query q(query, variables); return q.evaluate_node_set(*this); } PUGI_IMPL_FN xpath_node_set xml_node::select_nodes(const xpath_query& query) const { return query.evaluate_node_set(*this); } PUGI_IMPL_FN xpath_node xml_node::select_single_node(const char_t* query, xpath_variable_set* variables) const { xpath_query q(query, variables); return q.evaluate_node(*this); } PUGI_IMPL_FN xpath_node xml_node::select_single_node(const xpath_query& query) const { return query.evaluate_node(*this); } } #endif #ifdef __BORLANDC__ # pragma option pop #endif // Intel C++ does not properly keep warning state for function templates, // so popping warning state at the end of translation unit leads to warnings in the middle. #if defined(_MSC_VER) && !defined(__INTEL_COMPILER) # pragma warning(pop) #endif #if defined(_MSC_VER) && defined(__c2__) # pragma clang diagnostic pop #endif // Undefine all local macros (makes sure we're not leaking macros in header-only mode) #undef PUGI_IMPL_NO_INLINE #undef PUGI_IMPL_UNLIKELY #undef PUGI_IMPL_STATIC_ASSERT #undef PUGI_IMPL_DMC_VOLATILE #undef PUGI_IMPL_UNSIGNED_OVERFLOW #undef PUGI_IMPL_MSVC_CRT_VERSION #undef PUGI_IMPL_SNPRINTF #undef PUGI_IMPL_NS_BEGIN #undef PUGI_IMPL_NS_END #undef PUGI_IMPL_FN #undef PUGI_IMPL_FN_NO_INLINE #undef PUGI_IMPL_GETHEADER_IMPL #undef PUGI_IMPL_GETPAGE_IMPL #undef PUGI_IMPL_GETPAGE #undef PUGI_IMPL_NODETYPE #undef PUGI_IMPL_IS_CHARTYPE_IMPL #undef PUGI_IMPL_IS_CHARTYPE #undef PUGI_IMPL_IS_CHARTYPEX #undef PUGI_IMPL_ENDSWITH #undef PUGI_IMPL_SKIPWS #undef PUGI_IMPL_OPTSET #undef PUGI_IMPL_PUSHNODE #undef PUGI_IMPL_POPNODE #undef PUGI_IMPL_SCANFOR #undef PUGI_IMPL_SCANWHILE #undef PUGI_IMPL_SCANWHILE_UNROLL #undef PUGI_IMPL_ENDSEG #undef PUGI_IMPL_THROW_ERROR #undef PUGI_IMPL_CHECK_ERROR #endif /** * Copyright (c) 2006-2023 Arseny Kapoulkine * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ pr0m1th3as-datatypes-9c9a8d3/src/ods/pugixml.hpp000066400000000000000000001520311522766574100216310ustar00rootroot00000000000000/** * pugixml parser - version 1.14 * -------------------------------------------------------- * Copyright (C) 2006-2023, by Arseny Kapoulkine (arseny.kapoulkine@gmail.com) * Report bugs and download new versions at https://pugixml.org/ * * This library is distributed under the MIT License. See notice at the end * of this file. * * This work is based on the pugxml parser, which is: * Copyright (C) 2003, by Kristen Wegner (kristen@tima.net) */ // Define version macro; evaluates to major * 1000 + minor * 10 + patch so that it's safe to use in less-than comparisons // Note: pugixml used major * 100 + minor * 10 + patch format up until 1.9 (which had version identifier 190); starting from pugixml 1.10, the minor version number is two digits #ifndef PUGIXML_VERSION # define PUGIXML_VERSION 1140 // 1.14 #endif // Include user configuration file (this can define various configuration macros) #include "pugiconfig.hpp" #ifndef HEADER_PUGIXML_HPP #define HEADER_PUGIXML_HPP // Include stddef.h for size_t and ptrdiff_t #include // Include exception header for XPath #if !defined(PUGIXML_NO_XPATH) && !defined(PUGIXML_NO_EXCEPTIONS) # include #endif // Include STL headers #ifndef PUGIXML_NO_STL # include # include # include #endif // Macro for deprecated features #ifndef PUGIXML_DEPRECATED # if defined(__GNUC__) # define PUGIXML_DEPRECATED __attribute__((deprecated)) # elif defined(_MSC_VER) && _MSC_VER >= 1300 # define PUGIXML_DEPRECATED __declspec(deprecated) # else # define PUGIXML_DEPRECATED # endif #endif // If no API is defined, assume default #ifndef PUGIXML_API # define PUGIXML_API #endif // If no API for classes is defined, assume default #ifndef PUGIXML_CLASS # define PUGIXML_CLASS PUGIXML_API #endif // If no API for functions is defined, assume default #ifndef PUGIXML_FUNCTION # define PUGIXML_FUNCTION PUGIXML_API #endif // If the platform is known to have long long support, enable long long functions #ifndef PUGIXML_HAS_LONG_LONG # if __cplusplus >= 201103 # define PUGIXML_HAS_LONG_LONG # elif defined(_MSC_VER) && _MSC_VER >= 1400 # define PUGIXML_HAS_LONG_LONG # endif #endif // If the platform is known to have move semantics support, compile move ctor/operator implementation #ifndef PUGIXML_HAS_MOVE # if __cplusplus >= 201103 # define PUGIXML_HAS_MOVE # elif defined(_MSC_VER) && _MSC_VER >= 1600 # define PUGIXML_HAS_MOVE # endif #endif // If C++ is 2011 or higher, add 'noexcept' specifiers #ifndef PUGIXML_NOEXCEPT # if __cplusplus >= 201103 # define PUGIXML_NOEXCEPT noexcept # elif defined(_MSC_VER) && _MSC_VER >= 1900 # define PUGIXML_NOEXCEPT noexcept # else # define PUGIXML_NOEXCEPT # endif #endif // Some functions can not be noexcept in compact mode #ifdef PUGIXML_COMPACT # define PUGIXML_NOEXCEPT_IF_NOT_COMPACT #else # define PUGIXML_NOEXCEPT_IF_NOT_COMPACT PUGIXML_NOEXCEPT #endif // If C++ is 2011 or higher, add 'override' qualifiers #ifndef PUGIXML_OVERRIDE # if __cplusplus >= 201103 # define PUGIXML_OVERRIDE override # elif defined(_MSC_VER) && _MSC_VER >= 1700 # define PUGIXML_OVERRIDE override # else # define PUGIXML_OVERRIDE # endif #endif // If C++ is 2011 or higher, use 'nullptr' #ifndef PUGIXML_NULL # if __cplusplus >= 201103 # define PUGIXML_NULL nullptr # elif defined(_MSC_VER) && _MSC_VER >= 1600 # define PUGIXML_NULL nullptr # else # define PUGIXML_NULL 0 # endif #endif // Character interface macros #ifdef PUGIXML_WCHAR_MODE # define PUGIXML_TEXT(t) L ## t # define PUGIXML_CHAR wchar_t #else # define PUGIXML_TEXT(t) t # define PUGIXML_CHAR char #endif namespace pugi { // Character type used for all internal storage and operations; depends on PUGIXML_WCHAR_MODE typedef PUGIXML_CHAR char_t; #ifndef PUGIXML_NO_STL // String type used for operations that work with STL string; depends on PUGIXML_WCHAR_MODE typedef std::basic_string, std::allocator > string_t; #endif } // The PugiXML namespace namespace pugi { // Tree node types enum xml_node_type { node_null, // Empty (null) node handle node_document, // A document tree's absolute root node_element, // Element tag, i.e. '' node_pcdata, // Plain character data, i.e. 'text' node_cdata, // Character data, i.e. '' node_comment, // Comment tag, i.e. '' node_pi, // Processing instruction, i.e. '' node_declaration, // Document declaration, i.e. '' node_doctype // Document type declaration, i.e. '' }; // Parsing options // Minimal parsing mode (equivalent to turning all other flags off). // Only elements and PCDATA sections are added to the DOM tree, no text conversions are performed. const unsigned int parse_minimal = 0x0000; // This flag determines if processing instructions (node_pi) are added to the DOM tree. This flag is off by default. const unsigned int parse_pi = 0x0001; // This flag determines if comments (node_comment) are added to the DOM tree. This flag is off by default. const unsigned int parse_comments = 0x0002; // This flag determines if CDATA sections (node_cdata) are added to the DOM tree. This flag is on by default. const unsigned int parse_cdata = 0x0004; // This flag determines if plain character data (node_pcdata) that consist only of whitespace are added to the DOM tree. // This flag is off by default; turning it on usually results in slower parsing and more memory consumption. const unsigned int parse_ws_pcdata = 0x0008; // This flag determines if character and entity references are expanded during parsing. This flag is on by default. const unsigned int parse_escapes = 0x0010; // This flag determines if EOL characters are normalized (converted to #xA) during parsing. This flag is on by default. const unsigned int parse_eol = 0x0020; // This flag determines if attribute values are normalized using CDATA normalization rules during parsing. This flag is on by default. const unsigned int parse_wconv_attribute = 0x0040; // This flag determines if attribute values are normalized using NMTOKENS normalization rules during parsing. This flag is off by default. const unsigned int parse_wnorm_attribute = 0x0080; // This flag determines if document declaration (node_declaration) is added to the DOM tree. This flag is off by default. const unsigned int parse_declaration = 0x0100; // This flag determines if document type declaration (node_doctype) is added to the DOM tree. This flag is off by default. const unsigned int parse_doctype = 0x0200; // This flag determines if plain character data (node_pcdata) that is the only child of the parent node and that consists only // of whitespace is added to the DOM tree. // This flag is off by default; turning it on may result in slower parsing and more memory consumption. const unsigned int parse_ws_pcdata_single = 0x0400; // This flag determines if leading and trailing whitespace is to be removed from plain character data. This flag is off by default. const unsigned int parse_trim_pcdata = 0x0800; // This flag determines if plain character data that does not have a parent node is added to the DOM tree, and if an empty document // is a valid document. This flag is off by default. const unsigned int parse_fragment = 0x1000; // This flag determines if plain character data is be stored in the parent element's value. This significantly changes the structure of // the document; this flag is only recommended for parsing documents with many PCDATA nodes in memory-constrained environments. // This flag is off by default. const unsigned int parse_embed_pcdata = 0x2000; // This flag determines whether determines whether the the two pcdata should be merged or not, if no intermediatory data are parsed in the document. // This flag is off by default. const unsigned int parse_merge_pcdata = 0x4000; // The default parsing mode. // Elements, PCDATA and CDATA sections are added to the DOM tree, character/reference entities are expanded, // End-of-Line characters are normalized, attribute values are normalized using CDATA normalization rules. const unsigned int parse_default = parse_cdata | parse_escapes | parse_wconv_attribute | parse_eol; // The full parsing mode. // Nodes of all types are added to the DOM tree, character/reference entities are expanded, // End-of-Line characters are normalized, attribute values are normalized using CDATA normalization rules. const unsigned int parse_full = parse_default | parse_pi | parse_comments | parse_declaration | parse_doctype; // These flags determine the encoding of input data for XML document enum xml_encoding { encoding_auto, // Auto-detect input encoding using BOM or < / class xml_object_range { public: typedef It const_iterator; typedef It iterator; xml_object_range(It b, It e): _begin(b), _end(e) { } It begin() const { return _begin; } It end() const { return _end; } bool empty() const { return _begin == _end; } private: It _begin, _end; }; // Writer interface for node printing (see xml_node::print) class PUGIXML_CLASS xml_writer { public: virtual ~xml_writer(); // Write memory chunk into stream/file/whatever virtual void write(const void* data, size_t size) = 0; }; // xml_writer implementation for FILE* class PUGIXML_CLASS xml_writer_file: public xml_writer { public: // Construct writer from a FILE* object; void* is used to avoid header dependencies on stdio xml_writer_file(void* file); virtual void write(const void* data, size_t size) PUGIXML_OVERRIDE; private: void* file; }; #ifndef PUGIXML_NO_STL // xml_writer implementation for streams class PUGIXML_CLASS xml_writer_stream: public xml_writer { public: // Construct writer from an output stream object xml_writer_stream(std::basic_ostream >& stream); xml_writer_stream(std::basic_ostream >& stream); virtual void write(const void* data, size_t size) PUGIXML_OVERRIDE; private: std::basic_ostream >* narrow_stream; std::basic_ostream >* wide_stream; }; #endif // A light-weight handle for manipulating attributes in DOM tree class PUGIXML_CLASS xml_attribute { friend class xml_attribute_iterator; friend class xml_node; private: xml_attribute_struct* _attr; typedef void (*unspecified_bool_type)(xml_attribute***); public: // Default constructor. Constructs an empty attribute. xml_attribute(); // Constructs attribute from internal pointer explicit xml_attribute(xml_attribute_struct* attr); // Safe bool conversion operator operator unspecified_bool_type() const; // Borland C++ workaround bool operator!() const; // Comparison operators (compares wrapped attribute pointers) bool operator==(const xml_attribute& r) const; bool operator!=(const xml_attribute& r) const; bool operator<(const xml_attribute& r) const; bool operator>(const xml_attribute& r) const; bool operator<=(const xml_attribute& r) const; bool operator>=(const xml_attribute& r) const; // Check if attribute is empty bool empty() const; // Get attribute name/value, or "" if attribute is empty const char_t* name() const; const char_t* value() const; // Get attribute value, or the default value if attribute is empty const char_t* as_string(const char_t* def = PUGIXML_TEXT("")) const; // Get attribute value as a number, or the default value if conversion did not succeed or attribute is empty int as_int(int def = 0) const; unsigned int as_uint(unsigned int def = 0) const; double as_double(double def = 0) const; float as_float(float def = 0) const; #ifdef PUGIXML_HAS_LONG_LONG long long as_llong(long long def = 0) const; unsigned long long as_ullong(unsigned long long def = 0) const; #endif // Get attribute value as bool (returns true if first character is in '1tTyY' set), or the default value if attribute is empty bool as_bool(bool def = false) const; // Set attribute name/value (returns false if attribute is empty or there is not enough memory) bool set_name(const char_t* rhs); bool set_name(const char_t* rhs, size_t size); bool set_value(const char_t* rhs); bool set_value(const char_t* rhs, size_t size); // Set attribute value with type conversion (numbers are converted to strings, boolean is converted to "true"/"false") bool set_value(int rhs); bool set_value(unsigned int rhs); bool set_value(long rhs); bool set_value(unsigned long rhs); bool set_value(double rhs); bool set_value(double rhs, int precision); bool set_value(float rhs); bool set_value(float rhs, int precision); bool set_value(bool rhs); #ifdef PUGIXML_HAS_LONG_LONG bool set_value(long long rhs); bool set_value(unsigned long long rhs); #endif // Set attribute value (equivalent to set_value without error checking) xml_attribute& operator=(const char_t* rhs); xml_attribute& operator=(int rhs); xml_attribute& operator=(unsigned int rhs); xml_attribute& operator=(long rhs); xml_attribute& operator=(unsigned long rhs); xml_attribute& operator=(double rhs); xml_attribute& operator=(float rhs); xml_attribute& operator=(bool rhs); #ifdef PUGIXML_HAS_LONG_LONG xml_attribute& operator=(long long rhs); xml_attribute& operator=(unsigned long long rhs); #endif // Get next/previous attribute in the attribute list of the parent node xml_attribute next_attribute() const; xml_attribute previous_attribute() const; // Get hash value (unique for handles to the same object) size_t hash_value() const; // Get internal pointer xml_attribute_struct* internal_object() const; }; #ifdef __BORLANDC__ // Borland C++ workaround bool PUGIXML_FUNCTION operator&&(const xml_attribute& lhs, bool rhs); bool PUGIXML_FUNCTION operator||(const xml_attribute& lhs, bool rhs); #endif // A light-weight handle for manipulating nodes in DOM tree class PUGIXML_CLASS xml_node { friend class xml_attribute_iterator; friend class xml_node_iterator; friend class xml_named_node_iterator; protected: xml_node_struct* _root; typedef void (*unspecified_bool_type)(xml_node***); public: // Default constructor. Constructs an empty node. xml_node(); // Constructs node from internal pointer explicit xml_node(xml_node_struct* p); // Safe bool conversion operator operator unspecified_bool_type() const; // Borland C++ workaround bool operator!() const; // Comparison operators (compares wrapped node pointers) bool operator==(const xml_node& r) const; bool operator!=(const xml_node& r) const; bool operator<(const xml_node& r) const; bool operator>(const xml_node& r) const; bool operator<=(const xml_node& r) const; bool operator>=(const xml_node& r) const; // Check if node is empty. bool empty() const; // Get node type xml_node_type type() const; // Get node name, or "" if node is empty or it has no name const char_t* name() const; // Get node value, or "" if node is empty or it has no value // Note: For text node.value() does not return "text"! Use child_value() or text() methods to access text inside nodes. const char_t* value() const; // Get attribute list xml_attribute first_attribute() const; xml_attribute last_attribute() const; // Get children list xml_node first_child() const; xml_node last_child() const; // Get next/previous sibling in the children list of the parent node xml_node next_sibling() const; xml_node previous_sibling() const; // Get parent node xml_node parent() const; // Get root of DOM tree this node belongs to xml_node root() const; // Get text object for the current node xml_text text() const; // Get child, attribute or next/previous sibling with the specified name xml_node child(const char_t* name) const; xml_attribute attribute(const char_t* name) const; xml_node next_sibling(const char_t* name) const; xml_node previous_sibling(const char_t* name) const; // Get attribute, starting the search from a hint (and updating hint so that searching for a sequence of attributes is fast) xml_attribute attribute(const char_t* name, xml_attribute& hint) const; // Get child value of current node; that is, value of the first child node of type PCDATA/CDATA const char_t* child_value() const; // Get child value of child with specified name. Equivalent to child(name).child_value(). const char_t* child_value(const char_t* name) const; // Set node name/value (returns false if node is empty, there is not enough memory, or node can not have name/value) bool set_name(const char_t* rhs); bool set_name(const char_t* rhs, size_t size); bool set_value(const char_t* rhs); bool set_value(const char_t* rhs, size_t size); // Add attribute with specified name. Returns added attribute, or empty attribute on errors. xml_attribute append_attribute(const char_t* name); xml_attribute prepend_attribute(const char_t* name); xml_attribute insert_attribute_after(const char_t* name, const xml_attribute& attr); xml_attribute insert_attribute_before(const char_t* name, const xml_attribute& attr); // Add a copy of the specified attribute. Returns added attribute, or empty attribute on errors. xml_attribute append_copy(const xml_attribute& proto); xml_attribute prepend_copy(const xml_attribute& proto); xml_attribute insert_copy_after(const xml_attribute& proto, const xml_attribute& attr); xml_attribute insert_copy_before(const xml_attribute& proto, const xml_attribute& attr); // Add child node with specified type. Returns added node, or empty node on errors. xml_node append_child(xml_node_type type = node_element); xml_node prepend_child(xml_node_type type = node_element); xml_node insert_child_after(xml_node_type type, const xml_node& node); xml_node insert_child_before(xml_node_type type, const xml_node& node); // Add child element with specified name. Returns added node, or empty node on errors. xml_node append_child(const char_t* name); xml_node prepend_child(const char_t* name); xml_node insert_child_after(const char_t* name, const xml_node& node); xml_node insert_child_before(const char_t* name, const xml_node& node); // Add a copy of the specified node as a child. Returns added node, or empty node on errors. xml_node append_copy(const xml_node& proto); xml_node prepend_copy(const xml_node& proto); xml_node insert_copy_after(const xml_node& proto, const xml_node& node); xml_node insert_copy_before(const xml_node& proto, const xml_node& node); // Move the specified node to become a child of this node. Returns moved node, or empty node on errors. xml_node append_move(const xml_node& moved); xml_node prepend_move(const xml_node& moved); xml_node insert_move_after(const xml_node& moved, const xml_node& node); xml_node insert_move_before(const xml_node& moved, const xml_node& node); // Remove specified attribute bool remove_attribute(const xml_attribute& a); bool remove_attribute(const char_t* name); // Remove all attributes bool remove_attributes(); // Remove specified child bool remove_child(const xml_node& n); bool remove_child(const char_t* name); // Remove all children bool remove_children(); // Parses buffer as an XML document fragment and appends all nodes as children of the current node. // Copies/converts the buffer, so it may be deleted or changed after the function returns. // Note: append_buffer allocates memory that has the lifetime of the owning document; removing the appended nodes does not immediately reclaim that memory. xml_parse_result append_buffer(const void* contents, size_t size, unsigned int options = parse_default, xml_encoding encoding = encoding_auto); // Find attribute using predicate. Returns first attribute for which predicate returned true. template xml_attribute find_attribute(Predicate pred) const { if (!_root) return xml_attribute(); for (xml_attribute attrib = first_attribute(); attrib; attrib = attrib.next_attribute()) if (pred(attrib)) return attrib; return xml_attribute(); } // Find child node using predicate. Returns first child for which predicate returned true. template xml_node find_child(Predicate pred) const { if (!_root) return xml_node(); for (xml_node node = first_child(); node; node = node.next_sibling()) if (pred(node)) return node; return xml_node(); } // Find node from subtree using predicate. Returns first node from subtree (depth-first), for which predicate returned true. template xml_node find_node(Predicate pred) const { if (!_root) return xml_node(); xml_node cur = first_child(); while (cur._root && cur._root != _root) { if (pred(cur)) return cur; if (cur.first_child()) cur = cur.first_child(); else if (cur.next_sibling()) cur = cur.next_sibling(); else { while (!cur.next_sibling() && cur._root != _root) cur = cur.parent(); if (cur._root != _root) cur = cur.next_sibling(); } } return xml_node(); } // Find child node by attribute name/value xml_node find_child_by_attribute(const char_t* name, const char_t* attr_name, const char_t* attr_value) const; xml_node find_child_by_attribute(const char_t* attr_name, const char_t* attr_value) const; #ifndef PUGIXML_NO_STL // Get the absolute node path from root as a text string. string_t path(char_t delimiter = '/') const; #endif // Search for a node by path consisting of node names and . or .. elements. xml_node first_element_by_path(const char_t* path, char_t delimiter = '/') const; // Recursively traverse subtree with xml_tree_walker bool traverse(xml_tree_walker& walker); #ifndef PUGIXML_NO_XPATH // Select single node by evaluating XPath query. Returns first node from the resulting node set. xpath_node select_node(const char_t* query, xpath_variable_set* variables = PUGIXML_NULL) const; xpath_node select_node(const xpath_query& query) const; // Select node set by evaluating XPath query xpath_node_set select_nodes(const char_t* query, xpath_variable_set* variables = PUGIXML_NULL) const; xpath_node_set select_nodes(const xpath_query& query) const; // (deprecated: use select_node instead) Select single node by evaluating XPath query. PUGIXML_DEPRECATED xpath_node select_single_node(const char_t* query, xpath_variable_set* variables = PUGIXML_NULL) const; PUGIXML_DEPRECATED xpath_node select_single_node(const xpath_query& query) const; #endif // Print subtree using a writer object void print(xml_writer& writer, const char_t* indent = PUGIXML_TEXT("\t"), unsigned int flags = format_default, xml_encoding encoding = encoding_auto, unsigned int depth = 0) const; #ifndef PUGIXML_NO_STL // Print subtree to stream void print(std::basic_ostream >& os, const char_t* indent = PUGIXML_TEXT("\t"), unsigned int flags = format_default, xml_encoding encoding = encoding_auto, unsigned int depth = 0) const; void print(std::basic_ostream >& os, const char_t* indent = PUGIXML_TEXT("\t"), unsigned int flags = format_default, unsigned int depth = 0) const; #endif // Child nodes iterators typedef xml_node_iterator iterator; iterator begin() const; iterator end() const; // Attribute iterators typedef xml_attribute_iterator attribute_iterator; attribute_iterator attributes_begin() const; attribute_iterator attributes_end() const; // Range-based for support xml_object_range children() const; xml_object_range attributes() const; // Range-based for support for all children with the specified name // Note: name pointer must have a longer lifetime than the returned object; be careful with passing temporaries! xml_object_range children(const char_t* name) const; // Get node offset in parsed file/string (in char_t units) for debugging purposes ptrdiff_t offset_debug() const; // Get hash value (unique for handles to the same object) size_t hash_value() const; // Get internal pointer xml_node_struct* internal_object() const; }; #ifdef __BORLANDC__ // Borland C++ workaround bool PUGIXML_FUNCTION operator&&(const xml_node& lhs, bool rhs); bool PUGIXML_FUNCTION operator||(const xml_node& lhs, bool rhs); #endif // A helper for working with text inside PCDATA nodes class PUGIXML_CLASS xml_text { friend class xml_node; xml_node_struct* _root; typedef void (*unspecified_bool_type)(xml_text***); explicit xml_text(xml_node_struct* root); xml_node_struct* _data_new(); xml_node_struct* _data() const; public: // Default constructor. Constructs an empty object. xml_text(); // Safe bool conversion operator operator unspecified_bool_type() const; // Borland C++ workaround bool operator!() const; // Check if text object is empty bool empty() const; // Get text, or "" if object is empty const char_t* get() const; // Get text, or the default value if object is empty const char_t* as_string(const char_t* def = PUGIXML_TEXT("")) const; // Get text as a number, or the default value if conversion did not succeed or object is empty int as_int(int def = 0) const; unsigned int as_uint(unsigned int def = 0) const; double as_double(double def = 0) const; float as_float(float def = 0) const; #ifdef PUGIXML_HAS_LONG_LONG long long as_llong(long long def = 0) const; unsigned long long as_ullong(unsigned long long def = 0) const; #endif // Get text as bool (returns true if first character is in '1tTyY' set), or the default value if object is empty bool as_bool(bool def = false) const; // Set text (returns false if object is empty or there is not enough memory) bool set(const char_t* rhs); bool set(const char_t* rhs, size_t size); // Set text with type conversion (numbers are converted to strings, boolean is converted to "true"/"false") bool set(int rhs); bool set(unsigned int rhs); bool set(long rhs); bool set(unsigned long rhs); bool set(double rhs); bool set(double rhs, int precision); bool set(float rhs); bool set(float rhs, int precision); bool set(bool rhs); #ifdef PUGIXML_HAS_LONG_LONG bool set(long long rhs); bool set(unsigned long long rhs); #endif // Set text (equivalent to set without error checking) xml_text& operator=(const char_t* rhs); xml_text& operator=(int rhs); xml_text& operator=(unsigned int rhs); xml_text& operator=(long rhs); xml_text& operator=(unsigned long rhs); xml_text& operator=(double rhs); xml_text& operator=(float rhs); xml_text& operator=(bool rhs); #ifdef PUGIXML_HAS_LONG_LONG xml_text& operator=(long long rhs); xml_text& operator=(unsigned long long rhs); #endif // Get the data node (node_pcdata or node_cdata) for this object xml_node data() const; }; #ifdef __BORLANDC__ // Borland C++ workaround bool PUGIXML_FUNCTION operator&&(const xml_text& lhs, bool rhs); bool PUGIXML_FUNCTION operator||(const xml_text& lhs, bool rhs); #endif // Child node iterator (a bidirectional iterator over a collection of xml_node) class PUGIXML_CLASS xml_node_iterator { friend class xml_node; private: mutable xml_node _wrap; xml_node _parent; xml_node_iterator(xml_node_struct* ref, xml_node_struct* parent); public: // Iterator traits typedef ptrdiff_t difference_type; typedef xml_node value_type; typedef xml_node* pointer; typedef xml_node& reference; #ifndef PUGIXML_NO_STL typedef std::bidirectional_iterator_tag iterator_category; #endif // Default constructor xml_node_iterator(); // Construct an iterator which points to the specified node xml_node_iterator(const xml_node& node); // Iterator operators bool operator==(const xml_node_iterator& rhs) const; bool operator!=(const xml_node_iterator& rhs) const; xml_node& operator*() const; xml_node* operator->() const; xml_node_iterator& operator++(); xml_node_iterator operator++(int); xml_node_iterator& operator--(); xml_node_iterator operator--(int); }; // Attribute iterator (a bidirectional iterator over a collection of xml_attribute) class PUGIXML_CLASS xml_attribute_iterator { friend class xml_node; private: mutable xml_attribute _wrap; xml_node _parent; xml_attribute_iterator(xml_attribute_struct* ref, xml_node_struct* parent); public: // Iterator traits typedef ptrdiff_t difference_type; typedef xml_attribute value_type; typedef xml_attribute* pointer; typedef xml_attribute& reference; #ifndef PUGIXML_NO_STL typedef std::bidirectional_iterator_tag iterator_category; #endif // Default constructor xml_attribute_iterator(); // Construct an iterator which points to the specified attribute xml_attribute_iterator(const xml_attribute& attr, const xml_node& parent); // Iterator operators bool operator==(const xml_attribute_iterator& rhs) const; bool operator!=(const xml_attribute_iterator& rhs) const; xml_attribute& operator*() const; xml_attribute* operator->() const; xml_attribute_iterator& operator++(); xml_attribute_iterator operator++(int); xml_attribute_iterator& operator--(); xml_attribute_iterator operator--(int); }; // Named node range helper class PUGIXML_CLASS xml_named_node_iterator { friend class xml_node; public: // Iterator traits typedef ptrdiff_t difference_type; typedef xml_node value_type; typedef xml_node* pointer; typedef xml_node& reference; #ifndef PUGIXML_NO_STL typedef std::bidirectional_iterator_tag iterator_category; #endif // Default constructor xml_named_node_iterator(); // Construct an iterator which points to the specified node // Note: name pointer is stored in the iterator and must have a longer lifetime than iterator itself xml_named_node_iterator(const xml_node& node, const char_t* name); // Iterator operators bool operator==(const xml_named_node_iterator& rhs) const; bool operator!=(const xml_named_node_iterator& rhs) const; xml_node& operator*() const; xml_node* operator->() const; xml_named_node_iterator& operator++(); xml_named_node_iterator operator++(int); xml_named_node_iterator& operator--(); xml_named_node_iterator operator--(int); private: mutable xml_node _wrap; xml_node _parent; const char_t* _name; xml_named_node_iterator(xml_node_struct* ref, xml_node_struct* parent, const char_t* name); }; // Abstract tree walker class (see xml_node::traverse) class PUGIXML_CLASS xml_tree_walker { friend class xml_node; private: int _depth; protected: // Get current traversal depth int depth() const; public: xml_tree_walker(); virtual ~xml_tree_walker(); // Callback that is called when traversal begins virtual bool begin(xml_node& node); // Callback that is called for each node traversed virtual bool for_each(xml_node& node) = 0; // Callback that is called when traversal ends virtual bool end(xml_node& node); }; // Parsing status, returned as part of xml_parse_result object enum xml_parse_status { status_ok = 0, // No error status_file_not_found, // File was not found during load_file() status_io_error, // Error reading from file/stream status_out_of_memory, // Could not allocate memory status_internal_error, // Internal error occurred status_unrecognized_tag, // Parser could not determine tag type status_bad_pi, // Parsing error occurred while parsing document declaration/processing instruction status_bad_comment, // Parsing error occurred while parsing comment status_bad_cdata, // Parsing error occurred while parsing CDATA section status_bad_doctype, // Parsing error occurred while parsing document type declaration status_bad_pcdata, // Parsing error occurred while parsing PCDATA section status_bad_start_element, // Parsing error occurred while parsing start element tag status_bad_attribute, // Parsing error occurred while parsing element attribute status_bad_end_element, // Parsing error occurred while parsing end element tag status_end_element_mismatch,// There was a mismatch of start-end tags (closing tag had incorrect name, some tag was not closed or there was an excessive closing tag) status_append_invalid_root, // Unable to append nodes since root type is not node_element or node_document (exclusive to xml_node::append_buffer) status_no_document_element // Parsing resulted in a document without element nodes }; // Parsing result struct PUGIXML_CLASS xml_parse_result { // Parsing status (see xml_parse_status) xml_parse_status status; // Last parsed offset (in char_t units from start of input data) ptrdiff_t offset; // Source document encoding xml_encoding encoding; // Default constructor, initializes object to failed state xml_parse_result(); // Cast to bool operator operator bool() const; // Get error description const char* description() const; }; // Document class (DOM tree root) class PUGIXML_CLASS xml_document: public xml_node { private: char_t* _buffer; char _memory[192]; // Non-copyable semantics xml_document(const xml_document&); xml_document& operator=(const xml_document&); void _create(); void _destroy(); void _move(xml_document& rhs) PUGIXML_NOEXCEPT_IF_NOT_COMPACT; public: // Default constructor, makes empty document xml_document(); // Destructor, invalidates all node/attribute handles to this document ~xml_document(); #ifdef PUGIXML_HAS_MOVE // Move semantics support xml_document(xml_document&& rhs) PUGIXML_NOEXCEPT_IF_NOT_COMPACT; xml_document& operator=(xml_document&& rhs) PUGIXML_NOEXCEPT_IF_NOT_COMPACT; #endif // Removes all nodes, leaving the empty document void reset(); // Removes all nodes, then copies the entire contents of the specified document void reset(const xml_document& proto); #ifndef PUGIXML_NO_STL // Load document from stream. xml_parse_result load(std::basic_istream >& stream, unsigned int options = parse_default, xml_encoding encoding = encoding_auto); xml_parse_result load(std::basic_istream >& stream, unsigned int options = parse_default); #endif // (deprecated: use load_string instead) Load document from zero-terminated string. No encoding conversions are applied. PUGIXML_DEPRECATED xml_parse_result load(const char_t* contents, unsigned int options = parse_default); // Load document from zero-terminated string. No encoding conversions are applied. xml_parse_result load_string(const char_t* contents, unsigned int options = parse_default); // Load document from file xml_parse_result load_file(const char* path, unsigned int options = parse_default, xml_encoding encoding = encoding_auto); xml_parse_result load_file(const wchar_t* path, unsigned int options = parse_default, xml_encoding encoding = encoding_auto); // Load document from buffer. Copies/converts the buffer, so it may be deleted or changed after the function returns. xml_parse_result load_buffer(const void* contents, size_t size, unsigned int options = parse_default, xml_encoding encoding = encoding_auto); // Load document from buffer, using the buffer for in-place parsing (the buffer is modified and used for storage of document data). // You should ensure that buffer data will persist throughout the document's lifetime, and free the buffer memory manually once document is destroyed. xml_parse_result load_buffer_inplace(void* contents, size_t size, unsigned int options = parse_default, xml_encoding encoding = encoding_auto); // Load document from buffer, using the buffer for in-place parsing (the buffer is modified and used for storage of document data). // You should allocate the buffer with pugixml allocation function; document will free the buffer when it is no longer needed (you can't use it anymore). xml_parse_result load_buffer_inplace_own(void* contents, size_t size, unsigned int options = parse_default, xml_encoding encoding = encoding_auto); // Save XML document to writer (semantics is slightly different from xml_node::print, see documentation for details). void save(xml_writer& writer, const char_t* indent = PUGIXML_TEXT("\t"), unsigned int flags = format_default, xml_encoding encoding = encoding_auto) const; #ifndef PUGIXML_NO_STL // Save XML document to stream (semantics is slightly different from xml_node::print, see documentation for details). void save(std::basic_ostream >& stream, const char_t* indent = PUGIXML_TEXT("\t"), unsigned int flags = format_default, xml_encoding encoding = encoding_auto) const; void save(std::basic_ostream >& stream, const char_t* indent = PUGIXML_TEXT("\t"), unsigned int flags = format_default) const; #endif // Save XML to file bool save_file(const char* path, const char_t* indent = PUGIXML_TEXT("\t"), unsigned int flags = format_default, xml_encoding encoding = encoding_auto) const; bool save_file(const wchar_t* path, const char_t* indent = PUGIXML_TEXT("\t"), unsigned int flags = format_default, xml_encoding encoding = encoding_auto) const; // Get document element xml_node document_element() const; }; #ifndef PUGIXML_NO_XPATH // XPath query return type enum xpath_value_type { xpath_type_none, // Unknown type (query failed to compile) xpath_type_node_set, // Node set (xpath_node_set) xpath_type_number, // Number xpath_type_string, // String xpath_type_boolean // Boolean }; // XPath parsing result struct PUGIXML_CLASS xpath_parse_result { // Error message (0 if no error) const char* error; // Last parsed offset (in char_t units from string start) ptrdiff_t offset; // Default constructor, initializes object to failed state xpath_parse_result(); // Cast to bool operator operator bool() const; // Get error description const char* description() const; }; // A single XPath variable class PUGIXML_CLASS xpath_variable { friend class xpath_variable_set; protected: xpath_value_type _type; xpath_variable* _next; xpath_variable(xpath_value_type type); // Non-copyable semantics xpath_variable(const xpath_variable&); xpath_variable& operator=(const xpath_variable&); public: // Get variable name const char_t* name() const; // Get variable type xpath_value_type type() const; // Get variable value; no type conversion is performed, default value (false, NaN, empty string, empty node set) is returned on type mismatch error bool get_boolean() const; double get_number() const; const char_t* get_string() const; const xpath_node_set& get_node_set() const; // Set variable value; no type conversion is performed, false is returned on type mismatch error bool set(bool value); bool set(double value); bool set(const char_t* value); bool set(const xpath_node_set& value); }; // A set of XPath variables class PUGIXML_CLASS xpath_variable_set { private: xpath_variable* _data[64]; void _assign(const xpath_variable_set& rhs); void _swap(xpath_variable_set& rhs); xpath_variable* _find(const char_t* name) const; static bool _clone(xpath_variable* var, xpath_variable** out_result); static void _destroy(xpath_variable* var); public: // Default constructor/destructor xpath_variable_set(); ~xpath_variable_set(); // Copy constructor/assignment operator xpath_variable_set(const xpath_variable_set& rhs); xpath_variable_set& operator=(const xpath_variable_set& rhs); #ifdef PUGIXML_HAS_MOVE // Move semantics support xpath_variable_set(xpath_variable_set&& rhs) PUGIXML_NOEXCEPT; xpath_variable_set& operator=(xpath_variable_set&& rhs) PUGIXML_NOEXCEPT; #endif // Add a new variable or get the existing one, if the types match xpath_variable* add(const char_t* name, xpath_value_type type); // Set value of an existing variable; no type conversion is performed, false is returned if there is no such variable or if types mismatch bool set(const char_t* name, bool value); bool set(const char_t* name, double value); bool set(const char_t* name, const char_t* value); bool set(const char_t* name, const xpath_node_set& value); // Get existing variable by name xpath_variable* get(const char_t* name); const xpath_variable* get(const char_t* name) const; }; // A compiled XPath query object class PUGIXML_CLASS xpath_query { private: void* _impl; xpath_parse_result _result; typedef void (*unspecified_bool_type)(xpath_query***); // Non-copyable semantics xpath_query(const xpath_query&); xpath_query& operator=(const xpath_query&); public: // Construct a compiled object from XPath expression. // If PUGIXML_NO_EXCEPTIONS is not defined, throws xpath_exception on compilation errors. explicit xpath_query(const char_t* query, xpath_variable_set* variables = PUGIXML_NULL); // Constructor xpath_query(); // Destructor ~xpath_query(); #ifdef PUGIXML_HAS_MOVE // Move semantics support xpath_query(xpath_query&& rhs) PUGIXML_NOEXCEPT; xpath_query& operator=(xpath_query&& rhs) PUGIXML_NOEXCEPT; #endif // Get query expression return type xpath_value_type return_type() const; // Evaluate expression as boolean value in the specified context; performs type conversion if necessary. // If PUGIXML_NO_EXCEPTIONS is not defined, throws std::bad_alloc on out of memory errors. bool evaluate_boolean(const xpath_node& n) const; // Evaluate expression as double value in the specified context; performs type conversion if necessary. // If PUGIXML_NO_EXCEPTIONS is not defined, throws std::bad_alloc on out of memory errors. double evaluate_number(const xpath_node& n) const; #ifndef PUGIXML_NO_STL // Evaluate expression as string value in the specified context; performs type conversion if necessary. // If PUGIXML_NO_EXCEPTIONS is not defined, throws std::bad_alloc on out of memory errors. string_t evaluate_string(const xpath_node& n) const; #endif // Evaluate expression as string value in the specified context; performs type conversion if necessary. // At most capacity characters are written to the destination buffer, full result size is returned (includes terminating zero). // If PUGIXML_NO_EXCEPTIONS is not defined, throws std::bad_alloc on out of memory errors. // If PUGIXML_NO_EXCEPTIONS is defined, returns empty set instead. size_t evaluate_string(char_t* buffer, size_t capacity, const xpath_node& n) const; // Evaluate expression as node set in the specified context. // If PUGIXML_NO_EXCEPTIONS is not defined, throws xpath_exception on type mismatch and std::bad_alloc on out of memory errors. // If PUGIXML_NO_EXCEPTIONS is defined, returns empty node set instead. xpath_node_set evaluate_node_set(const xpath_node& n) const; // Evaluate expression as node set in the specified context. // Return first node in document order, or empty node if node set is empty. // If PUGIXML_NO_EXCEPTIONS is not defined, throws xpath_exception on type mismatch and std::bad_alloc on out of memory errors. // If PUGIXML_NO_EXCEPTIONS is defined, returns empty node instead. xpath_node evaluate_node(const xpath_node& n) const; // Get parsing result (used to get compilation errors in PUGIXML_NO_EXCEPTIONS mode) const xpath_parse_result& result() const; // Safe bool conversion operator operator unspecified_bool_type() const; // Borland C++ workaround bool operator!() const; }; #ifndef PUGIXML_NO_EXCEPTIONS #if defined(_MSC_VER) // C4275 can be ignored in Visual C++ if you are deriving // from a type in the Standard C++ Library #pragma warning(push) #pragma warning(disable: 4275) #endif // XPath exception class class PUGIXML_CLASS xpath_exception: public std::exception { private: xpath_parse_result _result; public: // Construct exception from parse result explicit xpath_exception(const xpath_parse_result& result); // Get error message virtual const char* what() const throw() PUGIXML_OVERRIDE; // Get parse result const xpath_parse_result& result() const; }; #if defined(_MSC_VER) #pragma warning(pop) #endif #endif // XPath node class (either xml_node or xml_attribute) class PUGIXML_CLASS xpath_node { private: xml_node _node; xml_attribute _attribute; typedef void (*unspecified_bool_type)(xpath_node***); public: // Default constructor; constructs empty XPath node xpath_node(); // Construct XPath node from XML node/attribute xpath_node(const xml_node& node); xpath_node(const xml_attribute& attribute, const xml_node& parent); // Get node/attribute, if any xml_node node() const; xml_attribute attribute() const; // Get parent of contained node/attribute xml_node parent() const; // Safe bool conversion operator operator unspecified_bool_type() const; // Borland C++ workaround bool operator!() const; // Comparison operators bool operator==(const xpath_node& n) const; bool operator!=(const xpath_node& n) const; }; #ifdef __BORLANDC__ // Borland C++ workaround bool PUGIXML_FUNCTION operator&&(const xpath_node& lhs, bool rhs); bool PUGIXML_FUNCTION operator||(const xpath_node& lhs, bool rhs); #endif // A fixed-size collection of XPath nodes class PUGIXML_CLASS xpath_node_set { public: // Collection type enum type_t { type_unsorted, // Not ordered type_sorted, // Sorted by document order (ascending) type_sorted_reverse // Sorted by document order (descending) }; // Constant iterator type typedef const xpath_node* const_iterator; // We define non-constant iterator to be the same as constant iterator so that various generic algorithms (i.e. boost foreach) work typedef const xpath_node* iterator; // Default constructor. Constructs empty set. xpath_node_set(); // Constructs a set from iterator range; data is not checked for duplicates and is not sorted according to provided type, so be careful xpath_node_set(const_iterator begin, const_iterator end, type_t type = type_unsorted); // Destructor ~xpath_node_set(); // Copy constructor/assignment operator xpath_node_set(const xpath_node_set& ns); xpath_node_set& operator=(const xpath_node_set& ns); #ifdef PUGIXML_HAS_MOVE // Move semantics support xpath_node_set(xpath_node_set&& rhs) PUGIXML_NOEXCEPT; xpath_node_set& operator=(xpath_node_set&& rhs) PUGIXML_NOEXCEPT; #endif // Get collection type type_t type() const; // Get collection size size_t size() const; // Indexing operator const xpath_node& operator[](size_t index) const; // Collection iterators const_iterator begin() const; const_iterator end() const; // Sort the collection in ascending/descending order by document order void sort(bool reverse = false); // Get first node in the collection by document order xpath_node first() const; // Check if collection is empty bool empty() const; private: type_t _type; xpath_node _storage[1]; xpath_node* _begin; xpath_node* _end; void _assign(const_iterator begin, const_iterator end, type_t type); void _move(xpath_node_set& rhs) PUGIXML_NOEXCEPT; }; #endif #ifndef PUGIXML_NO_STL // Convert wide string to UTF8 std::basic_string, std::allocator > PUGIXML_FUNCTION as_utf8(const wchar_t* str); std::basic_string, std::allocator > PUGIXML_FUNCTION as_utf8(const std::basic_string, std::allocator >& str); // Convert UTF8 to wide string std::basic_string, std::allocator > PUGIXML_FUNCTION as_wide(const char* str); std::basic_string, std::allocator > PUGIXML_FUNCTION as_wide(const std::basic_string, std::allocator >& str); #endif // Memory allocation function interface; returns pointer to allocated memory or NULL on failure typedef void* (*allocation_function)(size_t size); // Memory deallocation function interface typedef void (*deallocation_function)(void* ptr); // Override default memory management functions. All subsequent allocations/deallocations will be performed via supplied functions. void PUGIXML_FUNCTION set_memory_management_functions(allocation_function allocate, deallocation_function deallocate); // Get current memory management functions allocation_function PUGIXML_FUNCTION get_memory_allocation_function(); deallocation_function PUGIXML_FUNCTION get_memory_deallocation_function(); } #if !defined(PUGIXML_NO_STL) && (defined(_MSC_VER) || defined(__ICC)) namespace std { // Workarounds for (non-standard) iterator category detection for older versions (MSVC7/IC8 and earlier) std::bidirectional_iterator_tag PUGIXML_FUNCTION _Iter_cat(const pugi::xml_node_iterator&); std::bidirectional_iterator_tag PUGIXML_FUNCTION _Iter_cat(const pugi::xml_attribute_iterator&); std::bidirectional_iterator_tag PUGIXML_FUNCTION _Iter_cat(const pugi::xml_named_node_iterator&); } #endif #if !defined(PUGIXML_NO_STL) && defined(__SUNPRO_CC) namespace std { // Workarounds for (non-standard) iterator category detection std::bidirectional_iterator_tag PUGIXML_FUNCTION __iterator_category(const pugi::xml_node_iterator&); std::bidirectional_iterator_tag PUGIXML_FUNCTION __iterator_category(const pugi::xml_attribute_iterator&); std::bidirectional_iterator_tag PUGIXML_FUNCTION __iterator_category(const pugi::xml_named_node_iterator&); } #endif #endif // Make sure implementation is included in header-only mode // Use macro expansion in #include to work around QMake (QTBUG-11923) #if defined(PUGIXML_HEADER_ONLY) && !defined(PUGIXML_SOURCE) # define PUGIXML_SOURCE "pugixml.cpp" # include PUGIXML_SOURCE #endif /** * Copyright (c) 2006-2023 Arseny Kapoulkine * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ pr0m1th3as-datatypes-9c9a8d3/src/tz.cpp000066400000000000000000004023401522766574100200100ustar00rootroot00000000000000// The MIT License (MIT) // // Copyright (c) 2015, 2016, 2017 Howard Hinnant // Copyright (c) 2015 Ville Voutilainen // Copyright (c) 2016 Alexander Kormanovsky // Copyright (c) 2016, 2017 Jiangang Zhuang // Copyright (c) 2017 Nicolas Veloz Savino // Copyright (c) 2017 Florian Dang // Copyright (c) 2017 Aaron Bishop // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. // // Our apologies. When the previous paragraph was written, lowercase had not yet // been invented (that would involve another several millennia of evolution). // We did not mean to shout. #ifdef _WIN32 // windows.h will be included directly and indirectly (e.g. by curl). // We need to define these macros to prevent windows.h bringing in // more than we need and do it early so windows.h doesn't get included // without these macros having been defined. // min/max macros interfere with the C++ versions. # ifndef NOMINMAX # define NOMINMAX # endif // We don't need all that Windows has to offer. # ifndef WIN32_LEAN_AND_MEAN # define WIN32_LEAN_AND_MEAN # endif // for wcstombs # ifndef _CRT_SECURE_NO_WARNINGS # define _CRT_SECURE_NO_WARNINGS # endif // None of this happens with the MS SDK (at least VS14 which I tested), but: // Compiling with mingw, we get "error: 'KF_FLAG_DEFAULT' was not declared in this scope." // and error: 'SHGetKnownFolderPath' was not declared in this scope.". // It seems when using mingw NTDDI_VERSION is undefined and that // causes KNOWN_FOLDER_FLAG and the KF_ flags to not get defined. // So we must define NTDDI_VERSION to get those flags on mingw. // The docs say though here: // https://msdn.microsoft.com/en-nz/library/windows/desktop/aa383745(v=vs.85).aspx // that "If you define NTDDI_VERSION, you must also define _WIN32_WINNT." // So we declare we require Vista or greater. # ifdef __MINGW32__ # ifndef NTDDI_VERSION # define NTDDI_VERSION 0x06000000 # define _WIN32_WINNT _WIN32_WINNT_VISTA # elif NTDDI_VERSION < 0x06000000 # warning "If this fails to compile NTDDI_VERSION may be to low. See comments above." # endif // But once we define the values above we then get this linker error: // "tz.cpp:(.rdata$.refptr.FOLDERID_Downloads[.refptr.FOLDERID_Downloads]+0x0): " // "undefined reference to `FOLDERID_Downloads'" // which #include cures see: // https://support.microsoft.com/en-us/kb/130869 # include // But with included, the error moves on to: // error: 'FOLDERID_Downloads' was not declared in this scope // Which #include cures. # include # endif // __MINGW32__ # include # if !defined(S_ISDIR) && defined(S_IFMT) && defined(_S_IFDIR) # define S_ISDIR(m) (((m) & S_IFMT) == _S_IFDIR) # endif #endif // _WIN32 #include "date/tz_private.h" #ifdef __APPLE__ # include "date/ios.h" #else # define TARGET_OS_IPHONE 0 # define TARGET_OS_SIMULATOR 0 #endif #if defined(ANDROID) || defined(__ANDROID__) # include # if USE_OS_TZDB # define MISSING_LEAP_SECONDS 1 // from https://android.googlesource.com/platform/bionic/+/master/libc/tzcode/bionic.cpp static constexpr size_t ANDROID_TIMEZONE_NAME_LENGTH = 40; struct bionic_tzdata_header_t { char tzdata_version[12]; std::int32_t index_offset; std::int32_t data_offset; std::int32_t final_offset; }; struct index_entry_t { char buf[ANDROID_TIMEZONE_NAME_LENGTH]; std::int32_t start; std::int32_t length; std::int32_t unused; // Was raw GMT offset; always 0 since tzdata2014f (L). }; # endif // USE_OS_TZDB #endif // defined(ANDROID) || defined(__ANDROID__) #if USE_OS_TZDB # include #endif #include #include #include #include #include #include #include #include #include #include #if USE_OS_TZDB # include #endif #include #include #include #include #include // unistd.h is used on some platforms as part of the the means to get // the current time zone. On Win32 windows.h provides a means to do it. // gcc/mingw supports unistd.h on Win32 but MSVC does not. #ifdef __ANDROID__ # define INSTALL . #endif #ifdef _WIN32 # ifdef WINAPI_FAMILY # include # if WINAPI_FAMILY != WINAPI_FAMILY_DESKTOP_APP # define WINRT # define INSTALL . # endif # endif # include // _unlink etc. # if defined(__clang__) struct IUnknown; // fix for issue with static_cast<> in objbase.h // (see https://github.com/philsquared/Catch/issues/690) # endif # include // CoTaskFree, ShGetKnownFolderPath etc. # if HAS_REMOTE_API # include // _mkdir # include // ShFileOperation etc. # endif // HAS_REMOTE_API #else // !_WIN32 # include # if !USE_OS_TZDB && !defined(INSTALL) # include # endif # include # include # if !USE_SHELL_API # include # include # include # include # include # include # endif //!USE_SHELL_API #endif // !_WIN32 #if HAS_REMOTE_API // Note curl includes windows.h so we must include curl AFTER definitions of things // that affect windows.h such as NOMINMAX. #if defined(_MSC_VER) && defined(SHORTENED_CURL_INCLUDE) // For rmt_curl nuget package # include #else # include #endif #endif #ifdef _WIN32 static CONSTDATA char folder_delimiter = '\\'; #elif !defined(ANDROID) && !defined(__ANDROID__) static CONSTDATA char folder_delimiter = '/'; #endif // !defined(WIN32) && !defined(ANDROID) && !defined(__ANDROID__) #if defined(__GNUC__) && __GNUC__ < 5 // GCC 4.9 Bug 61489 Wrong warning with -Wmissing-field-initializers # pragma GCC diagnostic push # pragma GCC diagnostic ignored "-Wmissing-field-initializers" #endif // defined(__GNUC__) && __GNUC__ < 5 #if !USE_OS_TZDB # ifdef _WIN32 static std::wstring convert_utf8_to_utf16(const std::string& s) { std::wstring out; const int size = MultiByteToWideChar(CP_UTF8, 0, s.c_str(), -1, NULL, 0); if (size == 0) { std::string msg = "Failed to determine required size when converting \""; msg += s; msg += "\" to UTF-16."; throw std::runtime_error(msg); } out.resize(size); const int check = MultiByteToWideChar(CP_UTF8, 0, s.c_str(), -1, &out[0], size); if (size != check) { std::string msg = "Failed to convert \""; msg += s; msg += "\" to UTF-16."; throw std::runtime_error(msg); } return out; } # ifndef WINRT namespace { struct task_mem_deleter { void operator()(wchar_t buf[]) { if (buf != nullptr) CoTaskMemFree(buf); } }; using co_task_mem_ptr = std::unique_ptr; } // We might need to know certain locations even if not using the remote API, // so keep these routines out of that block for now. static std::string get_known_folder(const GUID& folderid) { std::string folder; PWSTR pfolder = nullptr; HRESULT hr = SHGetKnownFolderPath(folderid, KF_FLAG_DEFAULT, nullptr, &pfolder); if (SUCCEEDED(hr)) { co_task_mem_ptr folder_ptr(pfolder); const wchar_t* fptr = folder_ptr.get(); auto state = std::mbstate_t(); const auto required = std::wcsrtombs(nullptr, &fptr, 0, &state); if (required != 0 && required != std::size_t(-1)) { folder.resize(required); std::wcsrtombs(&folder[0], &fptr, folder.size(), &state); } } return folder; } # ifndef INSTALL // Usually something like "c:\Users\username\Downloads". static std::string get_download_folder() { return get_known_folder(FOLDERID_Downloads); } # endif // !INSTALL # endif // WINRT # else // !_WIN32 # if !defined(INSTALL) static std::string expand_path(std::string path) { # if TARGET_OS_IPHONE return date::iOSUtils::get_tzdata_path(); # else // !TARGET_OS_IPHONE ::wordexp_t w{}; std::unique_ptr<::wordexp_t, void(*)(::wordexp_t*)> hold{&w, ::wordfree}; ::wordexp(path.c_str(), &w, 0); if (w.we_wordc != 1) throw std::runtime_error("Cannot expand path: " + path); path = w.we_wordv[0]; return path; # endif // !TARGET_OS_IPHONE } static std::string get_download_folder() { return expand_path("~/Downloads"); } # endif // !defined(INSTALL) # endif // !_WIN32 /* * This class is provided to mimic the following usage of `ifstream`: * * std::ifstream is(filename); * * file_streambuf ibuf(filename); * std::istream is(&ibuf); * * This is required because `ifstream` does not support opening files * containing wide characters on Windows. On Windows, `file_streambuf` uses * `file_open()` to convert the file name to UTF-16 before opening it with * `_wfopen()`. * * Note that this is not an exact re-implementation of `ifstream`, * but is enough for usage here. * * It is partially based on these two implementations: * - fdinbuf from http://www.josuttis.com/cppcode/fdstream.html * - stdiobuf https://stackoverflow.com/questions/12342542/convert-file-to-ifstream-c-android-ndk * * Apparently MSVC provides non-standard overloads of `ifstream` that support * a `const wchar_t*` file name, but MinGW does not https://stackoverflow.com/a/822032 */ class file_streambuf : public std::streambuf { private: FILE* file_; static const int buffer_size_ = 1024; char buffer_[buffer_size_]; public: ~file_streambuf() { if (file_) { ::fclose(file_); } } file_streambuf(const file_streambuf&) = delete; file_streambuf& operator=(const file_streambuf&) = delete; file_streambuf(const std::string& filename) : file_(file_open(filename)) { } protected: virtual int_type underflow() { if (gptr() == egptr() && file_) { const size_t size = ::fread(buffer_, 1, buffer_size_, file_); setg(buffer_, buffer_, buffer_ + size); } return (gptr() == egptr()) ? traits_type::eof() : traits_type::to_int_type(*gptr()); } private: FILE* file_open(const std::string& filename) { # ifdef _WIN32 std::wstring wfilename = convert_utf8_to_utf16(filename); FILE* file = ::_wfopen(wfilename.c_str(), L"r"); # else // !_WIN32 FILE* file = ::fopen(filename.c_str(), "rb"); # endif // _WIN32 if (file == NULL) { std::string msg = "Error opening file \""; msg += filename; msg += "\"."; throw std::runtime_error(msg); } return file; } }; #endif // !USE_OS_TZDB namespace date { // +---------------------+ // | Begin Configuration | // +---------------------+ using namespace detail; #if !USE_OS_TZDB static std::string& access_install() { static std::string install #ifndef INSTALL = get_download_folder() + folder_delimiter + "tzdata"; #else // !INSTALL # define STRINGIZEIMP(x) #x # define STRINGIZE(x) STRINGIZEIMP(x) = STRINGIZE(INSTALL) + std::string(1, folder_delimiter) + "tzdata"; #undef STRINGIZEIMP #undef STRINGIZE #endif // !INSTALL { static char* tz_local_env = getenv("TZDATA"); if (tz_local_env != nullptr) { static std::string tz_local_env_s = tz_local_env; return tz_local_env_s; } } return install; } void set_install(const std::string& install) { access_install() = install; } static const std::string& get_install() { static const std::string& ref = access_install(); return ref; } #if HAS_REMOTE_API static std::string get_download_gz_file(const std::string& version) { auto file = get_install() + version + ".tar.gz"; return file; } #endif // HAS_REMOTE_API #endif // !USE_OS_TZDB // These can be used to reduce the range of the database to save memory CONSTDATA auto min_year = date::year::min(); CONSTDATA auto max_year = date::year::max(); CONSTDATA auto min_day = date::January/1; CONSTDATA auto max_day = date::December/31; #if USE_OS_TZDB CONSTCD14 const sys_seconds min_seconds = sys_days(min_year/min_day); #endif // USE_OS_TZDB #ifndef _WIN32 static std::string discover_tz_dir() { struct stat sb; using namespace std; # if defined(ANDROID) || defined(__ANDROID__) CONSTDATA auto tz_dir_default = "/apex/com.android.tzdata/etc/tz"; CONSTDATA auto tz_dir_fallback = "/system/usr/share/zoneinfo"; // Check updatable path first if(stat(tz_dir_default, &sb) == 0 && S_ISDIR(sb.st_mode)) return tz_dir_default; else if(stat(tz_dir_fallback, &sb) == 0 && S_ISDIR(sb.st_mode)) return tz_dir_fallback; else throw runtime_error("discover_tz_dir failed to find zoneinfo\n"); # elif !defined(__APPLE__) CONSTDATA auto tz_dir_default = "/usr/share/zoneinfo"; CONSTDATA auto tz_dir_buildroot = "/usr/share/zoneinfo/uclibc"; { static char* tz_local_env = getenv("TZDATA"); if (tz_local_env != nullptr) { static std::string tz_local_env_s = tz_local_env; return tz_local_env_s; } } // Check special path which is valid for buildroot with uclibc builds if(stat(tz_dir_buildroot, &sb) == 0 && S_ISDIR(sb.st_mode)) return tz_dir_buildroot; else if(stat(tz_dir_default, &sb) == 0 && S_ISDIR(sb.st_mode)) return tz_dir_default; else throw runtime_error("discover_tz_dir failed to find zoneinfo\n"); # else // __APPLE__ # if TARGET_OS_IPHONE # if TARGET_OS_SIMULATOR return "/usr/share/zoneinfo"; # else return "/var/db/timezone/zoneinfo"; # endif # else CONSTDATA auto timezone = "/etc/localtime"; if (!(lstat(timezone, &sb) == 0 && S_ISLNK(sb.st_mode) && sb.st_size > 0)) throw runtime_error("discover_tz_dir failed\n"); string result; unique_ptr rp(new char[sb.st_size]); const auto rp_length = readlink(timezone, rp.get(), sb.st_size); if (rp_length > 0) result = string(rp.get(), rp_length); // readlink doesn't null-terminate else throw system_error(errno, system_category(), "readlink() failed"); auto i = result.find("zoneinfo"); if (i == string::npos) throw runtime_error("discover_tz_dir failed to find zoneinfo\n"); i = result.find('/', i); if (i == string::npos) throw runtime_error("discover_tz_dir failed to find '/'\n"); return result.substr(0, i); # endif # endif // __APPLE__ } static const std::string& get_tz_dir() { static const std::string tz_dir = discover_tz_dir(); return tz_dir; } #endif // +-------------------+ // | End Configuration | // +-------------------+ #ifndef _MSC_VER static_assert(min_year <= max_year, "Configuration error"); #endif #if !defined(ANDROID) && !defined(__ANDROID__) static std::unique_ptr init_tzdb(); #endif // !defined(ANDROID) && !defined(__ANDROID__) tzdb_list::~tzdb_list() { const tzdb* ptr = head_; head_ = nullptr; while (ptr != nullptr) { auto next = ptr->next; delete ptr; ptr = next; } } tzdb_list::tzdb_list(tzdb_list&& x) NOEXCEPT : head_{x.head_.exchange(nullptr)} { } void tzdb_list::push_front(tzdb* tzdb) NOEXCEPT { tzdb->next = head_; head_ = tzdb; } tzdb_list::const_iterator tzdb_list::erase_after(const_iterator p) NOEXCEPT { auto t = p.p_->next; p.p_->next = p.p_->next->next; delete t; return ++p; } struct tzdb_list::undocumented_helper { static void push_front(tzdb_list& db_list, tzdb* tzdb) NOEXCEPT { db_list.push_front(tzdb); } }; static tzdb_list create_tzdb() { tzdb_list tz_db; tzdb_list::undocumented_helper::push_front(tz_db, init_tzdb().release()); return tz_db; } tzdb_list& get_tzdb_list() { static tzdb_list tz_db = create_tzdb(); return tz_db; } #if !defined(ANDROID) && !defined(__ANDROID__) inline static char tolower(char c) { return static_cast(std::tolower(c)); } inline static void tolower(std::string& s) { for (auto& c : s) c = tolower(c); } inline static std::string get_alpha_word(std::istream& in) { ws(in); std::string s; while (!in.eof() && std::isalpha(in.peek())) s.push_back(static_cast(in.get())); return s; } #endif // !defined(ANDROID) && !defined(__ANDROID__) inline static bool is_prefix_of(std::string const& key, std::string const& value) { const size_t size = std::min(key.size(), value.size()); return key.compare(0, size, value, 0, size) == 0; } #if !defined(ANDROID) && !defined(__ANDROID__) static unsigned parse_month(std::istream& in) { static std::string const month_names[] = {"january", "february", "march", "april", "may", "june", "july", "august", "september", "october", "november", "december"}; auto s = get_alpha_word(in); tolower(s); auto m = std::find_if(std::begin(month_names), std::end(month_names), [&s](std::string const& m) { return is_prefix_of(s, m); }) - month_names; if (m >= std::end(month_names) - std::begin(month_names)) throw std::runtime_error("oops: bad month name: " + s); return static_cast(++m); } #endif // !defined(ANDROID) && !defined(__ANDROID__) #if !USE_OS_TZDB #ifdef _WIN32 static void sort_zone_mappings(std::vector& mappings) { std::sort(mappings.begin(), mappings.end(), [](const date::detail::timezone_mapping& lhs, const date::detail::timezone_mapping& rhs)->bool { auto other_result = lhs.other.compare(rhs.other); if (other_result < 0) return true; else if (other_result == 0) { auto territory_result = lhs.territory.compare(rhs.territory); if (territory_result < 0) return true; else if (territory_result == 0) { if (lhs.type < rhs.type) return true; } } return false; }); } static bool native_to_standard_timezone_name(const std::string& native_tz_name, std::string& standard_tz_name) { // TOOD! Need be a case insensitive compare? if (native_tz_name == "UTC") { standard_tz_name = "Etc/UTC"; return true; } standard_tz_name.clear(); // TODO! we can improve on linear search. const auto& mappings = date::get_tzdb().mappings; for (const auto& tzm : mappings) { if (tzm.other == native_tz_name) { standard_tz_name = tzm.type; return true; } } return false; } // Parse this XML file: // https://raw.githubusercontent.com/unicode-org/cldr/master/common/supplemental/windowsZones.xml // The parsing method is designed to be simple and quick. It is not overly // forgiving of change but it should diagnose basic format issues. // See timezone_mapping structure for more info. static std::vector load_timezone_mappings_from_xml_file(const std::string& input_path) { std::size_t line_num = 0; std::vector mappings; std::string line; file_streambuf ibuf(input_path); std::istream is(&ibuf); auto error = [&input_path, &line_num](const char* info) { std::string msg = "Error loading time zone mapping file \""; msg += input_path; msg += "\" at line "; msg += std::to_string(line_num); msg += ": "; msg += info; throw std::runtime_error(msg); }; // [optional space]a="b" auto read_attribute = [&line, &error] (const char* name, std::string& value, std::size_t startPos) ->std::size_t { value.clear(); // Skip leading space before attribute name. std::size_t spos = line.find_first_not_of(' ', startPos); if (spos == std::string::npos) spos = startPos; // Assume everything up to next = is the attribute name // and that an = will always delimit that. std::size_t epos = line.find('=', spos); if (epos == std::string::npos) error("Expected \'=\' right after attribute name."); std::size_t name_len = epos - spos; // Expect the name we find matches the name we expect. if (line.compare(spos, name_len, name) != 0) { std::string msg; msg = "Expected attribute name \'"; msg += name; msg += "\' around position "; msg += std::to_string(spos); msg += " but found something else."; error(msg.c_str()); } ++epos; // Skip the '=' that is after the attribute name. spos = epos; if (spos < line.length() && line[spos] == '\"') ++spos; // Skip the quote that is before the attribute value. else { std::string msg = "Expected '\"' to begin value of attribute \'"; msg += name; msg += "\'."; error(msg.c_str()); } epos = line.find('\"', spos); if (epos == std::string::npos) { std::string msg = "Expected '\"' to end value of attribute \'"; msg += name; msg += "\'."; error(msg.c_str()); } // Extract everything in between the quotes. Note no escaping is done. std::size_t value_len = epos - spos; value.assign(line, spos, value_len); ++epos; // Skip the quote that is after the attribute value; return epos; }; // Quick but not overly forgiving XML mapping file processing. bool mapTimezonesOpenTagFound = false; bool mapTimezonesCloseTagFound = false; std::size_t mapZonePos = std::string::npos; std::size_t mapTimezonesPos = std::string::npos; CONSTDATA char mapTimeZonesOpeningTag[] = { ""); mapTimezonesCloseTagFound = (mapTimezonesPos != std::string::npos); if (!mapTimezonesCloseTagFound) { std::size_t commentPos = line.find(" " << x.target_; } // leap_second leap_second::leap_second(const std::string& s, detail::undocumented) { using namespace date; std::istringstream in(s); in.exceptions(std::ios::failbit | std::ios::badbit); std::string word; int y; MonthDayTime date; in >> word >> y >> date; date_ = date.to_time_point(year(y)); } static bool file_exists(const std::string& filename) { #ifdef _WIN32 std::wstring wfilename = convert_utf8_to_utf16(filename); return ::_waccess(wfilename.c_str(), 0) == 0; #else return ::access(filename.c_str(), F_OK) == 0; #endif } #if HAS_REMOTE_API // CURL tools namespace { struct curl_global_init_and_cleanup { ~curl_global_init_and_cleanup() { ::curl_global_cleanup(); } curl_global_init_and_cleanup() { if (::curl_global_init(CURL_GLOBAL_DEFAULT) != 0) throw std::runtime_error("CURL global initialization failed"); } curl_global_init_and_cleanup(curl_global_init_and_cleanup const&) = delete; curl_global_init_and_cleanup& operator=(curl_global_init_and_cleanup const&) = delete; }; struct curl_deleter { void operator()(CURL* p) const { ::curl_easy_cleanup(p); } }; } // unnamed namespace static std::unique_ptr curl_init() { static const curl_global_init_and_cleanup _{}; return std::unique_ptr{::curl_easy_init()}; } static bool download_to_string(const std::string& url, std::string& str) { str.clear(); auto curl = curl_init(); if (!curl) return false; std::string version; curl_easy_setopt(curl.get(), CURLOPT_USERAGENT, "curl"); curl_easy_setopt(curl.get(), CURLOPT_URL, url.c_str()); curl_write_callback write_cb = [](char* contents, std::size_t size, std::size_t nmemb, void* userp) -> std::size_t { auto& userstr = *static_cast(userp); auto realsize = size * nmemb; userstr.append(contents, realsize); return realsize; }; curl_easy_setopt(curl.get(), CURLOPT_WRITEFUNCTION, write_cb); curl_easy_setopt(curl.get(), CURLOPT_WRITEDATA, &str); curl_easy_setopt(curl.get(), CURLOPT_SSL_VERIFYPEER, false); auto res = curl_easy_perform(curl.get()); return (res == CURLE_OK); } namespace { enum class download_file_options { binary, text }; } static bool download_to_file(const std::string& url, const std::string& local_filename, download_file_options opts, char* error_buffer) { auto curl = curl_init(); if (!curl) return false; curl_easy_setopt(curl.get(), CURLOPT_URL, url.c_str()); curl_easy_setopt(curl.get(), CURLOPT_SSL_VERIFYPEER, false); if (error_buffer) curl_easy_setopt(curl.get(), CURLOPT_ERRORBUFFER, error_buffer); curl_write_callback write_cb = [](char* contents, std::size_t size, std::size_t nmemb, void* userp) -> std::size_t { auto& of = *static_cast(userp); auto realsize = size * nmemb; of.write(contents, static_cast(realsize)); return realsize; }; curl_easy_setopt(curl.get(), CURLOPT_WRITEFUNCTION, write_cb); decltype(curl_easy_perform(curl.get())) res; { std::ofstream of(local_filename, opts == download_file_options::binary ? std::ofstream::out | std::ofstream::binary : std::ofstream::out); of.exceptions(std::ios::badbit); curl_easy_setopt(curl.get(), CURLOPT_WRITEDATA, &of); res = curl_easy_perform(curl.get()); } return res == CURLE_OK; } std::string remote_version() { std::string version; std::string str; if (download_to_string("https://www.iana.org/time-zones", str)) { CONSTDATA char db[] = "/time-zones/releases/tzdata"; CONSTDATA auto db_size = sizeof(db) - 1; auto p = str.find(db, 0, db_size); const int ver_str_len = 5; if (p != std::string::npos && p + (db_size + ver_str_len) <= str.size()) version = str.substr(p + db_size, ver_str_len); } return version; } // TODO! Using system() create a process and a console window. // This is useful to see what errors may occur but is slow and distracting. // Consider implementing this functionality more directly, such as // using _mkdir and CreateProcess etc. // But use the current means now as matches Unix implementations and while // in proof of concept / testing phase. // TODO! Use eventually. static bool remove_folder_and_subfolders(const std::string& folder) { # ifdef _WIN32 # if USE_SHELL_API // Delete the folder contents by deleting the folder. std::string cmd = "rd /s /q \""; cmd += folder; cmd += '\"'; return std::system(cmd.c_str()) == EXIT_SUCCESS; # else // !USE_SHELL_API // Create a buffer containing the path to delete. It must be terminated // by two nuls. Who designs these API's... std::vector from; from.assign(folder.begin(), folder.end()); from.push_back('\0'); from.push_back('\0'); SHFILEOPSTRUCT fo{}; // Zero initialize. fo.wFunc = FO_DELETE; fo.pFrom = from.data(); fo.fFlags = FOF_NO_UI; int ret = SHFileOperation(&fo); if (ret == 0 && !fo.fAnyOperationsAborted) return true; return false; # endif // !USE_SHELL_API # else // !_WIN32 # if USE_SHELL_API return std::system(("rm -R " + folder).c_str()) == EXIT_SUCCESS; # else // !USE_SHELL_API struct dir_deleter { dir_deleter() {} void operator()(DIR* d) const { if (d != nullptr) { int result = closedir(d); assert(result == 0); } } }; using closedir_ptr = std::unique_ptr; std::string filename; struct stat statbuf; std::size_t folder_len = folder.length(); struct dirent* p = nullptr; closedir_ptr d(opendir(folder.c_str())); bool r = d.get() != nullptr; while (r && (p=readdir(d.get())) != nullptr) { if (strcmp(p->d_name, ".") == 0 || strcmp(p->d_name, "..") == 0) continue; // + 2 for path delimiter and nul terminator. std::size_t buf_len = folder_len + strlen(p->d_name) + 2; filename.resize(buf_len); std::size_t path_len = static_cast( snprintf(&filename[0], buf_len, "%s/%s", folder.c_str(), p->d_name)); assert(path_len == buf_len - 1); filename.resize(path_len); if (stat(filename.c_str(), &statbuf) == 0) r = S_ISDIR(statbuf.st_mode) ? remove_folder_and_subfolders(filename) : unlink(filename.c_str()) == 0; } d.reset(); if (r) r = rmdir(folder.c_str()) == 0; return r; # endif // !USE_SHELL_API # endif // !_WIN32 } static bool make_directory(const std::string& folder) { # ifdef _WIN32 # if USE_SHELL_API // Re-create the folder. std::string cmd = "mkdir \""; cmd += folder; cmd += '\"'; return std::system(cmd.c_str()) == EXIT_SUCCESS; # else // !USE_SHELL_API return _mkdir(folder.c_str()) == 0; # endif // !USE_SHELL_API # else // !_WIN32 # if USE_SHELL_API return std::system(("mkdir -p " + folder).c_str()) == EXIT_SUCCESS; # else // !USE_SHELL_API return mkdir(folder.c_str(), 0777) == 0; # endif // !USE_SHELL_API # endif // !_WIN32 } static bool delete_file(const std::string& file) { # ifdef _WIN32 # if USE_SHELL_API std::string cmd = "del \""; cmd += file; cmd += '\"'; return std::system(cmd.c_str()) == 0; # else // !USE_SHELL_API return _unlink(file.c_str()) == 0; # endif // !USE_SHELL_API # else // !_WIN32 # if USE_SHELL_API return std::system(("rm " + file).c_str()) == EXIT_SUCCESS; # else // !USE_SHELL_API return unlink(file.c_str()) == 0; # endif // !USE_SHELL_API # endif // !_WIN32 } # ifdef _WIN32 static bool move_file(const std::string& from, const std::string& to) { # if USE_SHELL_API std::string cmd = "move \""; cmd += from; cmd += "\" \""; cmd += to; cmd += '\"'; return std::system(cmd.c_str()) == EXIT_SUCCESS; # else // !USE_SHELL_API return !!::MoveFile(from.c_str(), to.c_str()); # endif // !USE_SHELL_API } // Usually something like "c:\Program Files". static std::string get_program_folder() { return get_known_folder(FOLDERID_ProgramFiles); } // Note folder can and usually does contain spaces. static std::string get_unzip_program() { std::string path; // 7-Zip appears to note its location in the registry. // If that doesn't work, fall through and take a guess, but it will likely be wrong. HKEY hKey = nullptr; if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, "SOFTWARE\\7-Zip", 0, KEY_READ, &hKey) == ERROR_SUCCESS) { char value_buffer[MAX_PATH + 1]; // fyi 260 at time of writing. // in/out parameter. Documentation say that size is a count of bytes not chars. DWORD size = sizeof(value_buffer) - sizeof(value_buffer[0]); DWORD tzi_type = REG_SZ; // Testing shows Path key value is "C:\Program Files\7-Zip\" i.e. always with trailing \. bool got_value = (RegQueryValueExA(hKey, "Path", nullptr, &tzi_type, reinterpret_cast(value_buffer), &size) == ERROR_SUCCESS); RegCloseKey(hKey); // Close now incase of throw later. if (got_value) { // Function does not guarantee to null terminate. value_buffer[size / sizeof(value_buffer[0])] = '\0'; path = value_buffer; if (!path.empty()) { path += "7z.exe"; return path; } } } path += get_program_folder(); path += folder_delimiter; path += "7-Zip\\7z.exe"; return path; } # if !USE_SHELL_API static int run_program(const std::string& command) { STARTUPINFO si{}; si.cb = sizeof(si); PROCESS_INFORMATION pi{}; // Allegedly CreateProcess overwrites the command line. Ugh. std::string mutable_command(command); if (CreateProcess(nullptr, &mutable_command[0], nullptr, nullptr, FALSE, CREATE_NO_WINDOW, nullptr, nullptr, &si, &pi)) { WaitForSingleObject(pi.hProcess, INFINITE); DWORD exit_code; bool got_exit_code = !!GetExitCodeProcess(pi.hProcess, &exit_code); CloseHandle(pi.hProcess); CloseHandle(pi.hThread); // Not 100% sure about this still active thing is correct, // but I'm going with it because I *think* WaitForSingleObject might // return in some cases without INFINITE-ly waiting. // But why/wouldn't GetExitCodeProcess return false in that case? if (got_exit_code && exit_code != STILL_ACTIVE) return static_cast(exit_code); } return EXIT_FAILURE; } # endif // !USE_SHELL_API static std::string get_download_tar_file(const std::string& version) { auto file = get_install(); file += folder_delimiter; file += "tzdata"; file += version; file += ".tar"; return file; } static bool extract_gz_file(const std::string& version, const std::string& gz_file, const std::string& dest_folder) { auto unzip_prog = get_unzip_program(); bool unzip_result = false; // Use the unzip program to extract the tar file from the archive. // Aim to create a string like: // "C:\Program Files\7-Zip\7z.exe" x "C:\Users\SomeUser\Downloads\tzdata2016d.tar.gz" // -o"C:\Users\SomeUser\Downloads\tzdata" std::string cmd; cmd = '\"'; cmd += unzip_prog; cmd += "\" x \""; cmd += gz_file; cmd += "\" -o\""; cmd += dest_folder; cmd += '\"'; # if USE_SHELL_API // When using shelling out with std::system() extra quotes are required around the // whole command. It's weird but necessary it seems, see: // http://stackoverflow.com/q/27975969/576911 cmd = "\"" + cmd + "\""; if (std::system(cmd.c_str()) == EXIT_SUCCESS) unzip_result = true; # else // !USE_SHELL_API if (run_program(cmd) == EXIT_SUCCESS) unzip_result = true; # endif // !USE_SHELL_API if (unzip_result) delete_file(gz_file); // Use the unzip program extract the data from the tar file that was // just extracted from the archive. auto tar_file = get_download_tar_file(version); cmd = '\"'; cmd += unzip_prog; cmd += "\" x \""; cmd += tar_file; cmd += "\" -o\""; cmd += get_install(); cmd += '\"'; # if USE_SHELL_API cmd = "\"" + cmd + "\""; if (std::system(cmd.c_str()) == EXIT_SUCCESS) unzip_result = true; # else // !USE_SHELL_API if (run_program(cmd) == EXIT_SUCCESS) unzip_result = true; # endif // !USE_SHELL_API if (unzip_result) delete_file(tar_file); return unzip_result; } static std::string get_download_mapping_file(const std::string& version) { auto file = get_install() + version + "windowsZones.xml"; return file; } # else // !_WIN32 # if !USE_SHELL_API static int run_program(const char* prog, const char*const args[]) { pid_t pid = fork(); if (pid == -1) // Child failed to start. return EXIT_FAILURE; if (pid != 0) { // We are in the parent. Child started. Wait for it. pid_t ret; int status; while ((ret = waitpid(pid, &status, 0)) == -1) { if (errno != EINTR) break; } if (ret != -1) { if (WIFEXITED(status)) return WEXITSTATUS(status); } printf("Child issues!\n"); return EXIT_FAILURE; // Not sure what status of child is. } else // We are in the child process. Start the program the parent wants to run. { if (execv(prog, const_cast(args)) == -1) // Does not return. { perror("unreachable 0\n"); _Exit(127); } printf("unreachable 2\n"); } printf("unreachable 2\n"); // Unreachable. assert(false); exit(EXIT_FAILURE); return EXIT_FAILURE; } # endif // !USE_SHELL_API static bool extract_gz_file(const std::string&, const std::string& gz_file, const std::string&) { # if USE_SHELL_API bool unzipped = std::system(("tar -xzf " + gz_file + " -C " + get_install()).c_str()) == EXIT_SUCCESS; # else // !USE_SHELL_API const char prog[] = {"/usr/bin/tar"}; const char*const args[] = { prog, "-xzf", gz_file.c_str(), "-C", get_install().c_str(), nullptr }; bool unzipped = (run_program(prog, args) == EXIT_SUCCESS); # endif // !USE_SHELL_API if (unzipped) { delete_file(gz_file); return true; } return false; } # endif // !_WIN32 bool remote_download(const std::string& version, char* error_buffer) { assert(!version.empty()); # ifdef _WIN32 // Download folder should be always available for Windows # else // !_WIN32 // Create download folder if it does not exist on UNIX system auto download_folder = get_install(); if (!file_exists(download_folder)) { if (!make_directory(download_folder)) return false; } # endif // _WIN32 auto url = "https://data.iana.org/time-zones/releases/tzdata" + version + ".tar.gz"; bool result = download_to_file(url, get_download_gz_file(version), download_file_options::binary, error_buffer); # ifdef _WIN32 if (result) { auto mapping_file = get_download_mapping_file(version); result = download_to_file( "https://raw.githubusercontent.com/unicode-org/cldr/master/" "common/supplemental/windowsZones.xml", mapping_file, download_file_options::text, error_buffer); } # endif // _WIN32 return result; } bool remote_install(const std::string& version) { auto success = false; assert(!version.empty()); std::string install = get_install(); auto gz_file = get_download_gz_file(version); if (file_exists(gz_file)) { if (file_exists(install)) remove_folder_and_subfolders(install); if (make_directory(install)) { if (extract_gz_file(version, gz_file, install)) success = true; # ifdef _WIN32 auto mapping_file_source = get_download_mapping_file(version); auto mapping_file_dest = get_install(); mapping_file_dest += folder_delimiter; mapping_file_dest += "windowsZones.xml"; if (!move_file(mapping_file_source, mapping_file_dest)) success = false; # endif // _WIN32 } } return success; } #endif // HAS_REMOTE_API static std::string get_version(const std::string& path) { std::string version; std::string path_version = path + "version"; if (file_exists(path_version)) { file_streambuf inbuf(path_version); std::istream infile(&inbuf); infile >> version; if (!infile.fail()) return version; } std::string path_news = path + "NEWS"; if (file_exists(path_news)) { file_streambuf inbuf(path_news); std::istream infile(&inbuf); while (infile) { infile >> version; if (version == "Release") { infile >> version; return version; } } } throw std::runtime_error("Unable to get Timezone database version from " + path); } static std::unique_ptr init_tzdb() { using namespace date; const std::string install = get_install(); const std::string path = install + folder_delimiter; std::string line; bool continue_zone = false; std::unique_ptr db(new tzdb); #if AUTO_DOWNLOAD if (!file_exists(install)) { auto rv = remote_version(); if (!rv.empty() && remote_download(rv)) { if (!remote_install(rv)) { std::string msg = "Timezone database version \""; msg += rv; msg += "\" did not install correctly to \""; msg += install; msg += "\""; throw std::runtime_error(msg); } } if (!file_exists(install)) { std::string msg = "Timezone database not found at \""; msg += install; msg += "\""; throw std::runtime_error(msg); } db->version = get_version(path); } else { db->version = get_version(path); auto rv = remote_version(); if (!rv.empty() && db->version != rv) { if (remote_download(rv)) { remote_install(rv); db->version = get_version(path); } } } #else // !AUTO_DOWNLOAD if (!file_exists(install)) { std::string msg = "Timezone database not found at \""; msg += install; msg += "\""; throw std::runtime_error(msg); } db->version = get_version(path); #endif // !AUTO_DOWNLOAD CONSTDATA char*const files[] = { "africa", "antarctica", "asia", "australasia", "backward", "etcetera", "europe", "pacificnew", "northamerica", "southamerica", "systemv", "leapseconds" }; for (const auto& filename : files) { std::string file_path = path + filename; if (!file_exists(file_path)) { continue; } file_streambuf inbuf(file_path); std::istream infile(&inbuf); while (infile) { std::getline(infile, line); if (!line.empty() && line[0] != '#') { std::istringstream in(line); std::string word; in >> word; tolower(word); if (is_prefix_of(word, "rule")) { db->rules.push_back(Rule(line)); continue_zone = false; } else if (is_prefix_of(word, "link")) { db->links.push_back(time_zone_link(line)); continue_zone = false; } else if (is_prefix_of(word, "leap")) { db->leap_seconds.push_back(leap_second(line, detail::undocumented{})); continue_zone = false; } else if (is_prefix_of(word, "zone")) { db->zones.push_back(time_zone(line, detail::undocumented{})); continue_zone = true; } else if (line[0] == '\t' && continue_zone) { db->zones.back().add(line); } else if (word.size() > 0 && word[0] == '#') { continue; } else { std::cerr << line << '\n'; } } } } std::sort(db->rules.begin(), db->rules.end()); Rule::split_overlaps(db->rules); std::sort(db->zones.begin(), db->zones.end()); db->zones.shrink_to_fit(); std::sort(db->links.begin(), db->links.end()); db->links.shrink_to_fit(); std::sort(db->leap_seconds.begin(), db->leap_seconds.end()); db->leap_seconds.shrink_to_fit(); #ifdef _WIN32 std::string mapping_file = get_install() + folder_delimiter + "windowsZones.xml"; db->mappings = load_timezone_mappings_from_xml_file(mapping_file); sort_zone_mappings(db->mappings); #endif // _WIN32 return db; } const tzdb& reload_tzdb() { #if AUTO_DOWNLOAD auto const& v = get_tzdb_list().front().version; if (!v.empty() && v == remote_version()) return get_tzdb_list().front(); #endif // AUTO_DOWNLOAD tzdb_list::undocumented_helper::push_front(get_tzdb_list(), init_tzdb().release()); return get_tzdb_list().front(); } #endif // !USE_OS_TZDB const tzdb& get_tzdb() { return get_tzdb_list().front(); } namespace { class recursion_limiter { unsigned depth_ = 0; unsigned limit_; class restore_recursion_depth; public: recursion_limiter(recursion_limiter const&) = delete; recursion_limiter& operator=(recursion_limiter const&) = delete; explicit constexpr recursion_limiter(unsigned limit) noexcept; restore_recursion_depth count(); }; class recursion_limiter::restore_recursion_depth { recursion_limiter* rc_; public: ~restore_recursion_depth(); restore_recursion_depth(restore_recursion_depth&&) = default; explicit restore_recursion_depth(recursion_limiter* rc) noexcept; }; inline recursion_limiter::restore_recursion_depth::~restore_recursion_depth() { --(rc_->depth_); } inline recursion_limiter::restore_recursion_depth::restore_recursion_depth(recursion_limiter* rc) noexcept : rc_{rc} {} inline constexpr recursion_limiter::recursion_limiter(unsigned limit) noexcept : limit_{limit} { } inline recursion_limiter::restore_recursion_depth recursion_limiter::count() { ++depth_; if (depth_ > limit_) throw std::runtime_error("recursion limit of " + std::to_string(limit_) + " exceeded"); return restore_recursion_depth{this}; } } // unnamed namespace const time_zone* #if HAS_STRING_VIEW tzdb::locate_zone(std::string_view tz_name) const #else tzdb::locate_zone(const std::string& tz_name) const #endif { // If a link-to-link chain exceeds this limit, give up thread_local recursion_limiter rc{10}; auto restore_count = rc.count(); auto zi = std::lower_bound(zones.begin(), zones.end(), tz_name, #if HAS_STRING_VIEW [](const time_zone& z, const std::string_view& nm) #else [](const time_zone& z, const std::string& nm) #endif { return z.name() < nm; }); if (zi == zones.end() || zi->name() != tz_name) { #if !USE_OS_TZDB auto li = std::lower_bound(links.begin(), links.end(), tz_name, #if HAS_STRING_VIEW [](const time_zone_link& z, const std::string_view& nm) #else [](const time_zone_link& z, const std::string& nm) #endif { return z.name() < nm; }); if (li != links.end() && li->name() == tz_name) { return locate_zone(li->target()); } #endif // !USE_OS_TZDB throw std::runtime_error(std::string(tz_name) + " not found in timezone database"); } return &*zi; } const time_zone* #if HAS_STRING_VIEW locate_zone(std::string_view tz_name) #else locate_zone(const std::string& tz_name) #endif { return get_tzdb().locate_zone(tz_name); } #if USE_OS_TZDB std::ostream& operator<<(std::ostream& os, const tzdb& db) { os << "Version: " << db.version << "\n\n"; for (const auto& x : db.zones) os << x << '\n'; os << '\n'; for (const auto& x : db.leap_seconds) os << x << '\n'; return os; } #else // !USE_OS_TZDB std::ostream& operator<<(std::ostream& os, const tzdb& db) { os << "Version: " << db.version << '\n'; std::string title("--------------------------------------------" "--------------------------------------------\n" "Name ""Start Y ""End Y " "Beginning ""Offset " "Designator\n" "--------------------------------------------" "--------------------------------------------\n"); int count = 0; for (const auto& x : db.rules) { if (count++ % 50 == 0) os << title; os << x << '\n'; } os << '\n'; title = std::string("---------------------------------------------------------" "--------------------------------------------------------\n" "Name ""Offset " "Rule ""Abrev ""Until\n" "---------------------------------------------------------" "--------------------------------------------------------\n"); count = 0; for (const auto& x : db.zones) { if (count++ % 10 == 0) os << title; os << x << '\n'; } os << '\n'; title = std::string("---------------------------------------------------------" "--------------------------------------------------------\n" "Alias ""To\n" "---------------------------------------------------------" "--------------------------------------------------------\n"); count = 0; for (const auto& x : db.links) { if (count++ % 45 == 0) os << title; os << x << '\n'; } os << '\n'; title = std::string("---------------------------------------------------------" "--------------------------------------------------------\n" "Leap second on\n" "---------------------------------------------------------" "--------------------------------------------------------\n"); os << title; for (const auto& x : db.leap_seconds) os << x << '\n'; return os; } #endif // !USE_OS_TZDB // ----------------------- #ifdef _WIN32 static std::string getTimeZoneKeyName() { DYNAMIC_TIME_ZONE_INFORMATION dtzi{}; auto result = GetDynamicTimeZoneInformation(&dtzi); if (result == TIME_ZONE_ID_INVALID) throw std::runtime_error("current_zone(): GetDynamicTimeZoneInformation()" " reported TIME_ZONE_ID_INVALID."); auto wlen = wcslen(dtzi.TimeZoneKeyName); char buf[128] = {}; assert(sizeof(buf) >= wlen+1); wcstombs(buf, dtzi.TimeZoneKeyName, wlen); if (strcmp(buf, "Coordinated Universal Time") == 0) return "UTC"; return buf; } const time_zone* tzdb::current_zone() const { std::string win_tzid = getTimeZoneKeyName(); std::string standard_tzid; if (!native_to_standard_timezone_name(win_tzid, standard_tzid)) { std::string msg; msg = "current_zone() failed: A mapping from the Windows Time Zone id \""; msg += win_tzid; msg += "\" was not found in the time zone mapping database."; throw std::runtime_error(msg); } return locate_zone(standard_tzid); } #else // !_WIN32 #if HAS_STRING_VIEW static std::string_view extract_tz_name(char const* rp) { using namespace std; string_view result = rp; CONSTDATA string_view zoneinfo = "zoneinfo"; size_t pos = result.rfind(zoneinfo); if (pos == result.npos) throw runtime_error( "current_zone() failed to find \"zoneinfo\" in " + string(result)); pos = result.find('/', pos); result.remove_prefix(pos + 1); return result; } #else // !HAS_STRING_VIEW static std::string extract_tz_name(char const* rp) { using namespace std; string result = rp; CONSTDATA char zoneinfo[] = "zoneinfo"; size_t pos = result.rfind(zoneinfo); if (pos == result.npos) throw runtime_error( "current_zone() failed to find \"zoneinfo\" in " + result); pos = result.find('/', pos); result.erase(0, pos + 1); return result; } #endif // HAS_STRING_VIEW static bool sniff_realpath(const char* timezone) { using namespace std; unique_ptr rp(realpath(timezone, nullptr), free); if (rp.get() == nullptr) throw system_error(errno, system_category(), "realpath() failed"); auto result = extract_tz_name(rp.get()); if (result.find("posix") == 0) return false; return result != "posixrules"; } const time_zone* tzdb::current_zone() const { // On some OS's a file called /etc/localtime may // exist and it may be either a real file // containing time zone details or a symlink to such a file. // On MacOS and BSD Unix if this file is a symlink it // might resolve to a path like this: // "/usr/share/zoneinfo/America/Los_Angeles" // If it does, we try to determine the current // timezone from the remainder of the path by removing the prefix // and hoping the rest resolves to a valid timezone. // It may not always work though. If it doesn't then an // exception will be thrown by local_timezone. // The path may also take a relative form: // "../usr/share/zoneinfo/America/Los_Angeles". { struct stat sb; CONSTDATA auto timezone = "/etc/localtime"; if (lstat(timezone, &sb) == 0 && S_ISLNK(sb.st_mode) && sb.st_size > 0) { using namespace std; static const bool use_realpath = sniff_realpath(timezone); if (use_realpath) { unique_ptr rp(realpath(timezone, nullptr), free); if (rp.get() == nullptr) throw system_error(errno, system_category(), "realpath() failed"); return locate_zone(extract_tz_name(rp.get())); } else { // +1 because st_size doesn't include the '\0' terminator const auto rp_size = sb.st_size + 1; unique_ptr rp(new char[rp_size]); const auto rp_length = readlink(timezone, rp.get(), rp_size); if (rp_length <= 0) throw system_error(errno, system_category(), "readlink() failed"); rp.get()[rp_length] = '\0'; // readlink doesn't null-terminate return locate_zone(extract_tz_name(rp.get())); } } } // On embedded systems e.g. buildroot with uclibc the timezone is linked // into /etc/TZ which is a symlink to path like this: // "/usr/share/zoneinfo/uclibc/America/Los_Angeles" // If it does, we try to determine the current // timezone from the remainder of the path by removing the prefix // and hoping the rest resolves to valid timezone. // It may not always work though. If it doesn't then an // exception will be thrown by local_timezone. // The path may also take a relative form: // "../usr/share/zoneinfo/uclibc/America/Los_Angeles". { struct stat sb; CONSTDATA auto timezone = "/etc/TZ"; if (lstat(timezone, &sb) == 0 && S_ISLNK(sb.st_mode) && sb.st_size > 0) { using namespace std; string result; unique_ptr rp(new char[sb.st_size]); const auto rp_length = readlink(timezone, rp.get(), sb.st_size); if (rp_length > 0) result = string(rp.get(), rp_length); // readlink doesn't null-terminate else throw system_error(errno, system_category(), "readlink() failed"); const size_t pos = result.find(get_tz_dir()); if (pos != result.npos) result.erase(0, get_tz_dir().size() + 1 + pos); return locate_zone(result); } } { // On some versions of some linux distro's (e.g. Ubuntu), // the current timezone might be in the first line of // the /etc/timezone file. std::ifstream timezone_file("/etc/timezone"); if (timezone_file.is_open()) { std::string result; std::getline(timezone_file, result); if (!result.empty()) return locate_zone(result); } // Fall through to try other means. } { // On some versions of some bsd distro's (e.g. FreeBSD), // the current timezone might be in the first line of // the /var/db/zoneinfo file. std::ifstream timezone_file("/var/db/zoneinfo"); if (timezone_file.is_open()) { std::string result; std::getline(timezone_file, result); if (!result.empty()) return locate_zone(result); } // Fall through to try other means. } { // On some versions of some bsd distro's (e.g. iOS), // it is not possible to use file based approach, // we switch to system API, calling functions in // CoreFoundation framework. #if TARGET_OS_IPHONE std::string result = date::iOSUtils::get_current_timezone(); if (!result.empty()) return locate_zone(result); #endif // Fall through to try other means. } { // On Android, it is not possible to use file based approach either, // we have to ask the value of `persist.sys.timezone` system property #if defined(ANDROID) || defined(__ANDROID__) char sys_timezone[PROP_VALUE_MAX]; if (__system_property_get("persist.sys.timezone", sys_timezone) > 0) { return locate_zone(sys_timezone); } #endif // defined(ANDROID) || defined(__ANDROID__) // Fall through to try other means. } { // On some versions of some linux distro's (e.g. Red Hat), // the current timezone might be in the first line of // the /etc/sysconfig/clock file as: // ZONE="US/Eastern" std::ifstream timezone_file("/etc/sysconfig/clock"); std::string result; while (timezone_file) { std::getline(timezone_file, result); auto p = result.find("ZONE=\""); if (p != std::string::npos) { result.erase(0, p+6); result.erase(result.rfind('"')); return locate_zone(result); } } // Fall through to try other means. } // On OpenWRT we need to check /etc/config/system // It will have a line with the following structure // ... // option zoneName 'Europe/Berlin' // ... { std::ifstream timezone_file("/etc/config/system"); if (timezone_file.is_open()) { for(std::string result; std::getline(timezone_file, result);) { std::string findStr = "option zoneName '"; size_t startPos = result.find(findStr); if (startPos != std::string::npos) { size_t endPos = result.find("'", startPos + findStr.size()); return locate_zone(result.substr(startPos + findStr.size(), endPos - startPos - findStr.size())); } } } } throw std::runtime_error("Could not get current timezone"); } #endif // !_WIN32 const time_zone* current_zone() { return get_tzdb().current_zone(); } } // namespace date #if defined(__GNUC__) && __GNUC__ < 5 # pragma GCC diagnostic pop #endif