Date.ecl 96 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347
  1. /*##############################################################################
  2. ## HPCC SYSTEMS software Copyright (C) 2012 HPCC Systems®. All rights reserved.
  3. ############################################################################## */
  4. /* The functions defined in this module are provisional, and subject to change */
  5. IMPORT lib_stringlib.StringLib;
  6. IMPORT lib_timelib.TimeLib;
  7. EXPORT Date := MODULE
  8. // A record structure with the different date elements separated out.
  9. EXPORT Date_rec := RECORD
  10. INTEGER2 year;
  11. UNSIGNED1 month;
  12. UNSIGNED1 day;
  13. END;
  14. // An unsigned number holding a date in the decimal form YYYYMMDD.
  15. // This type doesn't support dates before 1AD.
  16. EXPORT Date_t := UNSIGNED4;
  17. // A number of elapsed days. Origin depends on the function called.
  18. EXPORT Days_t := INTEGER4;
  19. // A record structure with the different time elements separated out.
  20. EXPORT Time_rec := RECORD
  21. UNSIGNED1 hour;
  22. UNSIGNED1 minute;
  23. UNSIGNED1 second;
  24. END;
  25. // An unsigned number holding a time of day in the decimal form HHMMDD.
  26. EXPORT Time_t := UNSIGNED3;
  27. // A signed number holding a number of seconds. Can be used to represent either
  28. // a duration or the number of seconds since epoch (Jan 1, 1970).
  29. EXPORT Seconds_t := INTEGER8;
  30. // A record structure with the different date and time elements separated out.
  31. EXPORT DateTime_rec := RECORD
  32. Date_rec;
  33. Time_Rec;
  34. END;
  35. // A signed number holding a number of microseconds. Can be used to represent
  36. // either a duration or the number of microseconds since epoch (Jan 1, 1970).
  37. EXPORT Timestamp_t := INTEGER8;
  38. /**
  39. * Extracts the year from a date type.
  40. *
  41. * @param date The date.
  42. * @return An integer representing the year.
  43. */
  44. EXPORT INTEGER2 Year(Date_t date) := date DIV 10000;
  45. /**
  46. * Extracts the month from a date type.
  47. *
  48. * @param date The date.
  49. * @return An integer representing the year.
  50. */
  51. EXPORT UNSIGNED1 Month(Date_t date) := (date DIV 100) % 100;
  52. /**
  53. * Extracts the day of the month from a date type.
  54. *
  55. * @param date The date.
  56. * @return An integer representing the year.
  57. */
  58. EXPORT UNSIGNED1 Day(Date_t date) := date % 100;
  59. /**
  60. * Extracts the hour from a time type.
  61. *
  62. * @param time The time.
  63. * @return An integer representing the hour.
  64. */
  65. EXPORT UNSIGNED1 Hour(Time_t time) := time DIV 10000;
  66. /**
  67. * Extracts the minutes from a time type.
  68. *
  69. * @param time The time.
  70. * @return An integer representing the minutes.
  71. */
  72. EXPORT UNSIGNED1 Minute(Time_t time) := (time DIV 100) % 100;
  73. /**
  74. * Extracts the seconds from a time type.
  75. *
  76. * @param time The time.
  77. * @return An integer representing the seconds.
  78. */
  79. EXPORT UNSIGNED1 Second(Time_t time) := time % 100;
  80. /**
  81. * Combines year, month day to create a date type.
  82. *
  83. * @param year The year (0-9999).
  84. * @param month The month (1-12).
  85. * @param day The day (1..daysInMonth).
  86. * @return A date created by combining the fields.
  87. */
  88. EXPORT Date_t DateFromParts(INTEGER2 year, UNSIGNED1 month, UNSIGNED1 day) := (year * 100 + month) * 100 + day;
  89. /**
  90. * Combines hour, minute second to create a time type.
  91. *
  92. * @param hour The hour (0-23).
  93. * @param minute The minute (0-59).
  94. * @param second The second (0-59).
  95. * @return A time created by combining the fields.
  96. */
  97. EXPORT Time_t TimeFromParts(UNSIGNED1 hour, UNSIGNED1 minute, UNSIGNED1 second) := (hour * 100 + minute) * 100 + second;
  98. /**
  99. * Combines date and time components to create a seconds type. The date must
  100. * be represented within the Gregorian calendar after the year 1600.
  101. *
  102. * @param year The year (1601-30827).
  103. * @param month The month (1-12).
  104. * @param day The day (1..daysInMonth).
  105. * @param hour The hour (0-23).
  106. * @param minute The minute (0-59).
  107. * @param second The second (0-59).
  108. * @param is_local_time TRUE if the datetime components are expressed
  109. * in local time rather than UTC, FALSE if the
  110. * components are expressed in UTC. Optional,
  111. * defaults to FALSE.
  112. * @return A Seconds_t value created by combining the fields.
  113. */
  114. EXPORT Seconds_t SecondsFromParts(INTEGER2 year,
  115. UNSIGNED1 month,
  116. UNSIGNED1 day,
  117. UNSIGNED1 hour,
  118. UNSIGNED1 minute,
  119. UNSIGNED1 second,
  120. BOOLEAN is_local_time = FALSE) :=
  121. TimeLib.SecondsFromParts(year, month, day, hour, minute, second, is_local_time);
  122. /**
  123. * Converts the number of seconds since epoch to a structure containing
  124. * date and time parts. The result must be representable within the
  125. * Gregorian calendar after the year 1600.
  126. *
  127. * @param seconds The number of seconds since epoch.
  128. * @param is_local_time TRUE if seconds is expressed in local time
  129. * rather than UTC, FALSE if seconds is expressed
  130. * in UTC. Optional, defaults to FALSE.
  131. * @return Module with exported attributes for year, month,
  132. * day, hour, minute, second, day_of_week, date
  133. * and time.
  134. */
  135. EXPORT SecondsToParts(Seconds_t seconds, BOOLEAN is_local_time = FALSE) := FUNCTION
  136. parts := ROW(TimeLib.SecondsToParts(seconds, is_local_time));
  137. result := MODULE
  138. EXPORT INTEGER2 year := parts.year + 1900;
  139. EXPORT UNSIGNED1 month := parts.mon + 1;
  140. EXPORT UNSIGNED1 day := parts.mday;
  141. EXPORT UNSIGNED1 hour := parts.hour;
  142. EXPORT UNSIGNED1 minute := parts.min;
  143. EXPORT UNSIGNED1 second := parts.sec;
  144. EXPORT UNSIGNED1 day_of_week := parts.wday + 1;
  145. EXPORT Date_t date := DateFromParts(year,month,day);
  146. EXPORT Time_t time := TimeFromParts(hour,minute,second);
  147. END;
  148. RETURN result;
  149. END;
  150. /**
  151. * Converts the number of microseconds since epoch to the number of seconds
  152. * since epoch.
  153. *
  154. * @param timestamp The number of microseconds since epoch.
  155. * @return The number of seconds since epoch.
  156. */
  157. EXPORT Seconds_t TimestampToSeconds(Timestamp_t timestamp) := timestamp DIV 1000000;
  158. /**
  159. * Tests whether the year is a leap year in the Gregorian calendar.
  160. *
  161. * @param year The year (0-9999).
  162. * @return True if the year is a leap year.
  163. */
  164. EXPORT BOOLEAN IsLeapYear(INTEGER2 year) := (year % 4 = 0) AND ((year % 100 != 0) OR (year % 400 = 0));
  165. /**
  166. * Tests whether a date is a leap year in the Gregorian calendar.
  167. *
  168. * @param date The date.
  169. * @return True if the year is a leap year.
  170. */
  171. EXPORT BOOLEAN IsDateLeapYear(Date_t date) := IsLeapYear(Year(date));
  172. SHARED YearDelta := +4800; // Offset the years by 4800 so dates up to -4713 work
  173. SHARED GregorianDateOrigin := -1753469; // 1 Jan 1AD = 1
  174. /**
  175. * Combines year, month, day in the Gregorian calendar to create the number
  176. * days since 31st December 1BC.
  177. *
  178. * @param year The year (-4713..9999).
  179. * @param month The month (1-12). A missing value (0) is treated as 1.
  180. * @param day The day (1..daysInMonth). A missing value (0) is treated as 1.
  181. * @return The number of elapsed days (1 Jan 1AD = 1)
  182. */
  183. EXPORT Days_t FromGregorianYMD(INTEGER2 year, UNSIGNED1 month, UNSIGNED1 day) := FUNCTION
  184. //See Frequently Asked Questions about Calendars by Claus Toendering
  185. safeDay := MAX(1, day); // treat 0 as 1
  186. safeMonth := MAX(1, month); // treat 0 as 1
  187. a := (14 - safeMonth) DIV 12;
  188. y := year + YearDelta - a;
  189. m := safeMonth + 12*a - 3;
  190. jd := safeDay + (153 * m + 2) DIV 5 + 365 * y + y DIV 4 - y DIV 100 + y DIV 400;
  191. RETURN jd + (GregorianDateOrigin - 1);
  192. END;
  193. /**
  194. * Converts the number days since 31st December 1BC to a date in the Gregorian calendar.
  195. *
  196. * @param days The number of elapsed days (1 Jan 1AD = 1)
  197. * @return Module containing Year, Month, Day in the Gregorian calendar
  198. */
  199. EXPORT ToGregorianYMD(Days_t days) := FUNCTION
  200. // See Fliegel and van Flandern (1968) and other quoted sources
  201. // (e.g., http://www.ortelius.de/kalender/calc_en.php)
  202. // Process as 4, 100 and 400 year cycles.
  203. daysIn4Years := 3*365+366;
  204. daysIn100Years := 25*daysIn4Years-1;
  205. daysIn400Years := 4*daysIn100Years+1;
  206. // Calculate days in each of the cycles.
  207. adjustedDays := days - GregorianDateOrigin;
  208. num400Years := adjustedDays div daysIn400Years;
  209. rem400Years := adjustedDays % daysIn400Years;
  210. num100Years := ((rem400Years div daysIn100Years + 1) * 3) DIV 4;
  211. rem100Years := rem400Years - num100Years * daysIn100Years;
  212. num4Years := rem100Years div daysIn4Years;
  213. rem4Years := rem100Years % daysIn4Years;
  214. years := ((rem4Years div 365 + 1) * 3) DIV 4;
  215. numdays := rem4Years - years * 365;
  216. //Now calculate the actual year, month day
  217. y := num400Years * 400 + num100Years * 100 + num4Years * 4 + years;
  218. m := (numdays * 5 + 308) div 153 - 2;
  219. d := numdays - (m + 4) * 153 div 5 + 122;
  220. result := MODULE
  221. EXPORT year := (y + (m + 2) div 12) - YearDelta;
  222. EXPORT month := (m + 2) % 12 + 1;
  223. EXPORT day := d + 1;
  224. END;
  225. RETURN result;
  226. END;
  227. /**
  228. * Converts a date in the Gregorian calendar to the number days since 31st December 1BC.
  229. *
  230. * @param date The date (using the Gregorian calendar)
  231. * @return The number of elapsed days (1 Jan 1AD = 1)
  232. */
  233. EXPORT Days_t FromGregorianDate(Date_t date) :=
  234. DEFINE FromGregorianYMD(Year(date), Month(date), Day(date));
  235. /**
  236. * Converts the number days since 31st December 1BC to a date in the Gregorian calendar.
  237. *
  238. * @param days The number of elapsed days (1 Jan 1AD = 1)
  239. * @return A Date_t in the Gregorian calendar
  240. */
  241. EXPORT Date_t ToGregorianDate(Days_t days) := DEFINE FUNCTION
  242. date := ToGregorianYMD(days);
  243. RETURN DateFromParts(date.year, date.month, date.day);
  244. END;
  245. /**
  246. * Returns a number representing the day of the year indicated by the given date.
  247. * The date must be in the Gregorian calendar after the year 1600.
  248. *
  249. * @param date A Date_t value.
  250. * @return A number (1-366) representing the number of days since
  251. * the beginning of the year.
  252. */
  253. EXPORT UNSIGNED2 DayOfYear(Date_t date) := FUNCTION
  254. theYear := Year(date);
  255. theMonth := Month(date);
  256. theDay := Day(date);
  257. dayNum := TimeLib.GetDayOfYear(theYear, theMonth, theDay) + 1;
  258. RETURN dayNum;
  259. END;
  260. /**
  261. * Returns a number representing the day of the week indicated by the given date.
  262. * The date must be in the Gregorian calendar after the year 1600.
  263. *
  264. * @param date A Date_t value.
  265. * @return A number 1-7 representing the day of the week, where 1 = Sunday.
  266. */
  267. EXPORT UNSIGNED1 DayOfWeek(Date_t date) := FUNCTION
  268. theYear := Year(date);
  269. theMonth := Month(date);
  270. theDay := Day(date);
  271. dayCode := TimeLib.GetDayOfWeek(theYear, theMonth, theDay) + 1;
  272. RETURN dayCode;
  273. END;
  274. /**
  275. * Tests whether the year is a leap year in the Julian calendar.
  276. *
  277. * @param year The year (0-9999).
  278. * @return True if the year is a leap year.
  279. */
  280. EXPORT BOOLEAN IsJulianLeapYear(INTEGER2 year) := (year % 4 = 0);
  281. SHARED JulianDateOrigin := -1753505; // 1 Jan 1AD = 1
  282. /**
  283. * Combines year, month, day in the Julian calendar to create the number
  284. * days since 31st December 1BC.
  285. *
  286. * @param year The year (-4800..9999).
  287. * @param month The month (1-12).
  288. * @param day The day (1..daysInMonth).
  289. * @return The number of elapsed days (1 Jan 1AD = 1)
  290. */
  291. EXPORT Days_t FromJulianYMD(INTEGER2 year, UNSIGNED1 month, UNSIGNED1 day) := FUNCTION
  292. //See Frequently Asked Questions about Calendars by Claus Toendering
  293. a := (14 - month) DIV 12;
  294. y := year + YearDelta - a;
  295. m := month + 12*a - 3;
  296. jd := day + (153 * m + 2) DIV 5 + 365 * y + y DIV 4;
  297. RETURN jd + (JulianDateOrigin-1);
  298. END;
  299. /**
  300. * Converts the number days since 31st December 1BC to a date in the Julian calendar.
  301. *
  302. * @param days The number of elapsed days (1 Jan 1AD = 1)
  303. * @return Module containing Year, Month, Day in the Julian calendar
  304. */
  305. EXPORT ToJulianYMD(Days_t days) := FUNCTION
  306. //See Frequently Asked Questions about Calendars by Claus Toendering
  307. daysIn4Years := 3*365+366;
  308. c := days - JulianDateOrigin;
  309. d := (4 * c + 3) DIV daysIn4Years;
  310. e := c - ((daysIn4Years * d) DIV 4);
  311. m := (5 * e + 2) DIV 153;
  312. result := MODULE
  313. EXPORT UNSIGNED1 day := e - ((153 * m + 2) DIV 5) + 1;
  314. EXPORT UNSIGNED1 month := m + 3 - 12 * (m DIV 10);
  315. EXPORT INTEGER2 year := d - YearDelta + (m DIV 10);
  316. END;
  317. RETURN result;
  318. END;
  319. /**
  320. * Converts a date in the Julian calendar to the number days since 31st December 1BC.
  321. *
  322. * @param date The date (using the Julian calendar)
  323. * @return The number of elapsed days (1 Jan 1AD = 1)
  324. */
  325. EXPORT Days_t FromJulianDate(Date_t date) := DEFINE FromJulianYMD(Year(date), Month(date), Day(date));
  326. /**
  327. * Converts the number days since 31st December 1BC to a date in the Julian calendar.
  328. *
  329. * @param days The number of elapsed days (1 Jan 1AD = 1)
  330. * @return A Date_t in the Julian calendar
  331. */
  332. EXPORT Date_t ToJulianDate(Days_t days) := DEFINE FUNCTION
  333. date := ToJulianYMD(days);
  334. RETURN DateFromParts(date.year, date.month, date.day);
  335. END;
  336. SHARED Date1900Delta := 693596; // 1 Jan 1900 = 0
  337. /**
  338. * Returns the number of days since 1st January 1900 (using the Gregorian Calendar)
  339. *
  340. * @param year The year (-4713..9999).
  341. * @param month The month (1-12). A missing value (0) is treated as 1.
  342. * @param day The day (1..daysInMonth). A missing value (0) is treated as 1.
  343. * @return The number of elapsed days since 1st January 1900
  344. */
  345. EXPORT Days_t DaysSince1900(INTEGER2 year, UNSIGNED1 month, UNSIGNED1 day) :=
  346. FromGregorianYMD(year, month, day) - Date1900Delta;
  347. /**
  348. * Returns the number of days since 1st January 1900 (using the Gregorian Calendar)
  349. *
  350. * @param date The date
  351. * @return The number of elapsed days since 1st January 1900
  352. */
  353. EXPORT Days_t ToDaysSince1900(Date_t date) := DaysSince1900(Year(date),Month(date),Day(date));
  354. /**
  355. * Converts the number days since 1st January 1900 to a date in the Julian calendar.
  356. *
  357. * @param days The number of elapsed days since 1st Jan 1900
  358. * @return A Date_t in the Julian calendar
  359. */
  360. EXPORT Date_t FromDaysSince1900(Days_t days) := ToGregorianDate(days + Date1900Delta);
  361. /**
  362. * Calculate the number of whole years between two dates.
  363. *
  364. * @param from The first date
  365. * @param to The last date
  366. * @return The number of years between them.
  367. */
  368. EXPORT INTEGER YearsBetween (Date_t from, Date_t to) := FUNCTION
  369. fromDate := MIN(from, to);
  370. toDate := MAX(from, to);
  371. years := Year(toDate) - Year(fromDate);
  372. adjustedYears := years - IF(Month(fromDate) > Month(toDate) OR (Month(fromDate) = Month(toDate) AND Day(fromDate) > Day(toDate)), 1, 0);
  373. RETURN adjustedYears * IF(from > to, -1, 1);
  374. END;
  375. /**
  376. * Calculate the number of whole months between two dates.
  377. *
  378. * If month_ends_equal is set to TRUE and the given dates fall on the last day
  379. * of their respective months, the dates' day values will be considered equal
  380. * regardless of their actual values. For example, given the dates 20160331
  381. * and 20160930 (last day of March and last day of September, respectively), if
  382. * month_ends_equal is FALSE then the function's result will be 5; if
  383. * month_ends_equal is TRUE then the result will be 6.
  384. *
  385. * @param from The first date
  386. * @param to The last date
  387. * @param month_ends_equal If TRUE and both dates fall on the last day of
  388. * their respective months, treat the difference
  389. * between the dates as whole months regardless of
  390. * the actual day values; if FALSE then the day value
  391. * of each date may be considered when calculating
  392. * the difference; OPTIONAL, defaults to FALSE
  393. * @return The number of months between them.
  394. */
  395. EXPORT INTEGER MonthsBetween(Date_t from, Date_t to, BOOLEAN month_ends_equal = FALSE) := FUNCTION
  396. fromDate := MIN(from, to);
  397. toDate := MAX(from, to);
  398. years := Year(toDate) - Year(fromDate);
  399. months := Month(toDate) - Month(fromDate);
  400. result := years * 12 + months;
  401. fromIsMonthEnd := Day(fromDate) = CHOOSE(Month(fromDate),31,IF(IsLeapYear(Year(fromDate)),29,28),31,30,31,30,31,31,30,31,30,31);
  402. toIsMonthEnd := Day(toDate) = CHOOSE(Month(toDate),31,IF(IsLeapYear(Year(toDate)),29,28),31,30,31,30,31,31,30,31,30,31);
  403. adjustment := MAP
  404. (
  405. month_ends_equal AND fromIsMonthEnd AND toIsMonthEnd => 0,
  406. Day(fromDate) > Day(toDate) => 1,
  407. 0
  408. );
  409. adjustedResult := result - adjustment;
  410. RETURN adjustedResult * IF(from > to, -1, 1);
  411. END;
  412. /**
  413. * Calculate the number of days between two dates.
  414. *
  415. * @param from The first date
  416. * @param to The last date
  417. * @return The number of days between them.
  418. */
  419. EXPORT INTEGER DaysBetween(Date_t from, Date_t to) := FUNCTION
  420. fromDays := FromGregorianDate(from);
  421. toDays := FromGregorianDate(to);
  422. RETURN toDays - fromDays;
  423. END;
  424. /**
  425. * Combines the fields from a Date_rec to create a Date_t
  426. *
  427. * @param date The row containing the date.
  428. * @return A Date_t representing the combined values.
  429. */
  430. EXPORT Date_t DateFromDateRec(Date_rec date) := (date.year * 100 + date.month) * 100 + date.day;
  431. /**
  432. * Combines the fields from a Date_rec to create a Date_t
  433. *
  434. * @param date The row containing the date.
  435. * @return A Date_t representing the combined values.
  436. */
  437. EXPORT Date_t DateFromRec(Date_rec date) := DateFromDateRec(date) : DEPRECATED('Replaced with DateFromDateRec() function');
  438. /**
  439. * Combines the fields from a Time_rec to create a Time_t
  440. *
  441. * @param time The row containing the time.
  442. * @return A Time_t representing the combined values.
  443. */
  444. EXPORT Time_t TimeFromTimeRec(Time_rec time) := (time.hour * 100 + time.minute) * 100 + time.second;
  445. /**
  446. * Combines the date fields from a DateTime_rec to create a Date_t
  447. *
  448. * @param datetime The row containing the datetime.
  449. * @return A Date_t representing the combined values.
  450. */
  451. EXPORT Date_t DateFromDateTimeRec(DateTime_rec datetime) := (datetime.year * 100 + datetime.month) * 100 + datetime.day;
  452. /**
  453. * Combines the time fields from a DateTime_rec to create a Time_t
  454. *
  455. * @param datetime The row containing the datetime.
  456. * @return A Time_t representing the combined values.
  457. */
  458. EXPORT Time_t TimeFromDateTimeRec(DateTime_rec datetime) := (datetime.hour * 100 + datetime.minute) * 100 + datetime.second;
  459. /**
  460. * Combines the date and time fields from a DateTime_rec to create a Seconds_t
  461. *
  462. * @param datetime The row containing the datetime.
  463. * @param is_local_time TRUE if the datetime components are expressed in local
  464. * time rather than UTC, FALSE if the components are
  465. * expressed in UTC. Optional, defaults to FALSE.
  466. * @return A Seconds_t representing the combined values.
  467. */
  468. EXPORT Seconds_t SecondsFromDateTimeRec(DateTime_rec datetime, BOOLEAN is_local_time = FALSE) :=
  469. SecondsFromParts(datetime.year, datetime.month, datetime.day, datetime.hour, datetime.minute, datetime.second, is_local_time);
  470. /**
  471. * Converts a string to a Date_t using the relevant string format.
  472. *
  473. * @param date_text The string to be converted.
  474. * @param format The format of the input string.
  475. * (See documentation for strftime)
  476. * @return The date that was matched in the string. Returns 0 if failed to match
  477. * or if the date components match but the result is an invalid date.
  478. *
  479. * Supported characters:
  480. %B Full month name
  481. %b or %h Abbreviated month name
  482. %d Day of month (two digits)
  483. %e Day of month (two digits, or a space followed by a single digit)
  484. %m Month (two digits)
  485. %t Whitespace
  486. %y Year within century (00-99)
  487. %Y Full year (yyyy)
  488. %j Julian day (1-366)
  489. Common date formats
  490. American '%m/%d/%Y' mm/dd/yyyy
  491. Euro '%d/%m/%Y' dd/mm/yyyy
  492. Iso format '%Y-%m-%d' yyyy-mm-dd
  493. Iso basic '%Y%m%d' yyyymmdd
  494. '%d-%b-%Y' dd-mon-yyyy e.g., '21-Mar-1954'
  495. */
  496. EXPORT Date_t FromStringToDate(STRING date_text, VARSTRING format) :=
  497. StringLib.StringToDate(date_text, format);
  498. /**
  499. * Converts a string to a date using the relevant string format.
  500. *
  501. * @param date_text The string to be converted.
  502. * @param format The format of the input string.
  503. * (See documentation for strftime)
  504. * @return The date that was matched in the string.
  505. * Returns 0 if failed to match.
  506. */
  507. EXPORT Date_t FromString(STRING date_text, VARSTRING format) :=
  508. FromStringToDate(date_text, format) : DEPRECATED('Replaced with FromStringToDate() function');
  509. /**
  510. * Converts a string to a Time_t using the relevant string format.
  511. *
  512. * @param date_text The string to be converted.
  513. * @param format The format of the input string.
  514. * (See documentation for strftime)
  515. * @return The time that was matched in the string. Returns 0 if failed to match.
  516. *
  517. * Supported characters:
  518. %H Hour (two digits)
  519. %k Hour (two digits, or a space followed by a single digit)
  520. %M Minute (two digits)
  521. %S Second (two digits)
  522. %t Whitespace
  523. */
  524. EXPORT Time_t FromStringToTime(STRING time_text, VARSTRING format) :=
  525. StringLib.StringToTimeOfDay(time_text, format);
  526. /**
  527. * Converts a string to a Seconds_t using the relevant string format.
  528. *
  529. * @param datetime_text The string to be converted.
  530. * @param format The format of the input string.
  531. * (See documentation for strftime)
  532. * @param is_local_time TRUE if datetime_text is expressed in local time
  533. * rather than UTC, FALSE otherwise. Optional, defaults
  534. * to FALSE.
  535. * @return The seconds that was matched in the string. Returns 0 if failed to match.
  536. *
  537. * Supported date characters:
  538. %B Full month name
  539. %b or %h Abbreviated month name
  540. %d Day of month (two digits)
  541. %e Day of month (two digits, or a space followed by a single digit)
  542. %m Month (two digits)
  543. %y Year within century (00-99)
  544. %Y Full year (yyyy)
  545. %j Julian day (1-366)
  546. *
  547. * Supported time characters:
  548. %H Hour (two digits)
  549. %k Hour (two digits, or a space followed by a single digit)
  550. %M Minute (two digits)
  551. %S Second (two digits)
  552. *
  553. * Other supported characters:
  554. %t Whitespace
  555. */
  556. EXPORT Seconds_t FromStringToSeconds(STRING datetime_text, VARSTRING format, BOOLEAN is_local_time = FALSE) :=
  557. TimeLib.StringToSeconds(datetime_text, format, is_local_time);
  558. /**
  559. * Matches a string against a set of date string formats and returns a valid
  560. * Date_t object from the first format that successfully parses the string.
  561. *
  562. * @param date_text The string to be converted.
  563. * @param formats A set of formats to check against the string.
  564. * (See documentation for strftime)
  565. * @return The date that was matched in the string.
  566. * Returns 0 if failed to match.
  567. *
  568. * Supported characters:
  569. %B Full month name
  570. %b or %h Abbreviated month name
  571. %d Day of month (two digits)
  572. %e Day of month (two digits, or a space followed by a single digit)
  573. %m Month (two digits)
  574. %t Whitespace
  575. %y Year within century (00-99)
  576. %Y Full year (yyyy)
  577. %j Julian day (1-366)
  578. Common date formats
  579. American '%m/%d/%Y' mm/dd/yyyy
  580. Euro '%d/%m/%Y' dd/mm/yyyy
  581. Iso format '%Y-%m-%d' yyyy-mm-dd
  582. Iso basic '%Y%m%d' yyyymmdd
  583. '%d-%b-%Y' dd-mon-yyyy e.g., '21-Mar-1954'
  584. */
  585. EXPORT Date_t MatchDateString(STRING date_text, SET OF VARSTRING formats) :=
  586. StringLib.MatchDate(date_text, formats);
  587. /**
  588. * Matches a string against a set of time string formats and returns a valid
  589. * Time_t object from the first format that successfully parses the string.
  590. *
  591. * @param time_text The string to be converted.
  592. * @param formats A set of formats to check against the string.
  593. * (See documentation for strftime)
  594. * @return The time that was matched in the string.
  595. * Returns 0 if failed to match.
  596. */
  597. EXPORT Time_t MatchTimeString(STRING time_text, SET OF VARSTRING formats) :=
  598. StringLib.MatchTimeOfDay(time_text, formats);
  599. /**
  600. * Formats a date as a string.
  601. *
  602. * @param date The date to be converted.
  603. * @param format The format template to use for the conversion;
  604. * see strftime() for appropriate values. The maximum
  605. * length of the resulting string is 255 characters.
  606. * Optional; defaults to '%Y-%m-%d' which is YYYY-MM-DD.
  607. * @return Blank if date cannot be formatted, or the date in the
  608. * requested format.
  609. */
  610. EXPORT STRING DateToString(Date_t date, VARSTRING format = '%Y-%m-%d') :=
  611. TimeLib.DateToString(date, format);
  612. /**
  613. * Formats a time as a string.
  614. *
  615. * @param time The time to be converted.
  616. * @param format The format template to use for the conversion;
  617. * see strftime() for appropriate values. The maximum
  618. * length of the resulting string is 255 characters.
  619. * Optional; defaults to '%H:%M:%S' which is HH:MM:SS.
  620. * @return Blank if the time cannot be formatted, or the time
  621. * in the requested format.
  622. */
  623. EXPORT STRING TimeToString(Time_t time, VARSTRING format = '%H:%M:%S') :=
  624. TimeLib.TimeToString(time, format);
  625. /**
  626. * Converts a Seconds_t value into a human-readable string using a format template.
  627. *
  628. * @param seconds The seconds since epoch.
  629. * @param format The format template to use for the conversion; see
  630. * strftime() for appropriate values. The maximum length
  631. * of the resulting string is 255 characters.
  632. * Optional; defaults to '%Y-%m-%dT%H:%M:%S' which is YYYY-MM-DDTHH:MM:SS.
  633. * @return The converted seconds as a string.
  634. */
  635. EXPORT STRING SecondsToString(Seconds_t seconds, VARSTRING format = '%Y-%m-%dT%H:%M:%S') :=
  636. TimeLib.SecondsToString(seconds, format);
  637. /**
  638. * Converts a Timestamp_t value into a human-readable string using a format template.
  639. *
  640. * @param timestamp The microseconds since epoch.
  641. * @param format The format template to use for the conversion; see
  642. * strftime() for appropriate format specifiers. Two
  643. * additional format specifiers are available to show
  644. * fractional seconds:
  645. * %@ - fraction of seconds in microseconds (6 digits)
  646. * %# - fraction of seconds in milliseconds (3 digits)
  647. * The maximum length of the resulting string is 255
  648. * characters. This parameter is optional and defaults to
  649. * '%Y-%m-%dT%H:%M:%S.%@' which is YYYY-MM-DDTHH:MM:SS.ssssss.
  650. * @return The converted timestamp as a string.
  651. */
  652. EXPORT STRING TimestampToString(Timestamp_t timestamp, VARSTRING format = '%Y-%m-%dT%H:%M:%S.%@') := FUNCTION
  653. ms := INTFORMAT(timestamp % 1000000, 6, 1);
  654. f2 := REGEXREPLACE('%@', format, ms);
  655. f3 := REGEXREPLACE('%#', f2, ms[..3]);
  656. RETURN TimeLib.SecondsToString(timestamp DIV 1000000, f3);
  657. END;
  658. /**
  659. * Formats a date as a string.
  660. *
  661. * @param date The date to be converted.
  662. * @param format The format the date is output in.
  663. * (See documentation for strftime)
  664. * @return Blank if date cannot be formatted, or the date in the
  665. * requested format.
  666. */
  667. EXPORT STRING ToString(Date_t date, VARSTRING format) := DateToString(date, format) : DEPRECATED('Replaced with DateToString() function');
  668. /**
  669. * Converts a date from one format to another
  670. *
  671. * @param date_text The string containing the date to be converted.
  672. * @param from_format The format the date is to be converted from.
  673. * @param to_format The format the date is to be converted to.
  674. * @return The converted string, or blank if it failed to match the format.
  675. * @see FromStringToDate
  676. */
  677. EXPORT STRING ConvertDateFormat(STRING date_text, VARSTRING from_format='%m/%d/%Y', VARSTRING to_format='%Y%m%d') := FUNCTION
  678. parsedDate := FromStringToDate(date_text, from_format);
  679. reformatResult := IF(parsedDate = (Date_t)0, '', DateToString(parsedDate, to_format));
  680. RETURN reformatResult;
  681. END;
  682. /**
  683. * Converts a date from one format to another
  684. *
  685. * @param date_text The string containing the date to be converted.
  686. * @param from_format The format the date is to be converted from.
  687. * @param to_format The format the date is to be converted to.
  688. * @return The converted string, or blank if it failed to match the format.
  689. */
  690. EXPORT STRING ConvertFormat(STRING date_text, VARSTRING from_format='%m/%d/%Y', VARSTRING to_format='%Y%m%d') :=
  691. ConvertDateFormat(date_text, from_format, to_format) : DEPRECATED('Replaced with ConvertDateFormat() function');
  692. /**
  693. * Converts a time from one format to another
  694. *
  695. * @param time_text The string containing the time to be converted.
  696. * @param from_format The format the time is to be converted from.
  697. * @param to_format The format the time is to be converted to.
  698. * @return The converted string, or blank if it failed to match the format.
  699. */
  700. EXPORT STRING ConvertTimeFormat(STRING time_text, VARSTRING from_format='%H%M%S', VARSTRING to_format='%H:%M:%S') :=
  701. TimeToString(FromStringToTime(time_text, from_format), to_format);
  702. /**
  703. * Converts a date that matches one of a set of formats to another.
  704. *
  705. * @param date_text The string containing the date to be converted.
  706. * @param from_formats The list of formats the date is to be converted from.
  707. * @param to_format The format the date is to be converted to.
  708. * @return The converted string, or blank if it failed to match the format.
  709. * @see MatchDateString
  710. */
  711. EXPORT STRING ConvertDateFormatMultiple(STRING date_text, SET OF VARSTRING from_formats, VARSTRING to_format='%Y%m%d') := FUNCTION
  712. matchResult := MatchDateString(date_text, from_formats);
  713. reformatResult := IF(matchResult = (Date_t)0, '', DateToString(matchResult, to_format));
  714. RETURN reformatResult;
  715. END;
  716. /**
  717. * Converts a date that matches one of a set of formats to another.
  718. *
  719. * @param date_text The string containing the date to be converted.
  720. * @param from_formats The list of formats the date is to be converted from.
  721. * @param to_format The format the date is to be converted to.
  722. * @return The converted string, or blank if it failed to match the format.
  723. */
  724. EXPORT STRING ConvertFormatMultiple(STRING date_text, SET OF VARSTRING from_formats, VARSTRING to_format='%Y%m%d') :=
  725. ConvertDateFormatMultiple(date_text, from_formats, to_format) : DEPRECATED('Replaced with ConvertDateFormatMultiple() function');
  726. /**
  727. * Converts a time that matches one of a set of formats to another.
  728. *
  729. * @param time_text The string containing the time to be converted.
  730. * @param from_formats The list of formats the time is to be converted from.
  731. * @param to_format The format the time is to be converted to.
  732. * @return The converted string, or blank if it failed to match the format.
  733. */
  734. EXPORT STRING ConvertTimeFormatMultiple(STRING time_text, SET OF VARSTRING from_formats, VARSTRING to_format='%H:%m:%s') :=
  735. TimeToString(MatchTimeString(time_text, from_formats), to_format);
  736. /**
  737. * Adjusts a date by incrementing or decrementing year, month and/or day values.
  738. * The date must be in the Gregorian calendar after the year 1600.
  739. * If the new calculated date is invalid then it will be normalized according
  740. * to mktime() rules. Example: 20140130 + 1 month = 20140302.
  741. *
  742. * @param date The date to adjust.
  743. * @param year_delta The requested change to the year value;
  744. * optional, defaults to zero.
  745. * @param month_delta The requested change to the month value;
  746. * optional, defaults to zero.
  747. * @param day_delta The requested change to the day of month value;
  748. * optional, defaults to zero.
  749. * @return The adjusted Date_t value.
  750. */
  751. EXPORT Date_t AdjustDate(Date_t date,
  752. INTEGER2 year_delta = 0,
  753. INTEGER4 month_delta = 0,
  754. INTEGER4 day_delta = 0) :=
  755. TimeLib.AdjustDate(date, year_delta, month_delta, day_delta);
  756. /**
  757. * Adjusts a date by adding or subtracting seconds. The date must be in the
  758. * Gregorian calendar after the year 1600. If the new calculated
  759. * date is invalid then it will be normalized according to mktime() rules.
  760. * Example: 20140130 + 172800 seconds = 20140201.
  761. *
  762. * @param date The date to adjust.
  763. * @param seconds_delta The requested change to the date, in seconds.
  764. * @return The adjusted Date_t value.
  765. */
  766. EXPORT Date_t AdjustDateBySeconds(Date_t date, INTEGER4 seconds_delta) :=
  767. TimeLib.AdjustDateBySeconds(date, seconds_delta);
  768. /**
  769. * Adjusts a time by incrementing or decrementing hour, minute and/or second
  770. * values. If the new calculated time is invalid then it will be normalized
  771. * according to mktime() rules.
  772. *
  773. * @param time The time to adjust.
  774. * @param hour_delta The requested change to the hour value;
  775. * optional, defaults to zero.
  776. * @param minute_delta The requested change to the minute value;
  777. * optional, defaults to zero.
  778. * @param second_delta The requested change to the second of month value;
  779. * optional, defaults to zero.
  780. * @return The adjusted Time_t value.
  781. */
  782. EXPORT Time_t AdjustTime(Time_t time,
  783. INTEGER2 hour_delta = 0,
  784. INTEGER4 minute_delta = 0,
  785. INTEGER4 second_delta = 0) :=
  786. TimeLib.AdjustTime(time, hour_delta, minute_delta, second_delta);
  787. /**
  788. * Adjusts a time by adding or subtracting seconds. If the new calculated
  789. * time is invalid then it will be normalized according to mktime() rules.
  790. *
  791. * @param time The time to adjust.
  792. * @param seconds_delta The requested change to the time, in seconds.
  793. * @return The adjusted Time_t value.
  794. */
  795. EXPORT Time_t AdjustTimeBySeconds(Time_t time, INTEGER4 seconds_delta) :=
  796. TimeLib.AdjustTimeBySeconds(time, seconds_delta);
  797. /**
  798. * Adjusts a Seconds_t value by adding or subtracting years, months, days,
  799. * hours, minutes and/or seconds. This is performed by first converting the
  800. * seconds into a full date/time structure, applying any delta values to
  801. * individual date/time components, then converting the structure back to the
  802. * number of seconds. This interim date must lie within Gregorian calendar
  803. * after the year 1600. If the interim structure is found to have an invalid
  804. * date/time then it will be normalized according to mktime() rules. Therefore,
  805. * some delta values (such as "1 month") are actually relative to the value of
  806. * the seconds argument.
  807. *
  808. * @param seconds The number of seconds to adjust.
  809. * @param year_delta The requested change to the year value;
  810. * optional, defaults to zero.
  811. * @param month_delta The requested change to the month value;
  812. * optional, defaults to zero.
  813. * @param day_delta The requested change to the day of month value;
  814. * optional, defaults to zero.
  815. * @param hour_delta The requested change to the hour value;
  816. * optional, defaults to zero.
  817. * @param minute_delta The requested change to the minute value;
  818. * optional, defaults to zero.
  819. * @param second_delta The requested change to the second of month value;
  820. * optional, defaults to zero.
  821. * @return The adjusted Seconds_t value.
  822. */
  823. EXPORT Seconds_t AdjustSeconds(Seconds_t seconds,
  824. INTEGER2 year_delta = 0,
  825. INTEGER4 month_delta = 0,
  826. INTEGER4 day_delta = 0,
  827. INTEGER4 hour_delta = 0,
  828. INTEGER4 minute_delta = 0,
  829. INTEGER4 second_delta = 0) :=
  830. TimeLib.AdjustSeconds(seconds, year_delta, month_delta, day_delta, hour_delta, minute_delta, second_delta);
  831. /**
  832. * Adjusts a date by incrementing or decrementing months and/or years. This
  833. * routine uses the rule outlined in McGinn v. State, 46 Neb. 427, 65 N.W. 46 (1895):
  834. * "The term calendar month, whether employed in statutes or contracts, and
  835. * not appearing to have been used in a different sense, denotes a period
  836. * terminating with the day of the succeeding month numerically corresponding
  837. * to the day of its beginning, less one. If there be no corresponding day of
  838. * the succeeding month, it terminates with the last day thereof." The
  839. * internet suggests similar legal positions exist in the Commonwealth
  840. * and Germany. Note that day adjustments are performed after year and month
  841. * adjustments using the preceding rules. As an example, Jan. 31, 2014 + 1 month
  842. * will result in Feb. 28, 2014; Jan. 31, 2014 + 1 month + 1 day will result
  843. * in Mar. 1, 2014.
  844. *
  845. * @param date The date to adjust, in the Gregorian calendar after 1600.
  846. * @param year_delta The requested change to the year value;
  847. * optional, defaults to zero.
  848. * @param month_delta The requested change to the month value;
  849. * optional, defaults to zero.
  850. * @param day_delta The requested change to the day value;
  851. * optional, defaults to zero.
  852. * @return The adjusted Date_t value.
  853. */
  854. EXPORT Date_t AdjustCalendar(Date_t date,
  855. INTEGER2 year_delta = 0,
  856. INTEGER4 month_delta = 0,
  857. INTEGER4 day_delta = 0) :=
  858. TimeLib.AdjustCalendar(date, year_delta, month_delta, day_delta);
  859. /**
  860. * Helper function. Calculates the 1-based week number of a date, starting from
  861. * a reference date. Week 1 always contains the reference date, and week 2
  862. * begins on the following day of the week indicated by the value of
  863. * startingDayOfWeek. This is not an ISO-8601 implementation of computing week
  864. * numbers ("week dates").
  865. *
  866. * @param date The date for which to compute the week number;
  867. * must be greater than or equal to referenceDate
  868. * @param referenceDate The date from which the week number counting begins;
  869. * must be less than or equal to date
  870. * @param startingDayOfWeek The index number of the first day of a week, 1-7,
  871. * where 1 = Sunday
  872. * @return The 1-based week number of date, relative to
  873. * referenceDate
  874. *
  875. * @see YearWeekNumFromDate, MonthWeekNumFromDate
  876. */
  877. SHARED WeekNumForDate(Date_t date, Date_t referenceDate, UNSIGNED1 startingDayOfWeek) := FUNCTION
  878. referenceDayOfWeek := DayOfWeek(referenceDate);
  879. startingDayOfWeekDelta := (startingDayOfWeek - referenceDayOfWeek) % 7;
  880. referenceFirstDateOfWeek := AdjustDate(referenceDate, day_delta := startingDayOfWeekDelta);
  881. numberOfDays := DaysBetween(referenceFirstDateOfWeek, date) + 1;
  882. weekNum0 := (numberOfDays + 6) DIV 7;
  883. weekNum := IF(startingDayOfWeek > referenceDayOfWeek, weekNum0 + 1, weekNum0);
  884. RETURN weekNum;
  885. END;
  886. /**
  887. * Returns a number representing the day of the week indicated by the given date in ISO-8601.
  888. * The date must be in the Gregorian calendar after the year 1600.
  889. *
  890. * @param date A Date_t value.
  891. * @return A number 1-7 representing the day of the week, where 1 = Monday.
  892. */
  893. EXPORT ISODayOfWeekFromDate(Date_t d) := ((DayOfWeek(d) + 5) % 7) + 1;
  894. /**
  895. * Helper function to figure out the number of weeks for a given year.
  896. * For more details, see: https://en.wikipedia.org/wiki/ISO_week_date#Weeks_per_year
  897. *
  898. * @param date A Date_t value.
  899. * @return Number between 0 and 6 to help figure out whether year is long (53 weeks) or short (52 weeks).
  900. */
  901. SHARED ISOWeeksP(INTEGER2 year) := (year + TRUNCATE(year/4) - TRUNCATE(year/100) + TRUNCATE(year/400)) % 7;
  902. /**
  903. * Returns true for years with 53 weeks, and false for years with 52 weeks.
  904. *
  905. * @param date A Date_t value.
  906. * @return TRUE if year is a long year (i.e. 53 weeks), FALSE otherwise.
  907. */
  908. EXPORT ISOIsLongYear(INTEGER2 year) := (ISOWeeksP(year) = 4 OR ISOWeeksP(year - 1) = 3);
  909. /**
  910. * Returns a number representing the maximum number of weeks for the year from date.
  911. *
  912. * @param date A Date_t value.
  913. * @return The number 52 for short years and 53 for long ones.
  914. */
  915. EXPORT ISOWeeksFromDate(Date_t d) := 52 + IF(ISOIsLongYear(Year(d)), 1, 0);
  916. /**
  917. * Returns a number representing the raw week number of the given date.
  918. *
  919. * @param date A Date_t value.
  920. * @return A number from 1 to 53.
  921. */
  922. EXPORT ISORawWeekNumForDate(Date_t d) := TRUNCATE((DayOfYear(d) - ISODayOfWeekFromDate(d) + 10) / 7);
  923. /**
  924. * Returns the ISO 1-based week number and year, of that week number, of a date.
  925. * First day(s) of a year may be in the previous year's last week number.
  926. * This is an ISO-8601 implementation of computing week numbers ("week dates").
  927. *
  928. * @param date A Date_t value.
  929. * @return A number 1-53 representing the week number in a year,
  930. * and year 1600+ representing the year of that week number
  931. * (could be previous year from given date).
  932. */
  933. EXPORT ISOWeekNumWeekDayAndYearFromDate(Date_t d) := FUNCTION
  934. givenYear := Year(d);
  935. lastDayPreviousYear := DateFromParts(givenYear - 1, 12, 31);
  936. lastWeekPreviousYear := ISOWeeksFromDate(lastDayPreviousYear);
  937. lastDayGivenYear := DateFromParts(givenYear, 12, 31);
  938. lastWeekGivenYear := ISOWeeksFromDate(lastDayGivenYear);
  939. rawWeekNumber := ISORawWeekNumForDate(d);
  940. weekNumber := IF(rawWeekNumber < 1, lastWeekPreviousYear, IF(rawWeekNumber > lastWeekGivenYear, 1, rawWeekNumber));
  941. weekNumberYear := (givenYear + IF(rawWeekNumber < 1, -1, IF(rawWeekNumber > lastWeekGivenYear, 1, 0)));
  942. weekDay := ISODayOfWeekFromDate(d);
  943. result := MODULE
  944. EXPORT weekNumber := weekNumber;
  945. EXPORT year := weekNumberYear;
  946. EXPORT weekDay := weekDay;
  947. END;
  948. RETURN result;
  949. END;
  950. /**
  951. * Returns the ISO-8601 week date in extended (e.g. 2018-W23-7) or
  952. * compact (e.g. 2018W237) form.
  953. * This is an ISO-8601 implementation of computing week numbers ("week dates").
  954. *
  955. * @param date A Date_t value.
  956. * @return A number 1-53 representing the week number in a year,
  957. * and year 1600+ representing the year of that week number
  958. * (could be previous year from given date).
  959. */
  960. EXPORT ISOWeekDate(Date_t d, BOOLEAN extended = FALSE) := FUNCTION
  961. ISOWeekNumWeekDayAndYear := ISOWeekNumWeekDayAndYearFromDate(d);
  962. sep := IF(extended, '-', '');
  963. RETURN INTFORMAT(ISOWeekNumWeekDayAndYear.year, 4, 1) + sep + 'W' + INTFORMAT(ISOWeekNumWeekDayAndYear.weeknumber, 2, 1) + sep + ISOWeekNumWeekDayAndYear.weekday;
  964. END;
  965. /**
  966. * Returns the 1-based week number of a date within the date's year. Week 1
  967. * always contains the first day of the year, and week 2 begins on the
  968. * following day of the week indicated by the value of startingDayOfWeek. This
  969. * is not an ISO-8601 implementation of computing week numbers ("week dates").
  970. *
  971. * @param date The date for which to compute the week number
  972. * @param startingDayOfWeek The index number of the first day of a week, 1-7,
  973. * where 1 = Sunday; OPTIONAL, defaults to 1
  974. * @return The 1-based week number of date, relative to
  975. * the beginning of the date's year
  976. *
  977. * @see MonthWeekNumFromDate
  978. */
  979. EXPORT YearWeekNumFromDate(Date_t date, UNSIGNED1 startingDayOfWeek = 1) := FUNCTION
  980. yearStart := DateFromParts(Year(date), 1, 1);
  981. RETURN WeekNumForDate(date, yearStart, startingDayOfWeek);
  982. END;
  983. /**
  984. * Returns the 1-based week number of a date within the date's month. Week 1
  985. * always contains the first day of the month, and week 2 begins on the
  986. * following day of the week indicated by the value of startingDayOfWeek. This
  987. * is not an ISO-8601 implementation of computing week numbers ("week dates").
  988. *
  989. * @param date The date for which to compute the week number
  990. * @param startingDayOfWeek The index number of the first day of a week, 1-7,
  991. * where 1 = Sunday; OPTIONAL, defaults to 1
  992. * @return The 1-based week number of date, relative to
  993. * the beginning of the date's month
  994. *
  995. * @see YearWeekNumFromDate
  996. */
  997. EXPORT MonthWeekNumFromDate(Date_t date, UNSIGNED1 startingDayOfWeek = 1) := FUNCTION
  998. monthStart := DateFromParts(Year(date), Month(date), 1);
  999. RETURN WeekNumForDate(date, monthStart, startingDayOfWeek);
  1000. END;
  1001. /**
  1002. * Returns a boolean indicating whether daylight savings time is currently
  1003. * in effect locally.
  1004. *
  1005. * @return TRUE if daylight savings time is currently in effect, FALSE otherwise.
  1006. */
  1007. EXPORT BOOLEAN IsLocalDaylightSavingsInEffect() :=
  1008. TimeLib.IsLocalDaylightSavingsInEffect();
  1009. /**
  1010. * Returns the offset (in seconds) of the time represented from UTC, with
  1011. * positive values indicating locations east of the Prime Meridian. Given a
  1012. * UTC time in seconds since epoch, you can find the local time by adding the
  1013. * result of this function to the seconds. Note that daylight savings time is
  1014. * factored into the offset.
  1015. *
  1016. * @return The number of seconds offset from UTC.
  1017. */
  1018. EXPORT INTEGER4 LocalTimeZoneOffset() :=
  1019. TimeLib.LocalTimeZoneOffset();
  1020. /**
  1021. * Returns the current date.
  1022. *
  1023. * @param in_local_time TRUE if the returned value should be local to the
  1024. * cluster computing the date, FALSE for UTC.
  1025. * Optional, defaults to FALSE.
  1026. * @return A Date_t representing the current date.
  1027. */
  1028. EXPORT Date_t CurrentDate(BOOLEAN in_local_time = FALSE) :=
  1029. TimeLib.CurrentDate(in_local_time);
  1030. /**
  1031. * Returns the current date in the local time zone.
  1032. *
  1033. * @return A Date_t representing the current date.
  1034. */
  1035. EXPORT Date_t Today() := CurrentDate(TRUE);
  1036. /**
  1037. * Returns the current time of day
  1038. *
  1039. * @param in_local_time TRUE if the returned value should be local to the
  1040. * cluster computing the time, FALSE for UTC.
  1041. * Optional, defaults to FALSE.
  1042. * @return A Time_t representing the current time of day.
  1043. */
  1044. EXPORT Time_t CurrentTime(BOOLEAN in_local_time = FALSE) :=
  1045. TimeLib.CurrentTime(in_local_time);
  1046. /**
  1047. * Returns the current date and time as the number of seconds since epoch.
  1048. *
  1049. * @param in_local_time TRUE if the returned value should be local to the
  1050. * cluster computing the time, FALSE for UTC.
  1051. * Optional, defaults to FALSE.
  1052. * @return A Seconds_t representing the current time in
  1053. * UTC or local time, depending on the argument.
  1054. */
  1055. EXPORT Seconds_t CurrentSeconds(BOOLEAN in_local_time = FALSE) :=
  1056. TimeLib.CurrentSeconds(in_local_time);
  1057. /**
  1058. * Returns the current date and time as the number of microseconds since epoch.
  1059. *
  1060. * @param in_local_time TRUE if the returned value should be local to the
  1061. * cluster computing the time, FALSE for UTC.
  1062. * Optional, defaults to FALSE.
  1063. * @return A Timestamp_t representing the current time in
  1064. * microseconds in UTC or local time, depending on
  1065. * the argument.
  1066. */
  1067. EXPORT Timestamp_t CurrentTimestamp(BOOLEAN in_local_time = FALSE) :=
  1068. TimeLib.CurrentTimestamp(in_local_time);
  1069. /**
  1070. * Returns the beginning and ending dates for the month surrounding the given date.
  1071. *
  1072. * @param as_of_date The reference date from which the month will be
  1073. * calculated. This date must be a date within the
  1074. * Gregorian calendar. Optional, defaults to the
  1075. * current date in UTC.
  1076. * @return Module with exported attributes for startDate and endDate.
  1077. */
  1078. EXPORT DatesForMonth(Date_t as_of_date = CurrentDate(FALSE)) := FUNCTION
  1079. lastDay := TimeLib.GetLastDayOfMonth(as_of_date);
  1080. firstDay := (lastDay DIV 100) * 100 + 1;
  1081. result := MODULE
  1082. EXPORT Date_t startDate := firstDay;
  1083. EXPORT Date_t endDate := lastDay;
  1084. END;
  1085. RETURN result;
  1086. END;
  1087. /**
  1088. * Returns the beginning and ending dates for the week surrounding the given date
  1089. * (Sunday marks the beginning of a week).
  1090. *
  1091. * @param as_of_date The reference date from which the week will be
  1092. * calculated. This date must be a date within the
  1093. * Gregorian calendar. Optional, defaults to the
  1094. * current date in UTC.
  1095. * @return Module with exported attributes for startDate and endDate.
  1096. */
  1097. EXPORT DatesForWeek(Date_t as_of_date = CurrentDate(FALSE)) := FUNCTION
  1098. lastWeekDates := ROW(TimeLib.DatesForWeek(as_of_date));
  1099. result := MODULE
  1100. EXPORT Date_t startDate := lastWeekDates.startDate;
  1101. EXPORT Date_t endDate := lastWeekDates.endDate;
  1102. END;
  1103. RETURN result;
  1104. END;
  1105. /**
  1106. * Tests whether a date is valid, both by range-checking the year and by
  1107. * validating each of the other individual components.
  1108. *
  1109. * @param date The date to validate.
  1110. * @param yearLowerBound The minimum acceptable year.
  1111. * Optional; defaults to 1800.
  1112. * @param yearUpperBound The maximum acceptable year.
  1113. * Optional; defaults to 2100.
  1114. * @return TRUE if the date is valid, FALSE otherwise.
  1115. */
  1116. EXPORT BOOLEAN IsValidDate(Date_t date,
  1117. INTEGER2 yearLowerBound = 1800,
  1118. INTEGER2 yearUpperBound = 2100) := FUNCTION
  1119. yearInBounds := (Year(date) BETWEEN yearLowerBound AND yearUpperBound);
  1120. monthInBounds := (Month(date) BETWEEN 1 AND 12);
  1121. maxDayInMonth := CHOOSE(Month(date),31,IF(IsLeapYear(Year(date)),29,28),31,30,31,30,31,31,30,31,30,31);
  1122. dayInBounds := (Day(date) BETWEEN 1 AND maxDayInMonth);
  1123. RETURN yearInBounds AND monthInBounds AND dayInBounds;
  1124. END;
  1125. /**
  1126. * Tests whether a date is valid in the Gregorian calendar. The year
  1127. * must be between 1601 and 30827.
  1128. *
  1129. * @param date The Date_t to validate.
  1130. * @return TRUE if the date is valid, FALSE otherwise.
  1131. */
  1132. EXPORT BOOLEAN IsValidGregorianDate(Date_t date) := FUNCTION
  1133. yearInBounds := (Year(date) BETWEEN 1601 AND 30827);
  1134. matchesNormalized := (date = AdjustDate(date)); // AdjustDate normalizes, so this is a validation check
  1135. RETURN yearInBounds AND matchesNormalized;
  1136. END;
  1137. /**
  1138. * Tests whether a time is valid.
  1139. *
  1140. * @param time The time to validate.
  1141. * @return TRUE if the time is valid, FALSE otherwise.
  1142. */
  1143. EXPORT BOOLEAN IsValidTime(Time_t time) := FUNCTION
  1144. hourInBounds := (Hour(time) <= 23);
  1145. minuteInBounds := (Minute(time) <= 59);
  1146. secondInBounds := (Second(time) <= 59);
  1147. RETURN hourInBounds AND minuteInBounds AND secondInBounds;
  1148. END;
  1149. //------------------------------------------------------------------------------
  1150. // Transforms
  1151. //------------------------------------------------------------------------------
  1152. /**
  1153. * A transform to create a Date_rec from the individual elements
  1154. *
  1155. * @param year The year
  1156. * @param month The month (1-12).
  1157. * @param day The day (1..daysInMonth).
  1158. * @return A transform that creates a Date_rec containing the date.
  1159. */
  1160. EXPORT Date_rec CreateDate(INTEGER2 year, UNSIGNED1 month, UNSIGNED1 day) := TRANSFORM
  1161. SELF.year := year;
  1162. SELF.month := month;
  1163. SELF.day := day;
  1164. END;
  1165. /**
  1166. * A transform to create a Date_rec from a Seconds_t value.
  1167. *
  1168. * @param seconds The number seconds since epoch.
  1169. * @param is_local_time TRUE if seconds is expressed in local time
  1170. * rather than UTC, FALSE if seconds is expressed
  1171. * in UTC. Optional, defaults to FALSE.
  1172. * @return A transform that creates a Date_rec containing
  1173. * the date.
  1174. */
  1175. EXPORT Date_rec CreateDateFromSeconds(Seconds_t seconds, BOOLEAN is_local_time = FALSE) := TRANSFORM
  1176. timeParts := SecondsToParts(seconds, is_local_time);
  1177. SELF.year := timeParts.year;
  1178. SELF.month := timeParts.month;
  1179. SELF.day := timeParts.day;
  1180. END;
  1181. /**
  1182. * A transform to create a Time_rec from the individual elements
  1183. *
  1184. * @param hour The hour (0-23).
  1185. * @param minute The minute (0-59).
  1186. * @param second The second (0-59).
  1187. * @return A transform that creates a Time_rec containing the time of day.
  1188. */
  1189. EXPORT Time_rec CreateTime(UNSIGNED1 hour, UNSIGNED1 minute, UNSIGNED1 second) := TRANSFORM
  1190. SELF.hour := hour;
  1191. SELF.minute := minute;
  1192. SELF.second := second;
  1193. END;
  1194. /**
  1195. * A transform to create a Time_rec from a Seconds_t value.
  1196. *
  1197. * @param seconds The number seconds since epoch.
  1198. * @param is_local_time TRUE if seconds is expressed in local time
  1199. * rather than UTC, FALSE if seconds is expressed
  1200. * in UTC. Optional, defaults to FALSE.
  1201. * @return A transform that creates a Time_rec containing
  1202. * the time of day.
  1203. */
  1204. EXPORT Time_rec CreateTimeFromSeconds(Seconds_t seconds, BOOLEAN is_local_time = FALSE) := TRANSFORM
  1205. timeParts := SecondsToParts(seconds, is_local_time);
  1206. SELF.hour := timeParts.hour;
  1207. SELF.minute := timeParts.minute;
  1208. SELF.second := timeParts.second;
  1209. END;
  1210. /**
  1211. * A transform to create a DateTime_rec from the individual elements
  1212. *
  1213. * @param year The year
  1214. * @param month The month (1-12).
  1215. * @param day The day (1..daysInMonth).
  1216. * @param hour The hour (0-23).
  1217. * @param minute The minute (0-59).
  1218. * @param second The second (0-59).
  1219. * @return A transform that creates a DateTime_rec containing date
  1220. * and time components.
  1221. */
  1222. EXPORT DateTime_rec CreateDateTime(INTEGER2 year,
  1223. UNSIGNED1 month,
  1224. UNSIGNED1 day,
  1225. UNSIGNED1 hour,
  1226. UNSIGNED1 minute,
  1227. UNSIGNED1 second) := TRANSFORM
  1228. SELF.year := year;
  1229. SELF.month := month;
  1230. SELF.day := day;
  1231. SELF.hour := hour;
  1232. SELF.minute := minute;
  1233. SELF.second := second;
  1234. END;
  1235. /**
  1236. * A transform to create a DateTime_rec from a Seconds_t value.
  1237. *
  1238. * @param seconds The number seconds since epoch.
  1239. * @param is_local_time TRUE if seconds is expressed in local time
  1240. * rather than UTC, FALSE if seconds is expressed
  1241. * in UTC. Optional, defaults to FALSE.
  1242. * @return A transform that creates a DateTime_rec
  1243. * containing date and time components.
  1244. */
  1245. EXPORT DateTime_rec CreateDateTimeFromSeconds(Seconds_t seconds, BOOLEAN is_local_time = FALSE) := TRANSFORM
  1246. timeParts := SecondsToParts(seconds, is_local_time);
  1247. SELF.year := timeParts.year;
  1248. SELF.month := timeParts.month;
  1249. SELF.day := timeParts.day;
  1250. SELF.hour := timeParts.hour;
  1251. SELF.minute := timeParts.minute;
  1252. SELF.second := timeParts.second;
  1253. END;
  1254. //------------------------------------------------------------------------------
  1255. // Time Zone Module
  1256. //------------------------------------------------------------------------------
  1257. EXPORT TimeZone := MODULE, FORWARD
  1258. /**
  1259. * Record definition for exported time zone information
  1260. */
  1261. EXPORT TZDataLayout := RECORD
  1262. STRING5 tzAbbrev; // Time zone abbreviation; always uppercase; may be duplicated between records
  1263. INTEGER4 secondsOffset; // Number of seconds east (positive) or west (negative) of UTC
  1264. SET OF STRING15 locations; // Names of locations that use the given time zone abbreviation
  1265. END;
  1266. /**
  1267. * Hardcoded time zone definitions; a general description of each time zone
  1268. * is included as a line comment. This information was collected from
  1269. * https://www.timeanddate.com/time/zones/ with one modification (see below).
  1270. *
  1271. * The IST abbreviation can indicate three different time zones:
  1272. * India Standard Time
  1273. * Irish Standard Time
  1274. * Israel Standard Time
  1275. *
  1276. * Unfortunately, two of those IST time zones lie in the same location: ASIA.
  1277. * That makes it impossible to differentiate between them, and they have very
  1278. * different offsets. As a consequence, locations for Israel Standard Time and
  1279. * Israel Daylight Time have been changed from ASIA to ISRAEL.
  1280. */
  1281. EXPORT TZ_DATA := DATASET
  1282. (
  1283. [
  1284. {'A', 3600, ['MILITARY']}, // Alpha Time Zone
  1285. {'ACDT', 37800, ['AUSTRALIA']}, // Australian Central Daylight Time
  1286. {'ACST', 34200, ['AUSTRALIA']}, // Australian Central Standard Time
  1287. {'ACT', -18000, ['SOUTH AMERICA']}, // Acre Time
  1288. {'ACT', 34200, ['AUSTRALIA']}, // Australian Central Time
  1289. {'ACWST', 31500, ['AUSTRALIA']}, // Australian Central Western Standard Time
  1290. {'ADT', 10800, ['ASIA']}, // Arabia Daylight Time
  1291. {'ADT', -10800, ['NORTH AMERICA', 'ATLANTIC']}, // Atlantic Daylight Time
  1292. {'AEDT', 39600, ['AUSTRALIA']}, // Australian Eastern Daylight Time
  1293. {'AEST', 36000, ['AUSTRALIA']}, // Australian Eastern Standard Time
  1294. {'AET', 36000, ['AUSTRALIA']}, // Australian Eastern Time
  1295. {'AFT', 16200, ['ASIA']}, // Afghanistan Time
  1296. {'AKDT', -28800, ['NORTH AMERICA']}, // Alaska Daylight Time
  1297. {'AKST', -32400, ['NORTH AMERICA']}, // Alaska Standard Time
  1298. {'ALMT', 21600, ['ASIA']}, // Alma-Ata Time
  1299. {'AMST', -10800, ['SOUTH AMERICA']}, // Amazon Summer Time
  1300. {'AMST', 18000, ['ASIA']}, // Armenia Summer Time
  1301. {'AMT', -14400, ['SOUTH AMERICA']}, // Amazon Time
  1302. {'AMT', 14400, ['ASIA']}, // Armenia Time
  1303. {'ANAST', 43200, ['ASIA']}, // Anadyr Summer Time
  1304. {'ANAT', 43200, ['ASIA']}, // Anadyr Time
  1305. {'AOE', -43200, ['PACIFIC']}, // Anywhere on Earth
  1306. {'AQTT', 18000, ['ASIA']}, // Aqtobe Time
  1307. {'ART', -10800, ['ANTARCTICA', 'SOUTH AMERICA']}, // Argentina Time
  1308. {'AST', 7200, ['ASIA']}, // Arabia Standard Time
  1309. {'AST', -14400, ['NORTH AMERICA', 'ATLANTIC' , 'CARIBBEAN']}, // Atlantic Standard Time
  1310. {'AT', -14400, ['NORTH AMERICA', 'ATLANTIC', 'CARIBBEAN']}, // Atlantic Time
  1311. {'AWDT', 32400, ['AUSTRALIA']}, // Australian Western Daylight Time
  1312. {'AWST', 28800, ['AUSTRALIA']}, // Australian Western Standard Time
  1313. {'AZOST', 0, ['ATLANTIC']}, // Azores Summer Time
  1314. {'AZOT', -3600, ['ATLANTIC']}, // Azores Time
  1315. {'AZST', 18000, ['ASIA']}, // Azerbaijan Summer Time
  1316. {'AZT', 14400, ['ASIA']}, // Azerbaijan Time
  1317. {'B', 7200, ['MILITARY']}, // Bravo Time Zone
  1318. {'BNT', 28800, ['ASIA']}, // Brunei Darussalam Time
  1319. {'BOT', -14400, ['SOUTH AMERICA']}, // Bolivia Time
  1320. {'BRST', -7200, ['SOUTH AMERICA']}, // Brazil Summer Time
  1321. {'BRT', -10800, ['SOUTH AMERICA']}, // Brazil Time
  1322. {'BST', 21600, ['ASIA']}, // Bangladesh Standard Time
  1323. {'BST', 39600, ['PACIFIC']}, // Bougainville Standard Time
  1324. {'BST', 3600, ['EUROPE']}, // British Summer Time
  1325. {'BTT', 21600, ['ASIA']}, // Bhutan Time
  1326. {'C', 10800, ['MILITARY']}, // Charlie Time Zone
  1327. {'CAST', 28800, ['ANTARCTICA']}, // Casey Time
  1328. {'CAT', 7200, ['AFRICA']}, // Central Africa Time
  1329. {'CCT', 23400, ['INDIAN OCEAN']}, // Cocos Islands Time
  1330. {'CDT', -18000, ['NORTH AMERICA']}, // Central Daylight Time
  1331. {'CDT', -14400, ['CARIBBEAN']}, // Cuba Daylight Time
  1332. {'CEST', 7200, ['EUROPE', 'ANTARCTICA']}, // Central European Summer Time
  1333. {'CET', 3600, ['EUROPE', 'AFRICA']}, // Central European Time
  1334. {'CHADT', 49500, ['PACIFIC']}, // Chatham Island Daylight Time
  1335. {'CHAST', 45900, ['PACIFIC']}, // Chatham Island Standard Time
  1336. {'CHOST', 32400, ['ASIA']}, // Choibalsan Summer Time
  1337. {'CHOT', 28800, ['ASIA']}, // Choibalsan Time
  1338. {'ChST', 36000, ['PACIFIC']}, // Chamorro Standard Time
  1339. {'CHUT', 36000, ['PACIFIC']}, // Chuuk Time
  1340. {'CIDST', -14400, ['CARIBBEAN']}, // Cayman Islands Daylight Saving Time
  1341. {'CIST', -18000, ['CARIBBEAN']}, // Cayman Islands Standard Time
  1342. {'CKT', -36000, ['PACIFIC']}, // Cook Island Time
  1343. {'CLST', -10800, ['SOUTH AMERICA', 'ANTARCTICA']}, // Chile Summer Time
  1344. {'CLT', -14400, ['SOUTH AMERICA', 'ANTARCTICA']}, // Chile Standard Time
  1345. {'COT', -18000, ['SOUTH AMERICA']}, // Colombia Time
  1346. {'CST', -21600, ['NORTH AMERICA', 'CENTRAL AMERICA']}, // Central Standard Time
  1347. {'CST', 28800, ['ASIA']}, // China Standard Time
  1348. {'CST', -18000, ['CARIBBEAN']}, // Cuba Standard Time
  1349. {'CT', -21600, ['NORTH AMERICA', 'CENTRAL AMERICA']}, // Central Time
  1350. {'CVT', -3600, ['AFRICA']}, // Cape Verde Time
  1351. {'CXT', 25200, ['AUSTRALIA']}, // Christmas Island Time
  1352. {'D', 14400, ['MILITARY']}, // Delta Time Zone
  1353. {'DAVT', 25200, ['ANTARCTICA']}, // Davis Time
  1354. {'DDUT', 36000, ['ANTARCTICA']}, // Dumont-d'Urville Time
  1355. {'E', 18000, ['MILITARY']}, // Echo Time Zone
  1356. {'EASST', -18000, ['PACIFIC']}, // Easter Island Summer Time
  1357. {'EAST', -21600, ['PACIFIC']}, // Easter Island Standard Time
  1358. {'EAT', 10800, ['AFRICA', 'INDIAN OCEAN']}, // Eastern Africa Time
  1359. {'ECT', -18000, ['SOUTH AMERICA']}, // Ecuador Time
  1360. {'EDT', -14400, ['NORTH AMERICA', 'CARIBBEAN']}, // Eastern Daylight Time
  1361. {'EEST', 10800, ['EUROPE', 'ASIA']}, // Eastern European Summer Time
  1362. {'EET', 7200, ['EUROPE', 'ASIA', 'AFRICA']}, // Eastern European Time
  1363. {'EGST', 0, ['NORTH AMERICA']}, // Eastern Greenland Summer Time
  1364. {'EGT', -3600, ['NORTH AMERICA']}, // East Greenland Time
  1365. {'EST', -18000, ['NORTH AMERICA', 'CARIBBEAN', 'CENTRAL AMERICA']}, // Eastern Standard Time
  1366. {'ET', -18000, ['NORTH AMERICA', 'CARIBBEAN', 'CENTRAL AMERICA']}, // Eastern Time
  1367. {'F', 21600, ['MILITARY']}, // Foxtrot Time Zone
  1368. {'FET', 10800, ['EUROPE']}, // Further-Eastern European Time
  1369. {'FJST', 46800, ['PACIFIC']}, // Fiji Summer Time
  1370. {'FJT', 43200, ['PACIFIC']}, // Fiji Time
  1371. {'FKST', -10800, ['SOUTH AMERICA']}, // Falkland Islands Summer Time
  1372. {'FKT', -14400, ['SOUTH AMERICA']}, // Falkland Island Time
  1373. {'FNT', -7200, ['SOUTH AMERICA']}, // Fernando de Noronha Time
  1374. {'G', 25200, ['MILITARY']}, // Golf Time Zone
  1375. {'GALT', -21600, ['PACIFIC']}, // Galapagos Time
  1376. {'GAMT', -32400, ['PACIFIC']}, // Gambier Time
  1377. {'GET', 14400, ['ASIA']}, // Georgia Standard Time
  1378. {'GFT', -10800, ['SOUTH AMERICA']}, // French Guiana Time
  1379. {'GILT', 43200, ['PACIFIC']}, // Gilbert Island Time
  1380. {'GMT', 0, ['EUROPE', 'AFRICA', 'NORTH AMERICA', 'ANTARCTICA']}, // Greenwich Mean Time
  1381. {'GST', 14400, ['ASIA']}, // Gulf Standard Time
  1382. {'GST', -7200, ['SOUTH AMERICA']}, // South Georgia Time
  1383. {'GYT', -14400, ['SOUTH AMERICA']}, // Guyana Time
  1384. {'H', 28800, ['MILITARY']}, // Hotel Time Zone
  1385. {'HADT', -32400, ['NORTH AMERICA']}, // Hawaii-Aleutian Daylight Time
  1386. {'HAST', -36000, ['NORTH AMERICA', 'PACIFIC']}, // Hawaii-Aleutian Standard Time
  1387. {'HKT', 28800, ['ASIA']}, // Hong Kong Time
  1388. {'HOVST', 28800, ['ASIA']}, // Hovd Summer Time
  1389. {'HOVT', 25200, ['ASIA']}, // Hovd Time
  1390. {'I', 32400, ['MILITARY']}, // India Time Zone
  1391. {'ICT', 25200, ['ASIA']}, // Indochina Time
  1392. {'IDT', 10800, ['ISRAEL']}, // Israel Daylight Time; location was ASIA
  1393. {'IOT', 21600, ['INDIAN OCEAN']}, // Indian Chagos Time
  1394. {'IRDT', 16200, ['ASIA']}, // Iran Daylight Time
  1395. {'IRKST', 32400, ['ASIA']}, // Irkutsk Summer Time
  1396. {'IRKT', 28800, ['ASIA']}, // Irkutsk Time
  1397. {'IRST', 12600, ['ASIA']}, // Iran Standard Time
  1398. {'IST', 19800, ['ASIA']}, // India Standard Time
  1399. {'IST', 3600, ['EUROPE']}, // Irish Standard Time
  1400. {'IST', 7200, ['ISRAEL']}, // Israel Standard Time; location was ASIA
  1401. {'JST', 32400, ['ASIA']}, // Japan Standard Time
  1402. {'K', 36000, ['MILITARY']}, // Kilo Time Zone
  1403. {'KGT', 21600, ['ASIA']}, // Kyrgyzstan Time
  1404. {'KOST', 39600, ['PACIFIC']}, // Kosrae Time
  1405. {'KRAST', 28800, ['ASIA']}, // Krasnoyarsk Summer Time
  1406. {'KRAT', 25200, ['ASIA']}, // Krasnoyarsk Time
  1407. {'KST', 32400, ['ASIA']}, // Korea Standard Time
  1408. {'KUYT', 14400, ['EUROPE']}, // Kuybyshev Time
  1409. {'L', 39600, ['MILITARY']}, // Lima Time Zone
  1410. {'LHDT', 39600, ['AUSTRALIA']}, // Lord Howe Daylight Time
  1411. {'LHST', 37800, ['AUSTRALIA']}, // Lord Howe Standard Time
  1412. {'LINT', 50400, ['PACIFIC']}, // Line Islands Time
  1413. {'M', 43200, ['MILITARY']}, // Mike Time Zone
  1414. {'MAGST', 43200, ['ASIA']}, // Magadan Summer Time
  1415. {'MAGT', 39600, ['ASIA']}, // Magadan Time
  1416. {'MART', -34200, ['PACIFIC']}, // Marquesas Time
  1417. {'MAWT', 18000, ['ANTARCTICA']}, // Mawson Time
  1418. {'MDT', -21600, ['NORTH AMERICA']}, // Mountain Daylight Time
  1419. {'MHT', 43200, ['PACIFIC']}, // Marshall Islands Time
  1420. {'MMT', 23400, ['ASIA']}, // Myanmar Time
  1421. {'MSD', 14400, ['EUROPE']}, // Moscow Daylight Time
  1422. {'MSK', 10800, ['EUROPE', 'ASIA']}, // Moscow Standard Time
  1423. {'MST', -25200, ['NORTH AMERICA']}, // Mountain Standard Time
  1424. {'MT', -25200, ['NORTH AMERICA']}, // Mountain Time
  1425. {'MUT', 14400, ['AFRICA']}, // Mauritius Time
  1426. {'MVT', 18000, ['ASIA']}, // Maldives Time
  1427. {'MYT', 28800, ['ASIA']}, // Malaysia Time
  1428. {'N', -3600, ['MILITARY']}, // November Time Zone
  1429. {'NCT', 39600, ['PACIFIC']}, // New Caledonia Time
  1430. {'NDT', -9000, ['NORTH AMERICA']}, // Newfoundland Daylight Time
  1431. {'NFT', 39600, ['AUSTRALIA']}, // Norfolk Time
  1432. {'NOVST', 25200, ['ASIA']}, // Novosibirsk Summer Time
  1433. {'NOVT', 21600, ['ASIA']}, // Novosibirsk Time
  1434. {'NPT', 20700, ['ASIA']}, // Nepal Time
  1435. {'NRT', 43200, ['PACIFIC']}, // Nauru Time
  1436. {'NST', -12600, ['NORTH AMERICA']}, // Newfoundland Standard Time
  1437. {'NUT', -39600, ['PACIFIC']}, // Niue Time
  1438. {'NZDT', 46800, ['PACIFIC', 'ANTARCTICA']}, // New Zealand Daylight Time
  1439. {'NZST', 43200, ['PACIFIC', 'ANTARCTICA']}, // New Zealand Standard Time
  1440. {'O', -7200, ['MILITARY']}, // Oscar Time Zone
  1441. {'OMSST', 25200, ['ASIA']}, // Omsk Summer Time
  1442. {'OMST', 21600, ['ASIA']}, // Omsk Standard Time
  1443. {'ORAT', 18000, ['ASIA']}, // Oral Time
  1444. {'P', -10800, ['MILITARY']}, // Papa Time Zone
  1445. {'PDT', -25200, ['NORTH AMERICA']}, // Pacific Daylight Time
  1446. {'PET', -18000, ['SOUTH AMERICA']}, // Peru Time
  1447. {'PETST', 43200, ['ASIA']}, // Kamchatka Summer Time
  1448. {'PETT', 43200, ['ASIA']}, // Kamchatka Time
  1449. {'PGT', 36000, ['PACIFIC']}, // Papua New Guinea Time
  1450. {'PHOT', 46800, ['PACIFIC']}, // Phoenix Island Time
  1451. {'PHT', 28800, ['ASIA']}, // Philippine Time
  1452. {'PKT', 18000, ['ASIA']}, // Pakistan Standard Time
  1453. {'PMDT', -7200, ['NORTH AMERICA']}, // Pierre & Miquelon Daylight Time
  1454. {'PMST', -10800, ['NORTH AMERICA']}, // Pierre & Miquelon Standard Time
  1455. {'PONT', 39600, ['PACIFIC']}, // Pohnpei Standard Time
  1456. {'PST', -28800, ['NORTH AMERICA']}, // Pacific Standard Time
  1457. {'PST', -28800, ['PACIFIC']}, // Pitcairn Standard Time
  1458. {'PT', -28800, ['NORTH AMERICA']}, // Pacific Time
  1459. {'PWT', 32400, ['PACIFIC']}, // Palau Time
  1460. {'PYST', -10800, ['SOUTH AMERICA']}, // Paraguay Summer Time
  1461. {'PYT', -14400, ['SOUTH AMERICA']}, // Paraguay Time
  1462. {'PYT', 30600, ['ASIA']}, // Pyongyang Time
  1463. {'Q', -14400, ['MILITARY']}, // Quebec Time Zone
  1464. {'QYZT', 21600, ['ASIA']}, // Qyzylorda Time
  1465. {'R', -18000, ['MILITARY']}, // Romeo Time Zone
  1466. {'RET', 14400, ['AFRICA']}, // Reunion Time
  1467. {'ROTT', -10800, ['ANTARCTICA']}, // Rothera Time
  1468. {'S', -21600, ['MILITARY']}, // Sierra Time Zone
  1469. {'SAKT', 39600, ['ASIA']}, // Sakhalin Time
  1470. {'SAMT', 14400, ['EUROPE']}, // Samara Time
  1471. {'SAST', 7200, ['AFRICA']}, // South Africa Standard Time
  1472. {'SBT', 39600, ['PACIFIC']}, // Solomon Islands Time
  1473. {'SCT', 14400, ['AFRICA']}, // Seychelles Time
  1474. {'SGT', 28800, ['ASIA']}, // Singapore Time
  1475. {'SRET', 39600, ['ASIA']}, // Srednekolymsk Time
  1476. {'SRT', -10800, ['SOUTH AMERICA']}, // Suriname Time
  1477. {'SST', -39600, ['PACIFIC']}, // Samoa Standard Time
  1478. {'SYOT', 10800, ['ANTARCTICA']}, // Syowa Time
  1479. {'T', -25200, ['MILITARY']}, // Tango Time Zone
  1480. {'TAHT', -36000, ['PACIFIC']}, // Tahiti Time
  1481. {'TFT', 18000, ['INDIAN OCEAN']}, // French Southern and Antarctic Time
  1482. {'TJT', 18000, ['ASIA']}, // Tajikistan Time
  1483. {'TKT', 46800, ['PACIFIC']}, // Tokelau Time
  1484. {'TLT', 32400, ['ASIA']}, // East Timor Time
  1485. {'TMT', 18000, ['ASIA']}, // Turkmenistan Time
  1486. {'TOST', 50400, ['PACIFIC']}, // Tonga Summer Time
  1487. {'TOT', 46800, ['PACIFIC']}, // Tonga Time
  1488. {'TRT', 10800, ['ASIA', 'EUROPE']}, // Turkey Time
  1489. {'TVT', 43200, ['PACIFIC']}, // Tuvalu Time
  1490. {'U', -28800, ['MILITARY']}, // Uniform Time Zone
  1491. {'ULAST', 32400, ['ASIA']}, // Ulaanbaatar Summer Time
  1492. {'ULAT', 28800, ['ASIA']}, // Ulaanbaatar Time
  1493. {'UTC', 0, ['WORLDWIDE']}, // Coordinated Universal Time
  1494. {'UYST', -7200, ['SOUTH AMERICA']}, // Uruguay Summer Time
  1495. {'UYT', -10800, ['SOUTH AMERICA']}, // Uruguay Time
  1496. {'UZT', 18000, ['ASIA']}, // Uzbekistan Time
  1497. {'V', -32400, ['MILITARY']}, // Victor Time Zone
  1498. {'VET', -14400, ['SOUTH AMERICA']}, // Venezuelan Standard Time
  1499. {'VLAST', 39600, ['ASIA']}, // Vladivostok Summer Time
  1500. {'VLAT', 36000, ['ASIA']}, // Vladivostok Time
  1501. {'VOST', 21600, ['ANTARCTICA']}, // Vostok Time
  1502. {'VUT', 39600, ['PACIFIC']}, // Vanuatu Time
  1503. {'W', -36000, ['MILITARY']}, // Whiskey Time Zone
  1504. {'WAKT', 43200, ['PACIFIC']}, // Wake Time
  1505. {'WARST', -10800, ['SOUTH AMERICA']}, // Western Argentine Summer Time
  1506. {'WAST', 7200, ['AFRICA']}, // West Africa Summer Time
  1507. {'WAT', 3600, ['AFRICA']}, // West Africa Time
  1508. {'WEST', 3600, ['EUROPE', 'AFRICA']}, // Western European Summer Time
  1509. {'WET', 0, ['EUROPE', 'AFRICA']}, // Western European Time
  1510. {'WFT', 43200, ['PACIFIC']}, // Wallis and Futuna Time
  1511. {'WGST', -7200, ['NORTH AMERICA']}, // Western Greenland Summer Time
  1512. {'WGT', -10800, ['NORTH AMERICA']}, // West Greenland Time
  1513. {'WIB', 25200, ['ASIA']}, // Western Indonesian Time
  1514. {'WIT', 32400, ['ASIA']}, // Eastern Indonesian Time
  1515. {'WITA', 28800, ['ASIA']}, // Central Indonesian Time
  1516. {'WST', 50400, ['PACIFIC']}, // West Samoa Time
  1517. {'WST', 3600, ['AFRICA']}, // Western Sahara Summer Time
  1518. {'WT', 0, ['AFRICA']}, // Western Sahara Standard Time
  1519. {'X', -39600, ['MILITARY']}, // X-ray Time Zone
  1520. {'Y', -43200, ['MILITARY']}, // Yankee Time Zone
  1521. {'YAKST', 36000, ['ASIA']}, // Yakutsk Summer Time
  1522. {'YAKT', 32400, ['ASIA']}, // Yakutsk Time
  1523. {'YAPT', 36000, ['PACIFIC']}, // Yap Time
  1524. {'YEKST', 21600, ['ASIA']}, // Yekaterinburg Summer Time
  1525. {'YEKT', 18000, ['ASIA']}, // Yekaterinburg Time
  1526. {'Z', 0, ['MILITARY']} // Zulu Time Zone
  1527. ],
  1528. TZDataLayout
  1529. );
  1530. /**
  1531. * Return a list of unique time zone abbreviations from the hardcoded dataset.
  1532. * All abbreviations are in uppercase.
  1533. *
  1534. * @return A new DATASET({STRING5 tzAbbrev}) containing the
  1535. * unique time zone abbreviations.
  1536. */
  1537. EXPORT UniqueTZAbbreviations() := FUNCTION
  1538. RETURN TABLE(TZ_DATA, {tzAbbrev}, tzAbbrev);
  1539. END;
  1540. /**
  1541. * Return a list of unique location names from the hardcoded dataset.
  1542. * All names are in uppercase.
  1543. *
  1544. * @return A new DATASET({STRING name}) containing the
  1545. * unique location names.
  1546. */
  1547. EXPORT UniqueTZLocations() := FUNCTION
  1548. NameRec := {STRING name};
  1549. // Gather all locations as a collection of child datasets
  1550. collectedNames := PROJECT
  1551. (
  1552. TZ_DATA,
  1553. TRANSFORM
  1554. (
  1555. {
  1556. DATASET(NameRec) names
  1557. },
  1558. SELF.names := DATASET(LEFT.locations, NameRec)
  1559. )
  1560. );
  1561. // Flatten collected names, so there is one name per record
  1562. flattenedNames := NORMALIZE
  1563. (
  1564. collectedNames,
  1565. LEFT.names,
  1566. TRANSFORM
  1567. (
  1568. NameRec,
  1569. SELF.name := RIGHT.name
  1570. )
  1571. );
  1572. // Deduplicate the names
  1573. ds3 := TABLE(flattenedNames, {name}, name);
  1574. RETURN ds3;
  1575. END;
  1576. /**
  1577. * Finds the time zone records for a given location.
  1578. *
  1579. * @param location The name of the location to search for; must be a
  1580. * non-empty uppercase string; REQUIRED
  1581. * @return A new DATASET(STRING5 tzAbbrev, INTEGER4 secondsOffset)
  1582. * containing the found records
  1583. * @see FindTZData
  1584. */
  1585. EXPORT TZDataForLocation(STRING location) := FUNCTION
  1586. ResultRec := RECORD
  1587. STRING5 tzAbbrev;
  1588. INTEGER4 secondsOffset;
  1589. END;
  1590. foundRecs := TZ_DATA(location IN locations);
  1591. foundTrimmed := PROJECT
  1592. (
  1593. foundRecs,
  1594. TRANSFORM
  1595. (
  1596. ResultRec,
  1597. SELF := LEFT
  1598. )
  1599. );
  1600. RETURN foundTrimmed;
  1601. END;
  1602. /**
  1603. * Finds the time zone records for a given abbreviation and optional location.
  1604. * A location should be provided as a method of differentiation if the
  1605. * abbreviation has duplicate entries.
  1606. *
  1607. * @param timeZoneAbbrev The time zone abbreviation to search for;
  1608. * must be a non-empty uppercase string; REQUIRED
  1609. * @param location The name of the location to search for; if a
  1610. * location is not provided or is an empty string,
  1611. * all records matching only the abbreviation are
  1612. * returned; OPTIONAL, defaults to an empty string
  1613. * @return A new DATASET(TZDataLayout) containing the found
  1614. * records
  1615. * @see TZDataForLocation
  1616. */
  1617. EXPORT DATASET(TZDataLayout) FindTZData(STRING5 timeZoneAbbrev, STRING location = '') := FUNCTION
  1618. RETURN TZ_DATA(tzAbbrev = timeZoneAbbrev AND (location = '' OR location IN locations));
  1619. END;
  1620. /**
  1621. * Compute the offset, in seconds, between two different time zones. Each
  1622. * time zone is designated by a required time zone abbreviation and an
  1623. * optional location name. The result is the number of seconds (which can be
  1624. * either positive or negative) that would have to be applied to a time when
  1625. * traveling from 'fromTimeZoneAbbrev' to 'toTimeZoneAbbrev'.
  1626. *
  1627. * Be aware that some time zones explicitly represent daylight savings time, so
  1628. * it is entirely possible to change not only time zones but DST observance as
  1629. * well in a single call.
  1630. *
  1631. * @param fromTimeZoneAbbrev The time zone abbreviation designated as the
  1632. * starting point; must be a non-empty uppercase
  1633. * string; REQUIRED
  1634. * @param toTimeZoneAbbrev The time zone abbreviation designated as the
  1635. * ending point; must be a non-empty uppercase
  1636. * string; REQUIRED
  1637. * @param fromLocation The name of the location that goes along with
  1638. * fromTimeZoneAbbrev; if a location is not
  1639. * provided or is an empty string, the first
  1640. * record matching fromTimeZoneAbbrev will be used;
  1641. * OPTIONAL, defaults to an empty string
  1642. * @param toLocation The name of the location that goes along with
  1643. * toTimeZoneAbbrev; if a location is not
  1644. * provided or is an empty string, the first
  1645. * record matching toTimeZoneAbbrev will be used;
  1646. * OPTIONAL, defaults to an empty string
  1647. * @return The number of seconds between the two time
  1648. * zones; will return zero if either time zone
  1649. * cannot be found
  1650. * @see AdjustTimeTZ
  1651. */
  1652. EXPORT INTEGER4 SecondsBetweenTZ(STRING5 fromTimeZoneAbbrev,
  1653. STRING5 toTimeZoneAbbrev,
  1654. STRING fromLocation = '',
  1655. STRING toLocation = '') := FUNCTION
  1656. fromTZ := FindTZData(fromTimeZoneAbbrev, fromLocation);
  1657. toTZ := FindTZData(toTimeZoneAbbrev, toLocation);
  1658. hasTZInfo := EXISTS(fromTZ) AND EXISTS(toTZ);
  1659. fromSecondsOffset := fromTZ[1].secondsOffset;
  1660. toSecondsOffset := toTZ[1].secondsOffset;
  1661. RETURN IF
  1662. (
  1663. hasTZInfo,
  1664. toSecondsOffset - fromSecondsOffset,
  1665. 0
  1666. );
  1667. END;
  1668. /**
  1669. * Adjust a given Time_t time value for another time zone. Both the given time
  1670. * and the destination time zone are designated by a required time zone
  1671. * abbreviation and an optional location name.
  1672. *
  1673. * @param time The time value to adjust; REQUIRED
  1674. * @param fromTimeZoneAbbrev The time zone abbreviation that the time
  1675. * value is assumed to be within; must be a
  1676. * non-empty uppercase string; REQUIRED
  1677. * @param toTimeZoneAbbrev The time zone abbreviation designated as the
  1678. * ending point; must be a non-empty uppercase
  1679. * string; REQUIRED
  1680. * @param fromLocation The name of the location that goes along with
  1681. * fromTimeZoneAbbrev; if a location is not
  1682. * provided or is an empty string, the first
  1683. * record matching fromTimeZoneAbbrev will be used;
  1684. * OPTIONAL, defaults to an empty string
  1685. * @param toLocation The name of the location that goes along with
  1686. * toTimeZoneAbbrev; if a location is not
  1687. * provided or is an empty string, the first
  1688. * record matching toTimeZoneAbbrev will be used;
  1689. * OPTIONAL, defaults to an empty string
  1690. * @return The given time value adjusted by the difference
  1691. * between the two given time zones; if either
  1692. * time zone cannot be found then the original
  1693. * time value will be returned unchanged
  1694. * @see SecondsBetweenTZ
  1695. */
  1696. EXPORT Time_t AdjustTimeTZ(Time_t time,
  1697. STRING5 fromTimeZoneAbbrev,
  1698. STRING5 toTimeZoneAbbrev,
  1699. STRING fromLocation = '',
  1700. STRING toLocation = '') := FUNCTION
  1701. diff := SecondsBetweenTZ(fromTimeZoneAbbrev, toTimeZoneAbbrev, fromLocation, toLocation);
  1702. newTime := AdjustTime(time, second_delta := diff);
  1703. RETURN newTime;
  1704. END;
  1705. /**
  1706. * Converts a UTC time to a time designated by a time zone abbreviation and
  1707. * optional location.
  1708. *
  1709. * @param utcTime The UTC time value to adjust; REQUIRED
  1710. * @param toTimeZoneAbbrev The time zone abbreviation designated as the
  1711. * ending point; must be a non-empty uppercase
  1712. * string; REQUIRED
  1713. * @param toLocation The name of the location that goes along with
  1714. * toTimeZoneAbbrev; if a location is not
  1715. * provided or is an empty string, the first
  1716. * record matching toTimeZoneAbbrev will be used;
  1717. * OPTIONAL, defaults to an empty string
  1718. * @return The given UTC time value adjusted to the time
  1719. * zone defined by toTimeZoneAbbrev and toLocation;
  1720. * if the time zone cannot be found then the
  1721. * original time value will be returned unchanged
  1722. * @see AdjustTimeTZ
  1723. * @see ToUTCTime
  1724. */
  1725. EXPORT Time_t ToLocalTime(Time_t utcTime,
  1726. STRING5 toTimeZoneAbbrev,
  1727. STRING toLocation = '') := FUNCTION
  1728. RETURN AdjustTimeTZ(utcTime, 'UTC', toTimeZoneAbbrev, toLocation := toLocation);
  1729. END;
  1730. /**
  1731. * Converts a local time, defined with a time zone abbreviation and optional
  1732. * location, to a UTC time.
  1733. *
  1734. * @param localTime The time value to adjust; REQUIRED
  1735. * @param fromTimeZoneAbbrev The time zone abbreviation that the localTime
  1736. * value is assumed to be within; must be a
  1737. * non-empty uppercase string; REQUIRED
  1738. * @param fromLocation The name of the location that goes along with
  1739. * fromTimeZoneAbbrev; if a location is not
  1740. * provided or is an empty string, the first
  1741. * record matching fromTimeZoneAbbrev will be used;
  1742. * OPTIONAL, defaults to an empty string
  1743. * @return The given local time value adjusted to UTC time;
  1744. * if the given time zone cannot be found then the
  1745. * original UTC time value will be returned
  1746. * unchanged
  1747. * @see AdjustTimeTZ
  1748. * @see ToLocalTime
  1749. */
  1750. EXPORT Time_t ToUTCTime(Time_t localTime,
  1751. STRING5 fromTimeZoneAbbrev,
  1752. STRING fromLocation = '') := FUNCTION
  1753. RETURN AdjustTimeTZ(localTime, fromTimeZoneAbbrev, 'UTC', fromLocation := fromLocation);
  1754. END;
  1755. /**
  1756. * Given a dataset that contains a time zone abbreviation and optional location,
  1757. * this function macro appends four new attributes to the dataset that contain
  1758. * useful information for translating a time value into another time zone.
  1759. * This could be useful as an ETL step where time data is made common in
  1760. * respect to one particular time zone (e.g. UTC).
  1761. *
  1762. * The actions within this function macro are conceptually similar to
  1763. * SecondsBetweenTZ() but applied to an entire dataset, and somewhat more
  1764. * efficiently.
  1765. *
  1766. * Note: In order for this function macro to execute correctly, the calling
  1767. * code must import the Std library.
  1768. *
  1769. * @param inFile The dataset to process; REQUIRED
  1770. * @param timeZoneAbbrevField The attribute within inFile that contains
  1771. * the time zone abbreviation to use for matching;
  1772. * the values in this attribute should be in
  1773. * uppercase; this is not a string; REQUIRED
  1774. * @param newOffsetField The attribute that will be appended to inFile
  1775. * and will contain the number of seconds offset
  1776. * from UTC; this is not a string; REQUIRED
  1777. * @param fromLocationField The attribute within inFile that contains the
  1778. * time zone location for the time zone cited by
  1779. * timeZoneAbbrevField; this is not a string;
  1780. * OPTIONAL, defaults to a null value (indicating
  1781. * that there is no time zone location attribute)
  1782. * @param toTimeZoneAbbrev The 'to' time zone abbreviation to use for all
  1783. * calculations, as a string; OPTIONAL, defaults
  1784. * to 'UTC'
  1785. * @param toLocation The name of the location that goes along with
  1786. * toTimeZoneAbbrev; if a location is not
  1787. * provided or is an empty string, the first
  1788. * record matching toTimeZoneAbbrev will be used;
  1789. * OPTIONAL, defaults to an empty string
  1790. * @return A new dataset with the same record definition
  1791. * as inFile but with four new attributes added;
  1792. * the new attributes are named based on the name
  1793. * given as the newOffsetField attribute:
  1794. * INTEGER4 <newOffsetField> // Offset, in seconds, between original time zone and toTimeZoneAbbrev
  1795. * BOOLEAN <newOffsetField>_is_valid // TRUE if <newOffsetField> contains a valid value
  1796. * STRING5 <newOffsetField>_tz // The value of toTimeZoneAbbrev
  1797. * STRING15 <newOffsetField>_location // The time zone location for <newOffsetField>_tz
  1798. * If <newOffsetField>_is_valid is FALSE then
  1799. * <newOffsetField> will be zero.
  1800. * @see AppendTZAdjustedTime
  1801. *
  1802. * Examples:
  1803. *
  1804. * ds := DATASET
  1805. * (
  1806. * [
  1807. * {120000, 'CT'},
  1808. * {120000, 'ET'}
  1809. * ],
  1810. * {Std.Date.Time_t time, STRING tz}
  1811. * );
  1812. *
  1813. * utcOffsetDS := Std.Date.TimeZone.AppendTZOffset(ds, tz, seconds_to_utc);
  1814. * OUTPUT(utcOffsetDS, NAMED('offset_to_utc_result'));
  1815. *
  1816. * ptOffsetDS := Std.Date.TimeZone.AppendTZOffset
  1817. * (
  1818. * ds,
  1819. * tz,
  1820. * seconds_to_pacific_time,
  1821. * toTimeZoneAbbrev := 'PT',
  1822. * toLocation := 'NORTH AMERICA'
  1823. * );
  1824. * OUTPUT(ptOffsetDS, NAMED('offset_to_pacific_time_result'));
  1825. */
  1826. EXPORT AppendTZOffset(inFile,
  1827. timeZoneAbbrevField,
  1828. newOffsetField,
  1829. fromLocationField = '',
  1830. toTimeZoneAbbrev = '\'UTC\'',
  1831. toLocation = '\'\'') := FUNCTIONMACRO
  1832. // Find the destination time zone information just once
  1833. #UNIQUENAME(destOffsetDS);
  1834. LOCAL %destOffsetDS% := Std.Date.TimeZone.FindTZData(toTimeZoneAbbrev, toLocation);
  1835. #UNIQUENAME(destOffsetFound);
  1836. LOCAL %destOffsetFound% := EXISTS(%destOffsetDS%);
  1837. #UNIQUENAME(destLocation);
  1838. LOCAL %destLocation% := IF(toLocation != '', toLocation, %destOffsetDS%[1].locations[1]);
  1839. #UNIQUENAME(destOffset);
  1840. LOCAL %destOffset% := %destOffsetDS%[1].secondsOffset;
  1841. RETURN JOIN
  1842. (
  1843. inFile,
  1844. Std.Date.TimeZone.TZ_DATA,
  1845. LEFT.timeZoneAbbrevField = RIGHT.tzAbbrev
  1846. #IF(#TEXT(fromLocationField) != '')
  1847. AND LEFT.fromLocationField IN RIGHT.locations
  1848. #END
  1849. AND %destOffsetFound%,
  1850. TRANSFORM
  1851. (
  1852. {
  1853. RECORDOF(inFile),
  1854. INTEGER4 newOffsetField,
  1855. BOOLEAN #EXPAND(#TEXT(newOffsetField) + '_is_valid'),
  1856. STRING5 #EXPAND(#TEXT(newOffsetField) + '_tz'),
  1857. STRING15 #EXPAND(#TEXT(newOffsetField) + '_location')
  1858. },
  1859. wasFound := RIGHT.tzAbbrev != '';
  1860. SELF.newOffsetField := IF(wasFound, %destOffset% - RIGHT.secondsOffset, 0),
  1861. SELF.#EXPAND(#TEXT(newOffsetField) + '_is_valid') := wasFound,
  1862. SELF.#EXPAND(#TEXT(newOffsetField) + '_tz') := toTimeZoneAbbrev,
  1863. SELF.#EXPAND(#TEXT(newOffsetField) + '_location') := %destLocation%,
  1864. SELF := LEFT
  1865. ),
  1866. LEFT OUTER, LOOKUP
  1867. );
  1868. ENDMACRO;
  1869. /**
  1870. * Given a dataset that contains a time (in Time_t format), a time zone
  1871. * abbreviation, and an optional time zone location, this function macro
  1872. * appends four new attributes to the dataset: A new Time_t attribute
  1873. * containing the original time expressed in a different time zone, and three
  1874. * attributes providing information regarding that destination time zone and
  1875. * the validity of the translation. This could be useful as an ETL step where
  1876. * time data is made common in respect to one particular time zone (e.g. UTC).
  1877. *
  1878. * The actions within this function macro are conceptually similar to
  1879. * AdjustTimeTZ() but applied to an entire dataset, and somewhat more
  1880. * efficiently.
  1881. *
  1882. * Note: In order for this function macro to execute correctly, the calling
  1883. * code must import the Std library.
  1884. *
  1885. * @param inFile The dataset to process; REQUIRED
  1886. * @param timeField The attribute within inFile that contains a
  1887. * time represented in Time_t format; this is not
  1888. * a string; REQUIRED
  1889. * @param timeZoneAbbrevField The attribute within inFile that contains
  1890. * the time zone abbreviation to use for matching;
  1891. * the values in this attribute should be in
  1892. * uppercase; this is not a string; REQUIRED
  1893. * @param newTimeField The attribute that will be appended to inFile
  1894. * and will contain the adjusted value of timeField;
  1895. * this is not a string; REQUIRED
  1896. * @param fromLocationField The attribute within inFile that contains the
  1897. * time zone location for the time zone cited by
  1898. * timeZoneAbbrevField; this is not a string;
  1899. * OPTIONAL, defaults to a null value (indicating
  1900. * that there is no time zone location attribute)
  1901. * @param toTimeZoneAbbrev The 'to' time zone abbreviation to use for all
  1902. * calculations, as a string; OPTIONAL, defaults
  1903. * to 'UTC'
  1904. * @param toLocation The name of the location that goes along with
  1905. * toTimeZoneAbbrev; if a location is not
  1906. * provided or is an empty string, the first
  1907. * record matching toTimeZoneAbbrev will be used;
  1908. * OPTIONAL, defaults to an empty string
  1909. * @return A new dataset with the same record definition
  1910. * as inFile but with four new attributes added;
  1911. * the new attributes are named based on the name
  1912. * given as the newOffsetField attribute:
  1913. * Std.Date.Time_t <newOffsetField> // Value of timeField expressed in new time zone
  1914. * BOOLEAN <newOffsetField>_is_valid // TRUE if <newOffsetField> contains a valid value
  1915. * STRING5 <newOffsetField>_tz // The value of toTimeZoneAbbrev
  1916. * STRING15 <newOffsetField>_location // The time zone location for <newOffsetField>_tz
  1917. * If <newOffsetField>_is_valid is FALSE then
  1918. * <newOffsetField> will have the same value as
  1919. * timeField.
  1920. * @see AppendTZOffset
  1921. *
  1922. * Example:
  1923. *
  1924. * ds := DATASET
  1925. * (
  1926. * [
  1927. * {120000, 'CT'},
  1928. * {120000, 'ET'}
  1929. * ],
  1930. * {Std.Date.Time_t time, STRING tz}
  1931. * );
  1932. *
  1933. * utcRewriteDS := Std.Date.TimeZone.AppendTZAdjustedTime(ds, time, tz, utc_time);
  1934. * OUTPUT(utcRewriteDS, NAMED('utc_result'));
  1935. *
  1936. * ptRewriteDS := Std.Date.TimeZone.AppendTZAdjustedTime
  1937. * (
  1938. * ds,
  1939. * time,
  1940. * tz,
  1941. * pacific_time,
  1942. * toTimeZoneAbbrev := 'PT',
  1943. * toLocation := 'NORTH AMERICA'
  1944. * );
  1945. * OUTPUT(ptRewriteDS, NAMED('pacific_time_result'));
  1946. */
  1947. EXPORT AppendTZAdjustedTime(inFile,
  1948. timeField,
  1949. timeZoneAbbrevField,
  1950. newTimeField,
  1951. fromLocationField = '',
  1952. toTimeZoneAbbrev = '\'UTC\'',
  1953. toLocation = '\'\'') := FUNCTIONMACRO
  1954. // Find the destination time zone information just once
  1955. #UNIQUENAME(destOffsetDS);
  1956. LOCAL %destOffsetDS% := Std.Date.TimeZone.FindTZData(toTimeZoneAbbrev, toLocation);
  1957. #UNIQUENAME(destOffsetFound);
  1958. LOCAL %destOffsetFound% := EXISTS(%destOffsetDS%);
  1959. #UNIQUENAME(destLocation);
  1960. LOCAL %destLocation% := IF(toLocation != '', toLocation, %destOffsetDS%[1].locations[1]);
  1961. #UNIQUENAME(destOffset);
  1962. LOCAL %destOffset% := %destOffsetDS%[1].secondsOffset;
  1963. RETURN JOIN
  1964. (
  1965. inFile,
  1966. Std.Date.TimeZone.TZ_DATA,
  1967. LEFT.timeZoneAbbrevField = RIGHT.tzAbbrev
  1968. #IF(#TEXT(fromLocationField) != '')
  1969. AND LEFT.fromLocationField IN RIGHT.locations
  1970. #END
  1971. AND %destOffsetFound%,
  1972. TRANSFORM
  1973. (
  1974. {
  1975. RECORDOF(inFile),
  1976. Std.Date.Time_t newTimeField,
  1977. BOOLEAN #EXPAND(#TEXT(newTimeField) + '_is_valid'),
  1978. STRING5 #EXPAND(#TEXT(newTimeField) + '_tz'),
  1979. STRING15 #EXPAND(#TEXT(newTimeField) + '_location')
  1980. },
  1981. wasFound := RIGHT.tzAbbrev != '';
  1982. SELF.newTimeField := IF
  1983. (
  1984. wasFound,
  1985. Std.Date.AdjustTime(LEFT.timeField, second_delta := (%destOffset% - RIGHT.secondsOffset)),
  1986. LEFT.timeField
  1987. ),
  1988. SELF.#EXPAND(#TEXT(newTimeField) + '_is_valid') := wasFound,
  1989. SELF.#EXPAND(#TEXT(newTimeField) + '_tz') := toTimeZoneAbbrev,
  1990. SELF.#EXPAND(#TEXT(newTimeField) + '_location') := %destLocation%,
  1991. SELF := LEFT
  1992. ),
  1993. LEFT OUTER, LOOKUP
  1994. );
  1995. ENDMACRO;
  1996. END; // TimeZone Module
  1997. END; // Date Module