Browse Source

Init repository, extract Kosmorrolib

tags/v0.10.0
Jérôme Deuchnord 3 years ago
commit
a649f3e1ab
13 changed files with 1258 additions and 0 deletions
  1. +19
    -0
      Pipfile
  2. +330
    -0
      Pipfile.lock
  3. +21
    -0
      kosmorrolib/__init__.py
  4. BIN
      kosmorrolib/__pycache__/dateutil.cpython-39.pyc
  5. +117
    -0
      kosmorrolib/core.py
  6. +223
    -0
      kosmorrolib/data.py
  7. +28
    -0
      kosmorrolib/dateutil.py
  8. +41
    -0
      kosmorrolib/enum.py
  9. +163
    -0
      kosmorrolib/ephemerides.py
  10. +224
    -0
      kosmorrolib/events.py
  11. +40
    -0
      kosmorrolib/exceptions.py
  12. +46
    -0
      kosmorrolib/version.py
  13. +6
    -0
      tests.py

+ 19
- 0
Pipfile View File

@@ -0,0 +1,19 @@
[[source]]
name = "pypi"
url = "https://pypi.org/simple"
verify_ssl = true

[dev-packages]
pylintfileheader = "*"
pylint = "*"
babel = "*"
unittest-data-provider = "*"
coveralls = "*"

[packages]
skyfield = ">=1.32.0,<2.0.0"
numpy = ">=1.17.0,<2.0.0"
python-dateutil = "*"

[requires]
python_version = "3"

+ 330
- 0
Pipfile.lock View File

@@ -0,0 +1,330 @@
{
"_meta": {
"hash": {
"sha256": "05fc0ad542c6b81f02f9f1d6c4dbf8d2743ab07678c5c42aa3f7739c9aee7e36"
},
"pipfile-spec": 6,
"requires": {
"python_version": "3"
},
"sources": [
{
"name": "pypi",
"url": "https://pypi.org/simple",
"verify_ssl": true
}
]
},
"default": {
"certifi": {
"hashes": [
"sha256:1a4995114262bffbc2413b159f2a1a480c969de6e6eb13ee966d470af86af59c",
"sha256:719a74fb9e33b9bd44cc7f3a8d94bc35e4049deebe19ba7d8e108280cfd59830"
],
"version": "==2020.12.5"
},
"jplephem": {
"hashes": [
"sha256:e0017de1a45015b247faa8d15ebf35d2014abe949135f3703a3252adb96e43b1"
],
"version": "==2.15"
},
"numpy": {
"hashes": [
"sha256:032be656d89bbf786d743fee11d01ef318b0781281241997558fa7950028dd29",
"sha256:104f5e90b143dbf298361a99ac1af4cf59131218a045ebf4ee5990b83cff5fab",
"sha256:125a0e10ddd99a874fd357bfa1b636cd58deb78ba4a30b5ddb09f645c3512e04",
"sha256:12e4ba5c6420917571f1a5becc9338abbde71dd811ce40b37ba62dec7b39af6d",
"sha256:13adf545732bb23a796914fe5f891a12bd74cf3d2986eed7b7eba2941eea1590",
"sha256:2d7e27442599104ee08f4faed56bb87c55f8b10a5494ac2ead5c98a4b289e61f",
"sha256:3bc63486a870294683980d76ec1e3efc786295ae00128f9ea38e2c6e74d5a60a",
"sha256:3d3087e24e354c18fb35c454026af3ed8997cfd4997765266897c68d724e4845",
"sha256:4ed8e96dc146e12c1c5cdd6fb9fd0757f2ba66048bf94c5126b7efebd12d0090",
"sha256:60759ab15c94dd0e1ed88241fd4fa3312db4e91d2c8f5a2d4cf3863fad83d65b",
"sha256:65410c7f4398a0047eea5cca9b74009ea61178efd78d1be9847fac1d6716ec1e",
"sha256:66b467adfcf628f66ea4ac6430ded0614f5cc06ba530d09571ea404789064adc",
"sha256:7199109fa46277be503393be9250b983f325880766f847885607d9b13848f257",
"sha256:72251e43ac426ff98ea802a931922c79b8d7596480300eb9f1b1e45e0543571e",
"sha256:89e5336f2bec0c726ac7e7cdae181b325a9c0ee24e604704ed830d241c5e47ff",
"sha256:89f937b13b8dd17b0099c7c2e22066883c86ca1575a975f754babc8fbf8d69a9",
"sha256:9c94cab5054bad82a70b2e77741271790304651d584e2cdfe2041488e753863b",
"sha256:9eb551d122fadca7774b97db8a112b77231dcccda8e91a5bc99e79890797175e",
"sha256:a1d7995d1023335e67fb070b2fae6f5968f5be3802b15ad6d79d81ecaa014fe0",
"sha256:ae61f02b84a0211abb56462a3b6cd1e7ec39d466d3160eb4e1da8bf6717cdbeb",
"sha256:b9410c0b6fed4a22554f072a86c361e417f0258838957b78bd063bde2c7f841f",
"sha256:c26287dfc888cf1e65181f39ea75e11f42ffc4f4529e5bd19add57ad458996e2",
"sha256:c91ec9569facd4757ade0888371eced2ecf49e7982ce5634cc2cf4e7331a4b14",
"sha256:ecb5b74c702358cdc21268ff4c37f7466357871f53a30e6f84c686952bef16a9"
],
"index": "pypi",
"version": "==1.20.1"
},
"python-dateutil": {
"hashes": [
"sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c",
"sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a"
],
"index": "pypi",
"version": "==2.8.1"
},
"sgp4": {
"hashes": [
"sha256:0864ba1062418b1b3e23e38e90032dd2c6be1f09b66c5026a18f8171548720a9",
"sha256:1601f590950cd34ffc54c8c2811a228632d394c1e28a74054373638f7ead4801",
"sha256:2a75446b7299217f7e66002677226dab4e41b1ad62ce7fb238c0947071655899",
"sha256:2ad7a1c6dde04591a847c85397151e2adb458d889838a9e10177055bf373ec4a",
"sha256:2c21fc1f47882136883390497f92afd387d79c35f23a534b652ae44e02da0518",
"sha256:318309f4efa05361a64498ec0ac8e42a288439efe9cce6f823366c792d645e5c",
"sha256:39acc3f6a5efb9ccf76e2d3fbe4b56c9c0de10db0d5d4cfdf421b0969bbaa1e6",
"sha256:41bcb23f9fe020fa41d1729eebdff97144a6c884260ce50065e9201c45a989f8",
"sha256:71db20e277b639c05287302521e7c56fd9f77d081bb7a5f251294c18af188e43",
"sha256:7ea354472687dd54a40c0199238e6d22e4b86ab4d5eb356353be8559816f2754",
"sha256:89b42139eefd741724b6e41b80510cdbd76b0bda8f85d6cb37564051d119c384",
"sha256:93edb356869cd0c4c8b715523d26555c69bf81cebad23e5a31abdd5623e8819b",
"sha256:967ac911e1ffbd795df97e89c17f0c07fb2c782ae16f02b2bed2f9ad834cc7f9",
"sha256:b59509b488c0674c29806c181238a4d4868797122fb8e6681f7a3e5540a25403",
"sha256:c76a8db1df422a8473a680fcfb3c6b4cfa0318cf0e7fac9ea1b898f898139b8f",
"sha256:d33c185606eeeb7067ab3626eccdfdc4ea74346093fae646541ce8998c861540",
"sha256:de3ddc1a8a01a8b9f67008d2da85376d59aebac464a54477e11f459119de5308",
"sha256:df87ca4b6eac69b7ee2a6c0f7a3a29d9eda9785e21767e320d768f14170dd693",
"sha256:e303c82a3fc51a73cf0f69ee3215b25c299d84fb766e522a71136ad6f9d3a6c0",
"sha256:e576d9f4721d6380d6a7b9a9ca4dbc863ba9bd6f9242b4df12c0f68b50282f45",
"sha256:f45d0a205fb18919ff83ca449d483e6306721365d5ea52d63f86d91b6ca967c0",
"sha256:f5e6787d59683bfa5c9e1b88210d7bafc36b2fd8799438f30e1effa7da76764c",
"sha256:f7fc5b55497d79fe638fe203bc634332cea3f466d3f08910a956f97477cdecbb",
"sha256:fec4b597c4cc3dd330c9fd049c2d88ccea16aac474a57343082fa17fe1f84a05"
],
"version": "==2.17"
},
"six": {
"hashes": [
"sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259",
"sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'",
"version": "==1.15.0"
},
"skyfield": {
"hashes": [
"sha256:64d2716187b94ccb587ec6db46ecb252fb14ecc3b32ef37ce6e90683bb5956cb"
],
"index": "pypi",
"version": "==1.37"
}
},
"develop": {
"astroid": {
"hashes": [
"sha256:87ae7f2398b8a0ae5638ddecf9987f081b756e0e9fc071aeebdca525671fc4dc",
"sha256:b31c92f545517dcc452f284bc9c044050862fbe6d93d2b3de4a215a6b384bf0d"
],
"markers": "python_version >= '3.6'",
"version": "==2.5.0"
},
"babel": {
"hashes": [
"sha256:9d35c22fcc79893c3ecc85ac4a56cde1ecf3f19c540bba0922308a6c06ca6fa5",
"sha256:da031ab54472314f210b0adcff1588ee5d1d1d0ba4dbd07b94dba82bde791e05"
],
"index": "pypi",
"version": "==2.9.0"
},
"certifi": {
"hashes": [
"sha256:1a4995114262bffbc2413b159f2a1a480c969de6e6eb13ee966d470af86af59c",
"sha256:719a74fb9e33b9bd44cc7f3a8d94bc35e4049deebe19ba7d8e108280cfd59830"
],
"version": "==2020.12.5"
},
"chardet": {
"hashes": [
"sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa",
"sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
"version": "==4.0.0"
},
"coverage": {
"hashes": [
"sha256:03ed2a641e412e42cc35c244508cf186015c217f0e4d496bf6d7078ebe837ae7",
"sha256:04b14e45d6a8e159c9767ae57ecb34563ad93440fc1b26516a89ceb5b33c1ad5",
"sha256:0cdde51bfcf6b6bd862ee9be324521ec619b20590787d1655d005c3fb175005f",
"sha256:0f48fc7dc82ee14aeaedb986e175a429d24129b7eada1b7e94a864e4f0644dde",
"sha256:107d327071061fd4f4a2587d14c389a27e4e5c93c7cba5f1f59987181903902f",
"sha256:1375bb8b88cb050a2d4e0da901001347a44302aeadb8ceb4b6e5aa373b8ea68f",
"sha256:14a9f1887591684fb59fdba8feef7123a0da2424b0652e1b58dd5b9a7bb1188c",
"sha256:16baa799ec09cc0dcb43a10680573269d407c159325972dd7114ee7649e56c66",
"sha256:1b811662ecf72eb2d08872731636aee6559cae21862c36f74703be727b45df90",
"sha256:1ccae21a076d3d5f471700f6d30eb486da1626c380b23c70ae32ab823e453337",
"sha256:2f2cf7a42d4b7654c9a67b9d091ec24374f7c58794858bff632a2039cb15984d",
"sha256:322549b880b2d746a7672bf6ff9ed3f895e9c9f108b714e7360292aa5c5d7cf4",
"sha256:32ab83016c24c5cf3db2943286b85b0a172dae08c58d0f53875235219b676409",
"sha256:3fe50f1cac369b02d34ad904dfe0771acc483f82a1b54c5e93632916ba847b37",
"sha256:4a780807e80479f281d47ee4af2eb2df3e4ccf4723484f77da0bb49d027e40a1",
"sha256:4a8eb7785bd23565b542b01fb39115a975fefb4a82f23d407503eee2c0106247",
"sha256:5bee3970617b3d74759b2d2df2f6a327d372f9732f9ccbf03fa591b5f7581e39",
"sha256:60a3307a84ec60578accd35d7f0c71a3a971430ed7eca6567399d2b50ef37b8c",
"sha256:6625e52b6f346a283c3d563d1fd8bae8956daafc64bb5bbd2b8f8a07608e3994",
"sha256:66a5aae8233d766a877c5ef293ec5ab9520929c2578fd2069308a98b7374ea8c",
"sha256:68fb816a5dd901c6aff352ce49e2a0ffadacdf9b6fae282a69e7a16a02dad5fb",
"sha256:6b588b5cf51dc0fd1c9e19f622457cc74b7d26fe295432e434525f1c0fae02bc",
"sha256:6c4d7165a4e8f41eca6b990c12ee7f44fef3932fac48ca32cecb3a1b2223c21f",
"sha256:6d2e262e5e8da6fa56e774fb8e2643417351427604c2b177f8e8c5f75fc928ca",
"sha256:6d9c88b787638a451f41f97446a1c9fd416e669b4d9717ae4615bd29de1ac135",
"sha256:755c56beeacac6a24c8e1074f89f34f4373abce8b662470d3aa719ae304931f3",
"sha256:7e40d3f8eb472c1509b12ac2a7e24158ec352fc8567b77ab02c0db053927e339",
"sha256:812eaf4939ef2284d29653bcfee9665f11f013724f07258928f849a2306ea9f9",
"sha256:84df004223fd0550d0ea7a37882e5c889f3c6d45535c639ce9802293b39cd5c9",
"sha256:859f0add98707b182b4867359e12bde806b82483fb12a9ae868a77880fc3b7af",
"sha256:87c4b38288f71acd2106f5d94f575bc2136ea2887fdb5dfe18003c881fa6b370",
"sha256:89fc12c6371bf963809abc46cced4a01ca4f99cba17be5e7d416ed7ef1245d19",
"sha256:9564ac7eb1652c3701ac691ca72934dd3009997c81266807aef924012df2f4b3",
"sha256:9754a5c265f991317de2bac0c70a746efc2b695cf4d49f5d2cddeac36544fb44",
"sha256:a565f48c4aae72d1d3d3f8e8fb7218f5609c964e9c6f68604608e5958b9c60c3",
"sha256:a636160680c6e526b84f85d304e2f0bb4e94f8284dd765a1911de9a40450b10a",
"sha256:a839e25f07e428a87d17d857d9935dd743130e77ff46524abb992b962eb2076c",
"sha256:b62046592b44263fa7570f1117d372ae3f310222af1fc1407416f037fb3af21b",
"sha256:b7f7421841f8db443855d2854e25914a79a1ff48ae92f70d0a5c2f8907ab98c9",
"sha256:ba7ca81b6d60a9f7a0b4b4e175dcc38e8fef4992673d9d6e6879fd6de00dd9b8",
"sha256:bb32ca14b4d04e172c541c69eec5f385f9a075b38fb22d765d8b0ce3af3a0c22",
"sha256:c0ff1c1b4d13e2240821ef23c1efb1f009207cb3f56e16986f713c2b0e7cd37f",
"sha256:c669b440ce46ae3abe9b2d44a913b5fd86bb19eb14a8701e88e3918902ecd345",
"sha256:c67734cff78383a1f23ceba3b3239c7deefc62ac2b05fa6a47bcd565771e5880",
"sha256:c6809ebcbf6c1049002b9ac09c127ae43929042ec1f1dbd8bb1615f7cd9f70a0",
"sha256:cd601187476c6bed26a0398353212684c427e10a903aeafa6da40c63309d438b",
"sha256:ebfa374067af240d079ef97b8064478f3bf71038b78b017eb6ec93ede1b6bcec",
"sha256:fbb17c0d0822684b7d6c09915677a32319f16ff1115df5ec05bdcaaee40b35f3",
"sha256:fff1f3a586246110f34dc762098b5afd2de88de507559e63553d7da643053786"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'",
"version": "==5.4"
},
"coveralls": {
"hashes": [
"sha256:5399c0565ab822a70a477f7031f6c88a9dd196b3de2877b3facb43b51bd13434",
"sha256:f8384968c57dee4b7133ae701ecdad88e85e30597d496dcba0d7fbb470dca41f"
],
"index": "pypi",
"version": "==3.0.0"
},
"docopt": {
"hashes": [
"sha256:49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491"
],
"version": "==0.6.2"
},
"idna": {
"hashes": [
"sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6",
"sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==2.10"
},
"isort": {
"hashes": [
"sha256:c729845434366216d320e936b8ad6f9d681aab72dc7cbc2d51bedc3582f3ad1e",
"sha256:fff4f0c04e1825522ce6949973e83110a6e907750cd92d128b0d14aaaadbffdc"
],
"markers": "python_version >= '3.6' and python_version < '4'",
"version": "==5.7.0"
},
"lazy-object-proxy": {
"hashes": [
"sha256:1d33d6f789697f401b75ce08e73b1de567b947740f768376631079290118ad39",
"sha256:2f2de8f8ac0be3e40d17730e0600619d35c78c13a099ea91ef7fb4ad944ce694",
"sha256:3782931963dc89e0e9a0ae4348b44762e868ea280e4f8c233b537852a8996ab9",
"sha256:37d9c34b96cca6787fe014aeb651217944a967a5b165e2cacb6b858d2997ab84",
"sha256:38c3865bd220bd983fcaa9aa11462619e84a71233bafd9c880f7b1cb753ca7fa",
"sha256:429c4d1862f3fc37cd56304d880f2eae5bd0da83bdef889f3bd66458aac49128",
"sha256:522b7c94b524389f4a4094c4bf04c2b02228454ddd17c1a9b2801fac1d754871",
"sha256:57fb5c5504ddd45ed420b5b6461a78f58cbb0c1b0cbd9cd5a43ad30a4a3ee4d0",
"sha256:5944a9b95e97de1980c65f03b79b356f30a43de48682b8bdd90aa5089f0ec1f4",
"sha256:6f4e5e68b7af950ed7fdb594b3f19a0014a3ace0fedb86acb896e140ffb24302",
"sha256:71a1ef23f22fa8437974b2d60fedb947c99a957ad625f83f43fd3de70f77f458",
"sha256:8a44e9901c0555f95ac401377032f6e6af66d8fc1fbfad77a7a8b1a826e0b93c",
"sha256:b6577f15d5516d7d209c1a8cde23062c0f10625f19e8dc9fb59268859778d7d7",
"sha256:c8fe2d6ff0ff583784039d0255ea7da076efd08507f2be6f68583b0da32e3afb",
"sha256:cadfa2c2cf54d35d13dc8d231253b7985b97d629ab9ca6e7d672c35539d38163",
"sha256:cd1bdace1a8762534e9a36c073cd54e97d517a17d69a17985961265be6d22847",
"sha256:ddbdcd10eb999d7ab292677f588b658372aadb9a52790f82484a37127a390108",
"sha256:e7273c64bccfd9310e9601b8f4511d84730239516bada26a0c9846c9697617ef",
"sha256:e7428977763150b4cf83255625a80a23dfdc94d43be7791ce90799d446b4e26f",
"sha256:e960e8be509e8d6d618300a6c189555c24efde63e85acaf0b14b2cd1ac743315",
"sha256:ecb5dd5990cec6e7f5c9c1124a37cb2c710c6d69b0c1a5c4aa4b35eba0ada068",
"sha256:ef3f5e288aa57b73b034ce9c1f1ac753d968f9069cd0742d1d69c698a0167166",
"sha256:fa5b2dee0e231fa4ad117be114251bdfe6afe39213bd629d43deb117b6a6c40a",
"sha256:fa7fb7973c622b9e725bee1db569d2c2ee64d2f9a089201c5e8185d482c7352d"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
"version": "==1.5.2"
},
"mccabe": {
"hashes": [
"sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42",
"sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"
],
"version": "==0.6.1"
},
"pylint": {
"hashes": [
"sha256:81ce108f6342421169ea039ff1f528208c99d2e5a9c4ca95cfc5291be6dfd982",
"sha256:a251b238db462b71d25948f940568bb5b3ae0e37dbaa05e10523f54f83e6cc7e"
],
"index": "pypi",
"version": "==2.7.1"
},
"pylintfileheader": {
"hashes": [
"sha256:7871193691484210268d467dc12d88ac5b3ba7eb7dec6239e24075797185a3b2",
"sha256:a23f143b0fb4d65f984ffd824731d6e41f2840e26a5752a90df93f4454b5ccd1"
],
"index": "pypi",
"version": "==0.3.0"
},
"pytz": {
"hashes": [
"sha256:83a4a90894bf38e243cf052c8b58f381bfe9a7a483f6a9cab140bc7f702ac4da",
"sha256:eb10ce3e7736052ed3623d49975ce333bcd712c7bb19a58b9e2089d4057d0798"
],
"version": "==2021.1"
},
"requests": {
"hashes": [
"sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804",
"sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
"version": "==2.25.1"
},
"toml": {
"hashes": [
"sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b",
"sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"
],
"markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2'",
"version": "==0.10.2"
},
"unittest-data-provider": {
"hashes": [
"sha256:86bc7fb6608c2570aeedadea346fe3034afc940807dd7519e95e5dbc899ac2be"
],
"index": "pypi",
"version": "==1.0.1"
},
"urllib3": {
"hashes": [
"sha256:1b465e494e3e0d8939b50680403e3aedaa2bc434b7d5af64dfd3c958d7f5ae80",
"sha256:de3eedaad74a2683334e282005cd8d7f22f4d55fa690a2a1020a416cb0a47e73"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'",
"version": "==1.26.3"
},
"wrapt": {
"hashes": [
"sha256:b62ffa81fb85f4332a4f609cab4ac40709470da05643a082ec1eb88e6d9b97d7"
],
"version": "==1.12.1"
}
}
}

+ 21
- 0
kosmorrolib/__init__.py View File

@@ -0,0 +1,21 @@
#!/usr/bin/env python3

# Kosmorro - Compute The Next Ephemerides
# Copyright (C) 2019 Jérôme Deuchnord <jerome@deuchnord.fr>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero 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 Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.

from .version import MAJOR_VERSION, MINOR_VERSION, PATCH_VERSION, VERSION
from .ephemerides import get_ephemerides
from .events import get_events

BIN
kosmorrolib/__pycache__/dateutil.cpython-39.pyc View File


+ 117
- 0
kosmorrolib/core.py View File

@@ -0,0 +1,117 @@
#!/usr/bin/env python3

# Kosmorro - Compute The Next Ephemerides
# Copyright (C) 2019 Jérôme Deuchnord <jerome@deuchnord.fr>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero 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 Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.

import os
import re
from shutil import rmtree
from pathlib import Path

from datetime import date
from dateutil.relativedelta import relativedelta

from skyfield.api import Loader
from skyfield.timelib import Time
from skyfield.nutationlib import iau2000b

CACHE_FOLDER = str(Path.home()) + '/.kosmorro-cache'

class Environment:
def __init__(self):
self._vars = {}

def __set__(self, key, value):
self._vars[key] = value

def __getattr__(self, key):
return self._vars[key] if key in self._vars else None

def __str__(self):
return self._vars.__str__()

def __len__(self):
return len(self._vars)

def get_env() -> Environment:
environment = Environment()

for var in os.environ:
if not re.search('^KOSMORRO_', var):
continue

[_, env] = var.split('_', 1)
environment.__set__(env.lower(), os.getenv(var))

return environment

def get_loader():
return Loader(CACHE_FOLDER)


def get_timescale():
return get_loader().timescale()


def get_skf_objects():
return get_loader()('de421.bsp')


def get_iau2000b(time: Time):
return iau2000b(time.tt)


def clear_cache():
rmtree(CACHE_FOLDER)


def flatten_list(the_list: list):
new_list = []
for item in the_list:
if isinstance(item, list):
for item2 in flatten_list(item):
new_list.append(item2)
continue

new_list.append(item)

return new_list


def get_date(date_arg: str) -> date:
if re.match(r'^\d{4}-\d{2}-\d{2}$', date_arg):
try:
return date.fromisoformat(date_arg)
except ValueError as error:
raise ValueError(_('The date {date} is not valid: {error}').format(date=date_arg,
error=error.args[0])) from error
elif re.match(r'^([+-])(([0-9]+)y)?[ ]?(([0-9]+)m)?[ ]?(([0-9]+)d)?$', date_arg):
def get_offset(date_arg: str, signifier: str):
if re.search(r'([0-9]+)' + signifier, date_arg):
return abs(int(re.search(r'[+-]?([0-9]+)' + signifier, date_arg).group(0)[:-1]))
return 0

days = get_offset(date_arg, 'd')
months = get_offset(date_arg, 'm')
years = get_offset(date_arg, 'y')

if date_arg[0] == '+':
return date.today() + relativedelta(days=days, months=months, years=years)
return date.today() - relativedelta(days=days, months=months, years=years)

else:
error_msg = 'The date {date} does not match the required YYYY-MM-DD format or the offset format.'
raise ValueError(error_msg.format(date=date_arg))

+ 223
- 0
kosmorrolib/data.py View File

@@ -0,0 +1,223 @@
#!/usr/bin/env python3

# Kosmorro - Compute The Next Ephemerides
# Copyright (C) 2019 Jérôme Deuchnord <jerome@deuchnord.fr>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero 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 Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.

from abc import ABC, abstractmethod
from typing import Union
from datetime import datetime

from numpy import pi, arcsin

from skyfield.api import Topos, Time
from skyfield.vectorlib import VectorSum as SkfPlanet

from .core import get_skf_objects
from .enum import MoonPhaseType, EventType


class Serializable(ABC):
@abstractmethod
def serialize(self) -> dict:
pass


class MoonPhase(Serializable):
def __init__(self, phase_type: MoonPhaseType, time: datetime = None, next_phase_date: datetime = None):
self.phase_type = phase_type
self.time = time
self.next_phase_date = next_phase_date

def get_next_phase(self):
if self.phase_type in [MoonPhaseType.NEW_MOON, MoonPhaseType.WAXING_CRESCENT]:
return MoonPhaseType.FIRST_QUARTER
if self.phase_type in [MoonPhaseType.FIRST_QUARTER, MoonPhaseType.WAXING_GIBBOUS]:
return MoonPhaseType.FULL_MOON
if self.phase_type in [MoonPhaseType.FULL_MOON, MoonPhaseType.WANING_GIBBOUS]:
return MoonPhaseType.LAST_QUARTER

return MoonPhaseType.NEW_MOON

def serialize(self) -> dict:
return {
'phase': self.phase_type.name,
'time': self.time.isoformat() if self.time is not None else None,
'next': {
'phase': self.get_next_phase().name,
'time': self.next_phase_date.isoformat()
}
}


class Object(Serializable):
"""
An astronomical object.
"""

def __init__(self,
name: str,
skyfield_name: str,
radius: float = None):
"""
Initialize an astronomical object

:param str name: the official name of the object (may be internationalized)
:param str skyfield_name: the internal name of the object in Skyfield library
:param float radius: the radius (in km) of the object
:param AsterEphemerides ephemerides: the ephemerides associated to the object
"""
self.name = name
self.skyfield_name = skyfield_name
self.radius = radius

def __repr__(self):
return '<Object type=%s name=%s />' % (self.get_type(), self.name)

def get_skyfield_object(self) -> SkfPlanet:
return get_skf_objects()[self.skyfield_name]

@abstractmethod
def get_type(self) -> str:
pass

def get_apparent_radius(self, time: Time, from_place) -> float:
"""
Calculate the apparent radius, in degrees, of the object from the given place at a given time.
:param time:
:param from_place:
:return:
"""
if self.radius is None:
raise ValueError('Missing radius for %s object' % self.name)

return 360 / pi * arcsin(self.radius / from_place.at(time).observe(self.get_skyfield_object()).distance().km)

def serialize(self) -> dict:
return {
'name': self.name,
'type': self.get_type(),
'radius': self.radius,
}


class Star(Object):
def get_type(self) -> str:
return 'star'


class Planet(Object):
def get_type(self) -> str:
return 'planet'


class DwarfPlanet(Planet):
def get_type(self) -> str:
return 'dwarf_planet'


class Satellite(Object):
def get_type(self) -> str:
return 'satellite'


class Event(Serializable):
def __init__(self, event_type: EventType, objects: [Object], start_time: datetime,
end_time: Union[datetime, None] = None, details: str = None):
self.event_type = event_type
self.objects = objects
self.start_time = start_time
self.end_time = end_time
self.details = details

def __repr__(self):
return '<Event type=%s objects=%s start=%s end=%s details=%s />' % (self.event_type.name,
self.objects,
self.start_time,
self.end_time,
self.details)

def get_description(self, show_details: bool = True) -> str:
description = self.event_type.value % self._get_objects_name()
if show_details and self.details is not None:
description += ' ({:s})'.format(self.details)
return description

def _get_objects_name(self):
if len(self.objects) == 1:
return self.objects[0].name

return tuple(object.name for object in self.objects)

def serialize(self) -> dict:
return {
'objects': [object.serialize() for object in self.objects],
'EventType': self.event_type.name,
'starts_at': self.start_time.isoformat(),
'ends_at': self.end_time.isoformat() if self.end_time is not None else None,
'details': self.details
}


class AsterEphemerides(Serializable):
def __init__(self,
rise_time: Union[datetime, None],
culmination_time: Union[datetime, None],
set_time: Union[datetime, None],
aster: Object):
self.rise_time = rise_time
self.culmination_time = culmination_time
self.set_time = set_time
self.object = aster

def serialize(self) -> dict:
return {
'object': self.object.serialize(),
'rise_time': self.rise_time.isoformat() if self.rise_time is not None else None,
'culmination_time': self.culmination_time.isoformat() if self.culmination_time is not None else None,
'set_time': self.set_time.isoformat() if self.set_time is not None else None
}


EARTH = Planet('Earth', 'EARTH')

ASTERS = [Star('Sun', 'SUN', radius=696342),
Satellite('Moon', 'MOON', radius=1737.4),
Planet('Mercury', 'MERCURY', radius=2439.7),
Planet('Venus', 'VENUS', radius=6051.8),
Planet('Mars', 'MARS', radius=3396.2),
Planet('Jupiter', 'JUPITER BARYCENTER', radius=71492),
Planet('Saturn', 'SATURN BARYCENTER', radius=60268),
Planet('Uranus', 'URANUS BARYCENTER', radius=25559),
Planet('Neptune', 'NEPTUNE BARYCENTER', radius=24764),
Planet('Pluto', 'PLUTO BARYCENTER', radius=1185)]


class Position:
def __init__(self, latitude: float, longitude: float, aster: Object):
self.latitude = latitude
self.longitude = longitude
self.aster = aster
self._topos = None

def get_planet_topos(self) -> Topos:
if self.aster is None:
raise TypeError('Observation planet must be set.')

if self._topos is None:
self._topos = self.aster.get_skyfield_object() + Topos(latitude_degrees=self.latitude,
longitude_degrees=self.longitude)

return self._topos

+ 28
- 0
kosmorrolib/dateutil.py View File

@@ -0,0 +1,28 @@
#!/usr/bin/env python3

# Kosmorro - Compute The Next Ephemerides
# Copyright (C) 2019 Jérôme Deuchnord <jerome@deuchnord.fr>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero 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 Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.

from datetime import datetime, timezone, timedelta


def translate_to_timezone(date: datetime, to_tz: int, from_tz: int = None):
if from_tz is not None:
source_tz = timezone(timedelta(hours=from_tz))
else:
source_tz = timezone.utc

return date.replace(tzinfo=source_tz).astimezone(tz=timezone(timedelta(hours=to_tz)))

+ 41
- 0
kosmorrolib/enum.py View File

@@ -0,0 +1,41 @@
#!/usr/bin/env python3

# Kosmorro - Compute The Next Ephemerides
# Copyright (C) 2019 Jérôme Deuchnord <jerome@deuchnord.fr>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero 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 Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.

from enum import Enum, auto


class MoonPhaseType(Enum):
"""An enumeration of moon phases."""
NEW_MOON = 1
WAXING_CRESCENT = 2
FIRST_QUARTER = 3
WAXING_GIBBOUS = 4
FULL_MOON = 5
WANING_GIBBOUS = 6
LAST_QUARTER = 7
WANING_CRESCENT = 8


class EventType(Enum):
"""An enumeration for the supported event types."""
OPPOSITION = 1
CONJUNCTION = 2
OCCULTATION = 3
MAXIMAL_ELONGATION = 4
MOON_PERIGEE = 5
MOON_APOGEE = 6

+ 163
- 0
kosmorrolib/ephemerides.py View File

@@ -0,0 +1,163 @@
#!/usr/bin/env python3

# Kosmorro - Compute The Next Ephemerides
# Copyright (C) 2019 Jérôme Deuchnord <jerome@deuchnord.fr>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero 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 Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.

import datetime
from typing import Union

from skyfield.searchlib import find_discrete, find_maxima
from skyfield.timelib import Time
from skyfield.constants import tau
from skyfield.errors import EphemerisRangeError

from .data import Position, AsterEphemerides, MoonPhase, Object, ASTERS
from .dateutil import translate_to_timezone
from .core import get_skf_objects, get_timescale, get_iau2000b
from .enum import MoonPhaseType
from .exceptions import OutOfRangeDateError

RISEN_ANGLE = -0.8333


def _get_skyfield_to_moon_phase(times: [Time], vals: [int], now: Time) -> Union[MoonPhaseType, None]:
tomorrow = get_timescale().utc(now.utc_datetime().year, now.utc_datetime().month, now.utc_datetime().day + 1)

phases = list(MoonPhaseType)
current_phase = None
current_phase_time = None
next_phase_time = None
i = 0

if len(times) == 0:
return None

for i, time in enumerate(times):
if now.utc_iso() <= time.utc_iso():
if vals[i] in [0, 2, 4, 6]:
if time.utc_datetime() < tomorrow.utc_datetime():
current_phase_time = time
current_phase = phases[vals[i]]
else:
i -= 1
current_phase_time = None
current_phase = phases[vals[i]]
else:
current_phase = phases[vals[i]]

break

for j in range(i + 1, len(times)):
if vals[j] in [0, 2, 4, 6]:
next_phase_time = times[j]
break

return MoonPhaseType(current_phase,
current_phase_time.utc_datetime() if current_phase_time is not None else None,
next_phase_time.utc_datetime() if next_phase_time is not None else None)


def get_moon_phase(compute_date: datetime.date, timezone: int = 0) -> MoonPhaseType:
earth = get_skf_objects()['earth']
moon = get_skf_objects()['moon']
sun = get_skf_objects()['sun']

def moon_phase_at(time: Time):
time._nutation_angles = get_iau2000b(time)
current_earth = earth.at(time)
_, mlon, _ = current_earth.observe(moon).apparent().ecliptic_latlon('date')
_, slon, _ = current_earth.observe(sun).apparent().ecliptic_latlon('date')
return (((mlon.radians - slon.radians) // (tau / 8)) % 8).astype(int)

moon_phase_at.rough_period = 7.0 # one lunar phase per week

today = get_timescale().utc(compute_date.year, compute_date.month, compute_date.day)
time1 = get_timescale().utc(compute_date.year, compute_date.month, compute_date.day - 10)
time2 = get_timescale().utc(compute_date.year, compute_date.month, compute_date.day + 10)

try:
times, phase = find_discrete(time1, time2, moon_phase_at)
except EphemerisRangeError as error:
start = translate_to_timezone(error.start_time.utc_datetime(), timezone)
end = translate_to_timezone(error.end_time.utc_datetime(), timezone)

start = datetime.date(start.year, start.month, start.day) + datetime.timedelta(days=12)
end = datetime.date(end.year, end.month, end.day) - datetime.timedelta(days=12)

raise OutOfRangeDateError(start, end) from error

return _get_skyfield_to_moon_phase(times, phase, today)


def get_ephemerides(date: datetime.date, position: Position, timezone: int = 0) -> [AsterEphemerides]:
ephemerides = []

def get_angle(for_aster: Object):
def fun(time: Time) -> float:
return position.get_planet_topos().at(time).observe(for_aster.get_skyfield_object()).apparent().altaz()[0]\
.degrees
fun.rough_period = 1.0
return fun

def is_risen(for_aster: Object):
def fun(time: Time) -> bool:
return get_angle(for_aster)(time) > RISEN_ANGLE
fun.rough_period = 0.5
return fun

start_time = get_timescale().utc(date.year, date.month, date.day, -timezone)
end_time = get_timescale().utc(date.year, date.month, date.day, 23 - timezone, 59, 59)

try:
for aster in ASTERS:
rise_times, arr = find_discrete(start_time, end_time, is_risen(aster))
try:
culmination_time, _ = find_maxima(start_time, end_time, f=get_angle(aster), epsilon=1./3600/24, num=12)
culmination_time = culmination_time[0] if len(culmination_time) > 0 else None
except ValueError:
culmination_time = None

if len(rise_times) == 2:
rise_time = rise_times[0 if arr[0] else 1]
set_time = rise_times[1 if not arr[1] else 0]
else:
rise_time = rise_times[0] if arr[0] else None
set_time = rise_times[0] if not arr[0] else None

# Convert the Time instances to Python datetime objects
if rise_time is not None:
rise_time = translate_to_timezone(rise_time.utc_datetime().replace(microsecond=0),
to_tz=timezone)

if culmination_time is not None:
culmination_time = translate_to_timezone(culmination_time.utc_datetime().replace(microsecond=0),
to_tz=timezone)

if set_time is not None:
set_time = translate_to_timezone(set_time.utc_datetime().replace(microsecond=0),
to_tz=timezone)

ephemerides.append(AsterEphemerides(rise_time, culmination_time, set_time, aster=aster))
except EphemerisRangeError as error:
start = translate_to_timezone(error.start_time.utc_datetime(), timezone)
end = translate_to_timezone(error.end_time.utc_datetime(), timezone)

start = datetime.date(start.year, start.month, start.day + 1)
end = datetime.date(end.year, end.month, end.day - 1)

raise OutOfRangeDateError(start, end) from error

return ephemerides

+ 224
- 0
kosmorrolib/events.py View File

@@ -0,0 +1,224 @@
#!/usr/bin/env python3

# Kosmorro - Compute The Next Ephemerides
# Copyright (C) 2019 Jérôme Deuchnord <jerome@deuchnord.fr>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero 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 Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.

from datetime import date as date_type

from skyfield.errors import EphemerisRangeError
from skyfield.timelib import Time
from skyfield.searchlib import find_discrete, find_maxima, find_minima
from numpy import pi

from .data import Event, Star, Planet, ASTERS
from .dateutil import translate_to_timezone
from .enum import EventType
from .exceptions import OutOfRangeDateError
from .core import get_timescale, get_skf_objects, flatten_list


def _search_conjunction(start_time: Time, end_time: Time, timezone: int) -> [Event]:
earth = get_skf_objects()['earth']
aster1 = None
aster2 = None

def is_in_conjunction(time: Time):
earth_pos = earth.at(time)
_, aster1_lon, _ = earth_pos.observe(aster1.get_skyfield_object()).apparent().ecliptic_latlon()
_, aster2_lon, _ = earth_pos.observe(aster2.get_skyfield_object()).apparent().ecliptic_latlon()

return ((aster1_lon.radians - aster2_lon.radians) / pi % 2.0).astype('int8') == 0

is_in_conjunction.rough_period = 60.0

computed = []
conjunctions = []

for aster1 in ASTERS:
# Ignore the Sun
if isinstance(aster1, Star):
continue

for aster2 in ASTERS:
if isinstance(aster2, Star) or aster2 == aster1 or aster2 in computed:
continue

times, is_conjs = find_discrete(start_time, end_time, is_in_conjunction)

for i, time in enumerate(times):
if is_conjs[i]:
aster1_pos = (aster1.get_skyfield_object() - earth).at(time)
aster2_pos = (aster2.get_skyfield_object() - earth).at(time)
distance = aster1_pos.separation_from(aster2_pos).degrees

if distance - aster2.get_apparent_radius(time, earth) < aster1.get_apparent_radius(time, earth):
occulting_aster = [aster1,
aster2] if aster1_pos.distance().km < aster2_pos.distance().km else [aster2,
aster1]

conjunctions.append(Event(EventType.OCCULTATION, occulting_aster,
translate_to_timezone(time.utc_datetime(), timezone)))
else:
conjunctions.append(Event(EventType.CONJUNCTION, [aster1, aster2],
translate_to_timezone(time.utc_datetime(), timezone)))

computed.append(aster1)

return conjunctions


def _search_oppositions(start_time: Time, end_time: Time, timezone: int) -> [Event]:
earth = get_skf_objects()['earth']
sun = get_skf_objects()['sun']
aster = None

def is_oppositing(time: Time) -> [bool]:
earth_pos = earth.at(time)
sun_pos = earth_pos.observe(sun).apparent() # Never do this without eyes protection!
aster_pos = earth_pos.observe(get_skf_objects()[aster.skyfield_name]).apparent()
_, lon1, _ = sun_pos.ecliptic_latlon()
_, lon2, _ = aster_pos.ecliptic_latlon()
return (lon1.degrees - lon2.degrees) > 180

is_oppositing.rough_period = 1.0
events = []

for aster in ASTERS:
if not isinstance(aster, Planet) or aster.skyfield_name in ['MERCURY', 'VENUS']:
continue

times, _ = find_discrete(start_time, end_time, is_oppositing)
for time in times:
events.append(Event(EventType.OPPOSITION, [aster], translate_to_timezone(time.utc_datetime(), timezone)))

return events


def _search_maximal_elongations(start_time: Time, end_time: Time, timezone: int) -> [Event]:
earth = get_skf_objects()['earth']
sun = get_skf_objects()['sun']
aster = None

def get_elongation(time: Time):
sun_pos = (sun - earth).at(time)
aster_pos = (aster.get_skyfield_object() - earth).at(time)
separation = sun_pos.separation_from(aster_pos)
return separation.degrees

get_elongation.rough_period = 1.0

events = []

for aster in ASTERS:
if aster.skyfield_name not in ['MERCURY', 'VENUS']:
continue

times, elongations = find_maxima(start_time, end_time, f=get_elongation, epsilon=1./24/3600, num=12)

for i, time in enumerate(times):
elongation = elongations[i]
events.append(Event(EventType.MAXIMAL_ELONGATION,
[aster],
translate_to_timezone(time.utc_datetime(), timezone),
details='{:.3n}°'.format(elongation)))

return events


def _get_moon_distance():
earth = get_skf_objects()['earth']
moon = get_skf_objects()['moon']

def get_distance(time: Time):
earth_pos = earth.at(time)
moon_pos = earth_pos.observe(moon).apparent()

return moon_pos.distance().au

get_distance.rough_period = 1.0

return get_distance


def _search_moon_apogee(start_time: Time, end_time: Time, timezone: int) -> [Event]:
moon = ASTERS[1]
events = []

times, _ = find_maxima(start_time, end_time, f=_get_moon_distance(), epsilon=1./24/60)

for time in times:
events.append(Event(EventType.MOON_APOGEE, [moon], translate_to_timezone(time.utc_datetime(), timezone)))

return events


def _search_moon_perigee(start_time: Time, end_time: Time, timezone: int) -> [Event]:
moon = ASTERS[1]
events = []

times, _ = find_minima(start_time, end_time, f=_get_moon_distance(), epsilon=1./24/60)

for time in times:
events.append(Event(EventType.MOON_PERIGEE, [moon], translate_to_timezone(time.utc_datetime(), timezone)))

return events


def get_events(date: date_type, timezone: int = 0) -> [Event]:
"""Calculate and return a list of events for the given date, adjusted to the given timezone if any.

Find events that happen on April 4th, 2020 (show hours in UTC):
>>> get_events(date_type(2020, 4, 4))
[<Event type=CONJUNCTION objects=[<Object type=planet name=Mercury />, <Object type=planet name=Neptune />] start=2020-04-04 01:14:39.063308+00:00 end=None details=None />]

Find events that happen on April 4th, 2020 (show timezones in UTC+2):

>>> get_events(date_type(2020, 4, 4), 2)
[<Event type=CONJUNCTION objects=[<Object type=planet name=Mercury />, <Object type=planet name=Neptune />] start=2020-04-04 03:14:39.063267+02:00 end=None details=None />]

Find events that happen on April 3rd, 2020 (show timezones in UTC-2):

>>> get_events(date_type(2020, 4, 3), -2)
[<Event type=CONJUNCTION objects=[<Object type=planet name=Mercury />, <Object type=planet name=Neptune />] start=2020-04-03 23:14:39.063388-02:00 end=None details=None />]

:param date: the date for which the events must be calculated
:param timezone: the timezone to adapt the results to. If not given, defaults to 0.
:return: a list of events found for the given date.
"""

start_time = get_timescale().utc(date.year, date.month, date.day, -timezone)
end_time = get_timescale().utc(date.year, date.month, date.day + 1, -timezone)

try:
found_events = []

for fun in [_search_oppositions,
_search_conjunction,
_search_maximal_elongations,
_search_moon_apogee,
_search_moon_perigee]:
found_events.append(fun(start_time, end_time, timezone))

return sorted(flatten_list(found_events), key=lambda event: event.start_time)
except EphemerisRangeError as error:
start_date = translate_to_timezone(error.start_time.utc_datetime(), timezone)
end_date = translate_to_timezone(error.end_time.utc_datetime(), timezone)

start_date = date_type(start_date.year, start_date.month, start_date.day)
end_date = date_type(end_date.year, end_date.month, end_date.day)

raise OutOfRangeDateError(start_date, end_date) from error

+ 40
- 0
kosmorrolib/exceptions.py View File

@@ -0,0 +1,40 @@
#!/usr/bin/env python3

# Kosmorro - Compute The Next Ephemerides
# Copyright (C) 2019 Jérôme Deuchnord <jerome@deuchnord.fr>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero 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 Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.

from datetime import date


class UnavailableFeatureError(RuntimeError):
def __init__(self, msg: str):
super().__init__()
self.msg = msg


class OutOfRangeDateError(RuntimeError):
def __init__(self, min_date: date, max_date: date):
super().__init__()
self.min_date = min_date
self.max_date = max_date
self.msg = 'The date must be between %s and %s' % (min_date.strftime('%Y-%m-%d'),
max_date.strftime('%Y-%m-%d'))


class CompileError(RuntimeError):
def __init__(self, msg):
super().__init__()
self.msg = msg

+ 46
- 0
kosmorrolib/version.py View File

@@ -0,0 +1,46 @@
#!/usr/bin/env python3

# Kosmorro - Compute The Next Ephemerides
# Copyright (C) 2019 Jérôme Deuchnord <jerome@deuchnord.fr>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero 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 Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.

"""
Kosmorrolib's versioning follows the `Semantic Versioning <https://semver.org/spec/v2.0.0.html>`_ standard,
meaning that:

* the versions always follow the X.Y.Z format, where X, Y and Z are natural numbers.
* the major version (X) never changes unless a change breaks compatibility (any breaking compatibility change in the
same major version is considered as a bug)
* the minor version (Y) never changes unless new features are introduced
* the patch version (Z) never changes unless there are bug fixes
"""

MAJOR_VERSION = 0
"""The major version of the library"""

MINOR_VERSION = 9
"""The minor version of the library"""

PATCH_VERSION = 0
"""The patch version of the library"""

VERSION = '%d.%d.%d' % (MAJOR_VERSION, MINOR_VERSION, PATCH_VERSION)
"""
The library version in a readable for human beings format.
Useful for instance, if you want to display it to the end user.

If you need to check the version in your program, you should prefer using the MAJOR_VERSION, MINOR_MINOR_VERSION and
PATCH_VERSION constants instead.
"""

+ 6
- 0
tests.py View File

@@ -0,0 +1,6 @@
import doctest

from kosmorrolib import *

for module in [events]:
doctest.testmod(module)

Loading…
Cancel
Save