test_timeplace.py 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561
  1. #******************************************************************************
  2. #******************************************************************************
  3. # import libraries
  4. from math import pi, floor
  5. from src import pysoleng as pse #
  6. from numpy.testing import assert_allclose
  7. import datetime as dt
  8. from zoneinfo import ZoneInfo
  9. # test using "!python -m pytest -s --cov --cov-report term-missing"
  10. # *****************************************************************************
  11. # *****************************************************************************
  12. class TestTimePlace:
  13. def test_flat_surface(self):
  14. # 1) single azimuth, single slope
  15. fs1 = pse.timeplace.FlatSurface(
  16. azimuth=45*pi/180,
  17. slope=45*pi/180
  18. )
  19. assert fs1.has_fixed_azimuth()
  20. assert fs1.has_fixed_slope()
  21. assert fs1.number_orientations() == 1
  22. # 2) single azimuth, multiple slopes
  23. fs2 = pse.timeplace.FlatSurface(
  24. azimuth=45*pi/180,
  25. slope=[45*pi/180, 0]
  26. )
  27. assert fs2.has_fixed_azimuth()
  28. assert not fs2.has_fixed_slope()
  29. assert fs2.number_orientations() == 2
  30. # 3) single azimuth, multiple slopes
  31. fs3 = pse.timeplace.FlatSurface(
  32. azimuth=[45*pi/180, 90*pi/180],
  33. slope=45*pi/180
  34. )
  35. assert not fs3.has_fixed_azimuth()
  36. assert fs3.has_fixed_slope()
  37. assert fs3.number_orientations() == 2
  38. # 4) multiple azimuths, multiple slopes
  39. fs4 = pse.timeplace.FlatSurface(
  40. azimuth=[45*pi/180],
  41. slope=[45*pi/180]
  42. )
  43. assert not fs4.has_fixed_azimuth()
  44. assert not fs4.has_fixed_slope()
  45. assert fs4.number_orientations() == 1
  46. # *********************************************************************
  47. # *********************************************************************
  48. # trigger errors
  49. # incorrect input area
  50. error_raised = False
  51. try:
  52. pse.timeplace.FlatSurface(
  53. azimuth=45*pi/180,
  54. slope=45*pi/180,
  55. area='a'
  56. )
  57. except TypeError:
  58. error_raised = True
  59. assert error_raised
  60. # incorrect sizes
  61. error_raised = False
  62. try:
  63. pse.timeplace.FlatSurface(
  64. azimuth=[45*pi/180],
  65. slope=[45*pi/180, 60*pi/180]
  66. )
  67. except ValueError:
  68. error_raised = True
  69. assert error_raised
  70. # incorrect elements in the iterables
  71. error_raised = False
  72. try:
  73. pse.timeplace.FlatSurface(
  74. azimuth=[45*pi/180, 'a'],
  75. slope=[45*pi/180, 60*pi/180]
  76. )
  77. except ValueError:
  78. error_raised = True
  79. assert error_raised
  80. # incorrect elements in the iterables
  81. error_raised = False
  82. try:
  83. pse.timeplace.FlatSurface(
  84. azimuth=[45*pi/180, 60*pi/180],
  85. slope=[45*pi/180, 'b']
  86. )
  87. except ValueError:
  88. error_raised = True
  89. assert error_raised
  90. # *********************************************************************
  91. # *********************************************************************
  92. # *************************************************************************
  93. # *************************************************************************
  94. def test_location_creation(self):
  95. #transformer = Transformer.from_crs("EPSG:4326", "EPSG:3857", always_xy=True)
  96. list_locations = [
  97. # unprojected
  98. ((30,110),'epsg:4326',(30,110)),
  99. ((30,-110),'epsg:4326',(30,-110)),
  100. ((-30,110),'epsg:4326',(-30,110)),
  101. ((-30,-110),'epsg:4326',(-30,-110)),
  102. # projected
  103. ((3339584.723798207, 3503549.8435043753),'epsg:3857',(30,30)),
  104. ((3339584.723798207, -3503549.8435043753),'epsg:3857',(30,-30)),
  105. ((-3339584.723798207, 3503549.8435043753),'epsg:3857',(-30,30)),
  106. ((-3339584.723798207, -3503549.8435043753),'epsg:3857',(-30,-30)),
  107. # projected, time
  108. ((-466939.6922422622, 3361248.752300655),'epsg:32633',(5,30)),
  109. ((-56351.259575267904, 6693618.35050865),'epsg:32633',(5,60)),
  110. ((17453.33453025535, 3329329.800484811),'epsg:32633',(10,30)),
  111. ((221288.77024763188, 6661953.040544909),'epsg:32633',(10,60))
  112. ]
  113. for location in list_locations:
  114. mylocation = pse.timeplace.Location(
  115. coordinates=location[0],
  116. crs_string=location[1])
  117. assert_allclose(mylocation.latitude, location[2][0])
  118. assert_allclose(mylocation.longitude, location[2][1])
  119. # locations with issues
  120. list_locations = [
  121. # epsg:4326
  122. ((30,182),'epsg:4326',(30,30)), # longitude out of bounds
  123. ((30,-181),'epsg:4326',(30,30)), # longitude out of bounds
  124. ((95,30),'epsg:4326',(30,30)), # latitute out of bounds
  125. ((-95,30),'epsg:4326',(30,30)), # latitute out of bounds
  126. ((-95,30,0),'epsg:4326',(30,30)) # 3 coordinates
  127. ]
  128. error_counter = 0
  129. for location in list_locations:
  130. # trigger the errors
  131. try:
  132. mylocation = pse.timeplace.Location(
  133. coordinates=location[0],
  134. crs_string=location[1])
  135. except ValueError:
  136. error_counter += 1
  137. assert error_counter == len(list_locations)
  138. # *************************************************************************
  139. # *************************************************************************
  140. def test_day_of_the_year(self):
  141. list_tests = [
  142. # non-leap year
  143. ('2011-01-15 12:00:00-00:00',15),
  144. ('2011-02-15 12:00:00-00:00',31+15),
  145. ('2011-03-15 12:00:00-00:00',59+15),
  146. ('2011-04-15 12:00:00-00:00',90+15),
  147. ('2011-05-15 12:00:00-00:00',120+15),
  148. ('2011-06-15 12:00:00-00:00',151+15),
  149. ('2011-07-15 12:00:00-00:00',181+15),
  150. ('2011-08-15 12:00:00-00:00',212+15),
  151. ('2011-09-15 12:00:00-00:00',243+15),
  152. ('2011-10-15 12:00:00-00:00',273+15),
  153. ('2011-11-15 12:00:00-00:00',304+15),
  154. ('2011-12-15 12:00:00-00:00',334+15),
  155. # leap year: a day is added after february
  156. ('2012-01-15 12:00:00-00:00',15),
  157. ('2012-02-15 12:00:00-00:00',31+15),
  158. ('2012-03-15 12:00:00-00:00',59+1+15), # after february: add +1
  159. ('2012-04-15 12:00:00-00:00',90+1+15),
  160. ('2012-05-15 12:00:00-00:00',120+1+15),
  161. ('2012-06-15 12:00:00-00:00',151+1+15),
  162. ('2012-07-15 12:00:00-00:00',181+1+15),
  163. ('2012-08-15 12:00:00-00:00',212+1+15),
  164. ('2012-09-15 12:00:00-00:00',243+1+15),
  165. ('2012-10-15 12:00:00-00:00',273+1+15),
  166. ('2012-11-15 12:00:00-00:00',304+1+15),
  167. ('2012-12-15 12:00:00-00:00',334+1+15),
  168. # UTC offsets: + is ahead of UTC (east), - is before UTC (west)
  169. ('2011-01-15 03:00:00+05:00',15-1), # 5 hours ahead: 22 pm UTC
  170. ('2011-02-15 03:00:00+05:00',31+15-1), # 5 hours ahead: 22 pm UTC
  171. ('2011-03-15 03:00:00+05:00',59+15-1), # 5 hours ahead: 22 pm UTC
  172. ('2011-01-15 22:00:00-05:00',15+1), # 5 hours before UTC: 3 am UTC
  173. ('2011-02-15 22:00:00-05:00',31+15+1), # 5 hours before UTC: 3 am UTC
  174. ('2011-03-15 22:00:00-05:00',59+15+1), # 5 hours before UTC: 3 am UTC
  175. ]
  176. for test in list_tests:
  177. # create datetime object
  178. my_dt = dt.datetime.fromisoformat(test[0])
  179. # get the day of the year
  180. doty = pse.timeplace.day_of_the_year(my_time=my_dt,
  181. as_integer=True)
  182. # validate it
  183. assert_allclose(doty, test[1])
  184. # get the non-integer variant
  185. doty_ni = pse.timeplace.day_of_the_year(my_time=my_dt,
  186. as_integer=False)
  187. # validate it
  188. assert_allclose(floor(doty_ni), test[1])
  189. # *************************************************************************
  190. # *************************************************************************
  191. def test_is_datetime_aware(self):
  192. list_tests = [
  193. ('2011-01-15 12:00:00-00:00', True),
  194. ('2011-01-15 12:00:00', False),
  195. ]
  196. # for each test
  197. for test in list_tests:
  198. # create datetime object
  199. my_dt = dt.datetime.fromisoformat(test[0])
  200. assert pse.timeplace.is_datetime_aware(my_dt) == test[1]
  201. # *************************************************************************
  202. # *************************************************************************
  203. def test_finding_standard_meridian(self):
  204. # TODO: get the daylight time savings time
  205. list_tests = [
  206. # format: coordinates, crs, time, dst offset degrees, true standard meridian
  207. # Nicosia: 35°10′21″N 33°21′54″E (Kalogirou, 2009; ex. 2.1, page 52)
  208. ((35.0, 33.3), 'epsg:4326', '2022-11-15 10:43:00+02:00', 0, 30),# winter
  209. ((35.0, 33.3), 'epsg:4326', '2022-11-15 10:43:00+03:00', 3600, 30),# summer
  210. # Madison: 43°04′29″N 89°23′03″W (Duffie and Beckman, 2013; ex. 1.5.1, page 11)
  211. ((43.0, -89.4), 'epsg:4326', '2010-02-03 10:19:00-06:00', 0, -90),# winter
  212. ((43.0, -89.4), 'epsg:4326', '2010-02-03 10:19:00-05:00', 3600, -90),# summer
  213. ]
  214. for test in list_tests:
  215. mylocation = pse.timeplace.Location(
  216. coordinates=test[0],
  217. crs_string=test[1])
  218. mytime = dt.datetime.fromisoformat(test[2])
  219. # compute the local meridian (subtract dst offset since it is included)
  220. local_meridian = mylocation.local_standard_meridian_longitude(
  221. mytime, test[3])
  222. assert_allclose(local_meridian, test[4])
  223. # try causing errors
  224. list_tests = [
  225. # naive datetime objects
  226. # Nicosia: 35°10′21″N 33°21′54″E (Kalogirou, 2009; ex. 2.1, page 52)
  227. ((35.0, 33.3), 'epsg:4326', '2022-11-15 10:43:00', 0, 30),# winter
  228. ((35.0, 33.3), 'epsg:4326', '2022-11-15 10:43:00', 3600, 30),# summer
  229. # Madison: 43°04′29″N 89°23′03″W (Duffie and Beckman, 2013; ex. 1.5.1, page 11)
  230. ((43.0, -89.4), 'epsg:4326', '2010-02-03 10:19:00', 0, -90),# winter
  231. ((43.0, -89.4), 'epsg:4326', '2010-02-03 10:19:00', 3600, -90),# summer
  232. ]
  233. # error counter
  234. error_counter = 0
  235. for test in list_tests:
  236. # trigger the errors
  237. try:
  238. mylocation = pse.timeplace.Location(
  239. coordinates=test[0],
  240. crs_string=test[1])
  241. mytime = dt.datetime.fromisoformat(test[2])
  242. # compute the local meridian (subtract dst offset since it is included)
  243. local_meridian = mylocation.local_standard_meridian_longitude(
  244. mytime, test[3])
  245. except ValueError:
  246. error_counter += 1
  247. assert error_counter == len(list_tests)
  248. # *************************************************************************
  249. # *************************************************************************
  250. def test_local_timezone_with_dst(self):
  251. # create utc time zone
  252. tz_utc = dt.timezone.utc
  253. # create time zone with dst, northern hemisphere
  254. tz_with_dst_nh = ZoneInfo("Portugal")
  255. # create time zone without dst, northern hemisphere
  256. tz_without_dst_nh = ZoneInfo("Iceland")
  257. # create time zone with dst, southern hemisphere
  258. tz_with_dst_sh = ZoneInfo("America/Asuncion")
  259. # create time zone without dst, souther hemisphere
  260. tz_without_dst_sh = ZoneInfo("America/Argentina/Buenos_Aires")
  261. # create tests with datetime objects with and without dst
  262. list_tests = [ # local time object, utc time, is dst active?, utc offset
  263. # Portugal, (Northern) Summer
  264. (dt.datetime(
  265. year=2012,
  266. month=6,
  267. day=5,
  268. hour=12,
  269. minute=0,
  270. second=0,
  271. microsecond=0,
  272. tzinfo=tz_with_dst_nh),
  273. dt.datetime(
  274. year=2012,
  275. month=6,
  276. day=5,
  277. hour=11,
  278. minute=0,
  279. second=0,
  280. microsecond=0,
  281. tzinfo=tz_utc),
  282. True,
  283. 3600),
  284. # Portugal, (Northern) Winter
  285. (dt.datetime(
  286. year=2012,
  287. month=12,
  288. day=5,
  289. hour=12,
  290. minute=0,
  291. second=0,
  292. microsecond=0,
  293. tzinfo=tz_with_dst_nh),
  294. dt.datetime(
  295. year=2012,
  296. month=12,
  297. day=5,
  298. hour=12,
  299. minute=0,
  300. second=0,
  301. microsecond=0,
  302. tzinfo=tz_utc),
  303. False,
  304. 0),
  305. # Iceland, (Northern) Summer
  306. (dt.datetime(
  307. year=2012,
  308. month=6,
  309. day=5,
  310. hour=12,
  311. minute=0,
  312. second=0,
  313. microsecond=0,
  314. tzinfo=tz_without_dst_nh),
  315. dt.datetime(
  316. year=2012,
  317. month=6,
  318. day=5,
  319. hour=12,
  320. minute=0,
  321. second=0,
  322. microsecond=0,
  323. tzinfo=tz_utc),
  324. False,
  325. 0),
  326. # Iceland, (Northern) Winter
  327. (dt.datetime(
  328. year=2012,
  329. month=12,
  330. day=5,
  331. hour=12,
  332. minute=0,
  333. second=0,
  334. microsecond=0,
  335. tzinfo=tz_without_dst_nh),
  336. dt.datetime(
  337. year=2012,
  338. month=12,
  339. day=5,
  340. hour=12,
  341. minute=0,
  342. second=0,
  343. microsecond=0,
  344. tzinfo=tz_utc),
  345. False,
  346. 0),
  347. # Paraguay, (Northern) Summer
  348. (dt.datetime(
  349. year=2012,
  350. month=6,
  351. day=5,
  352. hour=12,
  353. minute=0,
  354. second=0,
  355. microsecond=0,
  356. tzinfo=tz_with_dst_sh),
  357. dt.datetime(
  358. year=2012,
  359. month=6,
  360. day=5,
  361. hour=16,
  362. minute=0,
  363. second=0,
  364. microsecond=0,
  365. tzinfo=tz_utc),
  366. False,
  367. 0),
  368. # Paraguay, (Northern) Winter
  369. (dt.datetime(
  370. year=2012,
  371. month=12,
  372. day=5,
  373. hour=12,
  374. minute=0,
  375. second=0,
  376. microsecond=0,
  377. tzinfo=tz_with_dst_sh),
  378. dt.datetime(
  379. year=2012,
  380. month=12,
  381. day=5,
  382. hour=15,
  383. minute=0,
  384. second=0,
  385. microsecond=0,
  386. tzinfo=tz_utc),
  387. True,
  388. 3600),
  389. # Argentina, (Northern) Summer
  390. (dt.datetime(
  391. year=2012,
  392. month=6,
  393. day=5,
  394. hour=12,
  395. minute=0,
  396. second=0,
  397. microsecond=0,
  398. tzinfo=tz_without_dst_sh),
  399. dt.datetime(
  400. year=2012,
  401. month=6,
  402. day=5,
  403. hour=15,
  404. minute=0,
  405. second=0,
  406. microsecond=0,
  407. tzinfo=tz_utc),
  408. False,
  409. 0),
  410. # Argentina, (Northern) Winter
  411. (dt.datetime(
  412. year=2012,
  413. month=12,
  414. day=5,
  415. hour=12,
  416. minute=0,
  417. second=0,
  418. microsecond=0,
  419. tzinfo=tz_without_dst_sh),
  420. dt.datetime(
  421. year=2012,
  422. month=12,
  423. day=5,
  424. hour=15,
  425. minute=0,
  426. second=0,
  427. microsecond=0,
  428. tzinfo=tz_utc),
  429. False,
  430. 0)
  431. ]
  432. # verify that that the utc offset changes
  433. for test in list_tests:
  434. localtime = test[0]
  435. utc_time = test[1]
  436. # verify if there is or not dst-related offset
  437. assert test[2] == (localtime.dst() > dt.timedelta(seconds=0))
  438. assert localtime.dst() == dt.timedelta(seconds=test[3])
  439. #no offset from utc
  440. assert utc_time.utcoffset() == dt.timedelta(seconds=0)
  441. # check if utc times match (note: datetime objects are hashable)
  442. assert localtime.astimezone(tz_utc) == utc_time
  443. #******************************************************************************
  444. #******************************************************************************