Date.ecl 91 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230
  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. The resulting
  472. * date must be representable within the Gregorian calendar after the year 1600.
  473. *
  474. * @param date_text The string to be converted.
  475. * @param format The format of the input string.
  476. * (See documentation for strftime)
  477. * @return The date that was matched in the string. Returns 0 if failed to match
  478. * or if the date components match but the result is an invalid date.
  479. *
  480. * Supported characters:
  481. %B Full month name
  482. %b or %h Abbreviated month name
  483. %d Day of month (two digits)
  484. %e Day of month (two digits, or a space followed by a single digit)
  485. %m Month (two digits)
  486. %t Whitespace
  487. %y Year within century (00-99)
  488. %Y Full year (yyyy)
  489. %j Julian day (1-366)
  490. Common date formats
  491. American '%m/%d/%Y' mm/dd/yyyy
  492. Euro '%d/%m/%Y' dd/mm/yyyy
  493. Iso format '%Y-%m-%d' yyyy-mm-dd
  494. Iso basic '%Y%m%d' yyyymmdd
  495. '%d-%b-%Y' dd-mon-yyyy e.g., '21-Mar-1954'
  496. */
  497. EXPORT Date_t FromStringToDate(STRING date_text, VARSTRING format) :=
  498. StringLib.StringToDate(date_text, format);
  499. /**
  500. * Converts a string to a date using the relevant string format.
  501. *
  502. * @param date_text The string to be converted.
  503. * @param format The format of the input string.
  504. * (See documentation for strftime)
  505. * @return The date that was matched in the string.
  506. * Returns 0 if failed to match.
  507. */
  508. EXPORT Date_t FromString(STRING date_text, VARSTRING format) :=
  509. FromStringToDate(date_text, format) : DEPRECATED('Replaced with FromStringToDate() function');
  510. /**
  511. * Converts a string to a Time_t using the relevant string format.
  512. *
  513. * @param date_text The string to be converted.
  514. * @param format The format of the input string.
  515. * (See documentation for strftime)
  516. * @return The time that was matched in the string. Returns 0 if failed to match.
  517. *
  518. * Supported characters:
  519. %H Hour (two digits)
  520. %k (two digits, or a space followed by a single digit)
  521. %M Minute (two digits)
  522. %S Second (two digits)
  523. %t Whitespace
  524. */
  525. EXPORT Time_t FromStringToTime(STRING time_text, VARSTRING format) :=
  526. StringLib.StringToTimeOfDay(time_text, format);
  527. /**
  528. * Matches a string against a set of date string formats and returns a valid
  529. * Date_t object from the first format that successfully parses the string.
  530. *
  531. * @param date_text The string to be converted.
  532. * @param formats A set of formats to check against the string.
  533. * (See documentation for strftime)
  534. * @return The date that was matched in the string.
  535. * Returns 0 if failed to match.
  536. *
  537. * Supported 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. %t Whitespace
  544. %y Year within century (00-99)
  545. %Y Full year (yyyy)
  546. %j Julian day (1-366)
  547. Common date formats
  548. American '%m/%d/%Y' mm/dd/yyyy
  549. Euro '%d/%m/%Y' dd/mm/yyyy
  550. Iso format '%Y-%m-%d' yyyy-mm-dd
  551. Iso basic '%Y%m%d' yyyymmdd
  552. '%d-%b-%Y' dd-mon-yyyy e.g., '21-Mar-1954'
  553. */
  554. EXPORT Date_t MatchDateString(STRING date_text, SET OF VARSTRING formats) :=
  555. StringLib.MatchDate(date_text, formats);
  556. /**
  557. * Matches a string against a set of time string formats and returns a valid
  558. * Time_t object from the first format that successfully parses the string.
  559. *
  560. * @param time_text The string to be converted.
  561. * @param formats A set of formats to check against the string.
  562. * (See documentation for strftime)
  563. * @return The time that was matched in the string.
  564. * Returns 0 if failed to match.
  565. */
  566. EXPORT Time_t MatchTimeString(STRING time_text, SET OF VARSTRING formats) :=
  567. StringLib.MatchTimeOfDay(time_text, formats);
  568. /**
  569. * Formats a date as a string.
  570. *
  571. * @param date The date to be converted.
  572. * @param format The format template to use for the conversion;
  573. * see strftime() for appropriate values. The maximum
  574. * length of the resulting string is 255 characters.
  575. * Optional; defaults to '%Y-%m-%d' which is YYYY-MM-DD.
  576. * @return Blank if date cannot be formatted, or the date in the
  577. * requested format.
  578. */
  579. EXPORT STRING DateToString(Date_t date, VARSTRING format = '%Y-%m-%d') :=
  580. TimeLib.DateToString(date, format);
  581. /**
  582. * Formats a time as a string.
  583. *
  584. * @param time The time to be converted.
  585. * @param format The format template to use for the conversion;
  586. * see strftime() for appropriate values. The maximum
  587. * length of the resulting string is 255 characters.
  588. * Optional; defaults to '%H:%M:%S' which is HH:MM:SS.
  589. * @return Blank if the time cannot be formatted, or the time
  590. * in the requested format.
  591. */
  592. EXPORT STRING TimeToString(Time_t time, VARSTRING format = '%H:%M:%S') :=
  593. TimeLib.TimeToString(time, format);
  594. /**
  595. * Converts a Seconds_t value into a human-readable string using a format template.
  596. *
  597. * @param seconds The seconds since epoch.
  598. * @param format The format template to use for the conversion; see
  599. * strftime() for appropriate values. The maximum length
  600. * of the resulting string is 255 characters.
  601. * Optional; defaults to '%Y-%m-%dT%H:%M:%S' which is YYYY-MM-DDTHH:MM:SS.
  602. * @return The converted seconds as a string.
  603. */
  604. EXPORT STRING SecondsToString(Seconds_t seconds, VARSTRING format = '%Y-%m-%dT%H:%M:%S') :=
  605. TimeLib.SecondsToString(seconds, format);
  606. /**
  607. * Converts a Timestamp_t value into a human-readable string using a format template.
  608. *
  609. * @param timestamp The microseconds since epoch.
  610. * @param format The format template to use for the conversion; see
  611. * strftime() for appropriate format specifiers. Two
  612. * additional format specifiers are available to show
  613. * fractional seconds:
  614. * %@ - fraction of seconds in microseconds (6 digits)
  615. * %# - fraction of seconds in milliseconds (3 digits)
  616. * The maximum length of the resulting string is 255
  617. * characters. This parameter is optional and defaults to
  618. * '%Y-%m-%dT%H:%M:%S.%@' which is YYYY-MM-DDTHH:MM:SS.ssssss.
  619. * @return The converted timestamp as a string.
  620. */
  621. EXPORT STRING TimestampToString(Timestamp_t timestamp, VARSTRING format = '%Y-%m-%dT%H:%M:%S.%@') := FUNCTION
  622. f := INTFORMAT(timestamp % 1000000, 6, 1);
  623. s1 := TimeLib.SecondsToString(timestamp DIV 1000000, format);
  624. s2 := REGEXREPLACE('%@', s1, f);
  625. s3 := REGEXREPLACE('%#', s2, f[..3]);
  626. RETURN s3;
  627. END;
  628. /**
  629. * Formats a date as a string.
  630. *
  631. * @param date The date to be converted.
  632. * @param format The format the date is output in.
  633. * (See documentation for strftime)
  634. * @return Blank if date cannot be formatted, or the date in the
  635. * requested format.
  636. */
  637. EXPORT STRING ToString(Date_t date, VARSTRING format) := DateToString(date, format) : DEPRECATED('Replaced with DateToString() function');
  638. /**
  639. * Converts a date from one format to another
  640. *
  641. * @param date_text The string containing the date to be converted.
  642. * @param from_format The format the date is to be converted from.
  643. * @param to_format The format the date is to be converted to.
  644. * @return The converted string, or blank if it failed to match the format.
  645. * @see FromStringToDate
  646. */
  647. EXPORT STRING ConvertDateFormat(STRING date_text, VARSTRING from_format='%m/%d/%Y', VARSTRING to_format='%Y%m%d') := FUNCTION
  648. parsedDate := FromStringToDate(date_text, from_format);
  649. reformatResult := IF(parsedDate = (Date_t)0, '', DateToString(parsedDate, to_format));
  650. RETURN reformatResult;
  651. END;
  652. /**
  653. * Converts a date from one format to another
  654. *
  655. * @param date_text The string containing the date to be converted.
  656. * @param from_format The format the date is to be converted from.
  657. * @param to_format The format the date is to be converted to.
  658. * @return The converted string, or blank if it failed to match the format.
  659. */
  660. EXPORT STRING ConvertFormat(STRING date_text, VARSTRING from_format='%m/%d/%Y', VARSTRING to_format='%Y%m%d') :=
  661. ConvertDateFormat(date_text, from_format, to_format) : DEPRECATED('Replaced with ConvertDateFormat() function');
  662. /**
  663. * Converts a time from one format to another
  664. *
  665. * @param time_text The string containing the time to be converted.
  666. * @param from_format The format the time is to be converted from.
  667. * @param to_format The format the time is to be converted to.
  668. * @return The converted string, or blank if it failed to match the format.
  669. */
  670. EXPORT STRING ConvertTimeFormat(STRING time_text, VARSTRING from_format='%H%M%S', VARSTRING to_format='%H:%M:%S') :=
  671. TimeToString(FromStringToTime(time_text, from_format), to_format);
  672. /**
  673. * Converts a date that matches one of a set of formats to another.
  674. *
  675. * @param date_text The string containing the date to be converted.
  676. * @param from_formats The list of formats the date is to be converted from.
  677. * @param to_format The format the date is to be converted to.
  678. * @return The converted string, or blank if it failed to match the format.
  679. * @see MatchDateString
  680. */
  681. EXPORT STRING ConvertDateFormatMultiple(STRING date_text, SET OF VARSTRING from_formats, VARSTRING to_format='%Y%m%d') := FUNCTION
  682. matchResult := MatchDateString(date_text, from_formats);
  683. reformatResult := IF(matchResult = (Date_t)0, '', DateToString(matchResult, to_format));
  684. RETURN reformatResult;
  685. END;
  686. /**
  687. * Converts a date that matches one of a set of formats to another.
  688. *
  689. * @param date_text The string containing the date to be converted.
  690. * @param from_formats The list of formats the date is to be converted from.
  691. * @param to_format The format the date is to be converted to.
  692. * @return The converted string, or blank if it failed to match the format.
  693. */
  694. EXPORT STRING ConvertFormatMultiple(STRING date_text, SET OF VARSTRING from_formats, VARSTRING to_format='%Y%m%d') :=
  695. ConvertDateFormatMultiple(date_text, from_formats, to_format) : DEPRECATED('Replaced with ConvertDateFormatMultiple() function');
  696. /**
  697. * Converts a time that matches one of a set of formats to another.
  698. *
  699. * @param time_text The string containing the time to be converted.
  700. * @param from_formats The list of formats the time is to be converted from.
  701. * @param to_format The format the time is to be converted to.
  702. * @return The converted string, or blank if it failed to match the format.
  703. */
  704. EXPORT STRING ConvertTimeFormatMultiple(STRING time_text, SET OF VARSTRING from_formats, VARSTRING to_format='%H:%m:%s') :=
  705. TimeToString(MatchTimeString(time_text, from_formats), to_format);
  706. /**
  707. * Adjusts a date by incrementing or decrementing year, month and/or day values.
  708. * The date must be in the Gregorian calendar after the year 1600.
  709. * If the new calculated date is invalid then it will be normalized according
  710. * to mktime() rules. Example: 20140130 + 1 month = 20140302.
  711. *
  712. * @param date The date to adjust.
  713. * @param year_delta The requested change to the year value;
  714. * optional, defaults to zero.
  715. * @param month_delta The requested change to the month value;
  716. * optional, defaults to zero.
  717. * @param day_delta The requested change to the day of month value;
  718. * optional, defaults to zero.
  719. * @return The adjusted Date_t value.
  720. */
  721. EXPORT Date_t AdjustDate(Date_t date,
  722. INTEGER2 year_delta = 0,
  723. INTEGER4 month_delta = 0,
  724. INTEGER4 day_delta = 0) :=
  725. TimeLib.AdjustDate(date, year_delta, month_delta, day_delta);
  726. /**
  727. * Adjusts a date by adding or subtracting seconds. The date must be in the
  728. * Gregorian calendar after the year 1600. If the new calculated
  729. * date is invalid then it will be normalized according to mktime() rules.
  730. * Example: 20140130 + 172800 seconds = 20140201.
  731. *
  732. * @param date The date to adjust.
  733. * @param seconds_delta The requested change to the date, in seconds.
  734. * @return The adjusted Date_t value.
  735. */
  736. EXPORT Date_t AdjustDateBySeconds(Date_t date, INTEGER4 seconds_delta) :=
  737. TimeLib.AdjustDateBySeconds(date, seconds_delta);
  738. /**
  739. * Adjusts a time by incrementing or decrementing hour, minute and/or second
  740. * values. If the new calculated time is invalid then it will be normalized
  741. * according to mktime() rules.
  742. *
  743. * @param time The time to adjust.
  744. * @param hour_delta The requested change to the hour value;
  745. * optional, defaults to zero.
  746. * @param minute_delta The requested change to the minute value;
  747. * optional, defaults to zero.
  748. * @param second_delta The requested change to the second of month value;
  749. * optional, defaults to zero.
  750. * @return The adjusted Time_t value.
  751. */
  752. EXPORT Time_t AdjustTime(Time_t time,
  753. INTEGER2 hour_delta = 0,
  754. INTEGER4 minute_delta = 0,
  755. INTEGER4 second_delta = 0) :=
  756. TimeLib.AdjustTime(time, hour_delta, minute_delta, second_delta);
  757. /**
  758. * Adjusts a time by adding or subtracting seconds. If the new calculated
  759. * time is invalid then it will be normalized according to mktime() rules.
  760. *
  761. * @param time The time to adjust.
  762. * @param seconds_delta The requested change to the time, in seconds.
  763. * @return The adjusted Time_t value.
  764. */
  765. EXPORT Time_t AdjustTimeBySeconds(Time_t time, INTEGER4 seconds_delta) :=
  766. TimeLib.AdjustTimeBySeconds(time, seconds_delta);
  767. /**
  768. * Adjusts a Seconds_t value by adding or subtracting years, months, days,
  769. * hours, minutes and/or seconds. This is performed by first converting the
  770. * seconds into a full date/time structure, applying any delta values to
  771. * individual date/time components, then converting the structure back to the
  772. * number of seconds. This interim date must lie within Gregorian calendar
  773. * after the year 1600. If the interim structure is found to have an invalid
  774. * date/time then it will be normalized according to mktime() rules. Therefore,
  775. * some delta values (such as "1 month") are actually relative to the value of
  776. * the seconds argument.
  777. *
  778. * @param seconds The number of seconds to adjust.
  779. * @param year_delta The requested change to the year value;
  780. * optional, defaults to zero.
  781. * @param month_delta The requested change to the month value;
  782. * optional, defaults to zero.
  783. * @param day_delta The requested change to the day of month value;
  784. * optional, defaults to zero.
  785. * @param hour_delta The requested change to the hour value;
  786. * optional, defaults to zero.
  787. * @param minute_delta The requested change to the minute value;
  788. * optional, defaults to zero.
  789. * @param second_delta The requested change to the second of month value;
  790. * optional, defaults to zero.
  791. * @return The adjusted Seconds_t value.
  792. */
  793. EXPORT Seconds_t AdjustSeconds(Seconds_t seconds,
  794. INTEGER2 year_delta = 0,
  795. INTEGER4 month_delta = 0,
  796. INTEGER4 day_delta = 0,
  797. INTEGER4 hour_delta = 0,
  798. INTEGER4 minute_delta = 0,
  799. INTEGER4 second_delta = 0) :=
  800. TimeLib.AdjustSeconds(seconds, year_delta, month_delta, day_delta, hour_delta, minute_delta, second_delta);
  801. /**
  802. * Adjusts a date by incrementing or decrementing months and/or years. This
  803. * routine uses the rule outlined in McGinn v. State, 46 Neb. 427, 65 N.W. 46 (1895):
  804. * "The term calendar month, whether employed in statutes or contracts, and
  805. * not appearing to have been used in a different sense, denotes a period
  806. * terminating with the day of the succeeding month numerically corresponding
  807. * to the day of its beginning, less one. If there be no corresponding day of
  808. * the succeeding month, it terminates with the last day thereof." The
  809. * internet suggests similar legal positions exist in the Commonwealth
  810. * and Germany. Note that day adjustments are performed after year and month
  811. * adjustments using the preceding rules. As an example, Jan. 31, 2014 + 1 month
  812. * will result in Feb. 28, 2014; Jan. 31, 2014 + 1 month + 1 day will result
  813. * in Mar. 1, 2014.
  814. *
  815. * @param date The date to adjust, in the Gregorian calendar after 1600.
  816. * @param year_delta The requested change to the year value;
  817. * optional, defaults to zero.
  818. * @param month_delta The requested change to the month value;
  819. * optional, defaults to zero.
  820. * @param day_delta The requested change to the day value;
  821. * optional, defaults to zero.
  822. * @return The adjusted Date_t value.
  823. */
  824. EXPORT Date_t AdjustCalendar(Date_t date,
  825. INTEGER2 year_delta = 0,
  826. INTEGER4 month_delta = 0,
  827. INTEGER4 day_delta = 0) :=
  828. TimeLib.AdjustCalendar(date, year_delta, month_delta, day_delta);
  829. /**
  830. * Helper function. Calculates the 1-based week number of a date, starting from
  831. * a reference date. Week 1 always contains the reference date, and week 2
  832. * begins on the following day of the week indicated by the value of
  833. * startingDayOfWeek. This is not an ISO-8601 implementation of computing week
  834. * numbers ("week dates").
  835. *
  836. * @param date The date for which to compute the week number;
  837. * must be greater than or equal to referenceDate
  838. * @param referenceDate The date from which the week number counting begins;
  839. * must be less than or equal to date
  840. * @param startingDayOfWeek The index number of the first day of a week, 1-7,
  841. * where 1 = Sunday
  842. * @return The 1-based week number of date, relative to
  843. * referenceDate
  844. *
  845. * @see YearWeekNumFromDate, MonthWeekNumFromDate
  846. */
  847. SHARED WeekNumForDate(Date_t date, Date_t referenceDate, UNSIGNED1 startingDayOfWeek) := FUNCTION
  848. referenceDayOfWeek := DayOfWeek(referenceDate);
  849. startingDayOfWeekDelta := (startingDayOfWeek - referenceDayOfWeek) % 7;
  850. referenceFirstDateOfWeek := AdjustDate(referenceDate, day_delta := startingDayOfWeekDelta);
  851. numberOfDays := DaysBetween(referenceFirstDateOfWeek, date) + 1;
  852. weekNum0 := (numberOfDays + 6) DIV 7;
  853. weekNum := IF(startingDayOfWeek > referenceDayOfWeek, weekNum0 + 1, weekNum0);
  854. RETURN weekNum;
  855. END;
  856. /**
  857. * Returns the 1-based week number of a date within the date's year. Week 1
  858. * always contains the first day of the year, and week 2 begins on the
  859. * following day of the week indicated by the value of startingDayOfWeek. This
  860. * is not an ISO-8601 implementation of computing week numbers ("week dates").
  861. *
  862. * @param date The date for which to compute the week number
  863. * @param startingDayOfWeek The index number of the first day of a week, 1-7,
  864. * where 1 = Sunday; OPTIONAL, defaults to 1
  865. * @return The 1-based week number of date, relative to
  866. * the beginning of the date's year
  867. *
  868. * @see MonthWeekNumFromDate
  869. */
  870. EXPORT YearWeekNumFromDate(Date_t date, UNSIGNED1 startingDayOfWeek = 1) := FUNCTION
  871. yearStart := DateFromParts(Year(date), 1, 1);
  872. RETURN WeekNumForDate(date, yearStart, startingDayOfWeek);
  873. END;
  874. /**
  875. * Returns the 1-based week number of a date within the date's month. Week 1
  876. * always contains the first day of the month, and week 2 begins on the
  877. * following day of the week indicated by the value of startingDayOfWeek. This
  878. * is not an ISO-8601 implementation of computing week numbers ("week dates").
  879. *
  880. * @param date The date for which to compute the week number
  881. * @param startingDayOfWeek The index number of the first day of a week, 1-7,
  882. * where 1 = Sunday; OPTIONAL, defaults to 1
  883. * @return The 1-based week number of date, relative to
  884. * the beginning of the date's month
  885. *
  886. * @see YearWeekNumFromDate
  887. */
  888. EXPORT MonthWeekNumFromDate(Date_t date, UNSIGNED1 startingDayOfWeek = 1) := FUNCTION
  889. monthStart := DateFromParts(Year(date), Month(date), 1);
  890. RETURN WeekNumForDate(date, monthStart, startingDayOfWeek);
  891. END;
  892. /**
  893. * Returns a boolean indicating whether daylight savings time is currently
  894. * in effect locally.
  895. *
  896. * @return TRUE if daylight savings time is currently in effect, FALSE otherwise.
  897. */
  898. EXPORT BOOLEAN IsLocalDaylightSavingsInEffect() :=
  899. TimeLib.IsLocalDaylightSavingsInEffect();
  900. /**
  901. * Returns the offset (in seconds) of the time represented from UTC, with
  902. * positive values indicating locations east of the Prime Meridian. Given a
  903. * UTC time in seconds since epoch, you can find the local time by adding the
  904. * result of this function to the seconds. Note that daylight savings time is
  905. * factored into the offset.
  906. *
  907. * @return The number of seconds offset from UTC.
  908. */
  909. EXPORT INTEGER4 LocalTimeZoneOffset() :=
  910. TimeLib.LocalTimeZoneOffset();
  911. /**
  912. * Returns the current date.
  913. *
  914. * @param in_local_time TRUE if the returned value should be local to the
  915. * cluster computing the date, FALSE for UTC.
  916. * Optional, defaults to FALSE.
  917. * @return A Date_t representing the current date.
  918. */
  919. EXPORT Date_t CurrentDate(BOOLEAN in_local_time = FALSE) :=
  920. TimeLib.CurrentDate(in_local_time);
  921. /**
  922. * Returns the current date in the local time zone.
  923. *
  924. * @return A Date_t representing the current date.
  925. */
  926. EXPORT Date_t Today() := CurrentDate(TRUE);
  927. /**
  928. * Returns the current time of day
  929. *
  930. * @param in_local_time TRUE if the returned value should be local to the
  931. * cluster computing the time, FALSE for UTC.
  932. * Optional, defaults to FALSE.
  933. * @return A Time_t representing the current time of day.
  934. */
  935. EXPORT Time_t CurrentTime(BOOLEAN in_local_time = FALSE) :=
  936. TimeLib.CurrentTime(in_local_time);
  937. /**
  938. * Returns the current date and time as the number of seconds since epoch.
  939. *
  940. * @param in_local_time TRUE if the returned value should be local to the
  941. * cluster computing the time, FALSE for UTC.
  942. * Optional, defaults to FALSE.
  943. * @return A Seconds_t representing the current time in
  944. * UTC or local time, depending on the argument.
  945. */
  946. EXPORT Seconds_t CurrentSeconds(BOOLEAN in_local_time = FALSE) :=
  947. TimeLib.CurrentSeconds(in_local_time);
  948. /**
  949. * Returns the current date and time as the number of microseconds since epoch.
  950. *
  951. * @param in_local_time TRUE if the returned value should be local to the
  952. * cluster computing the time, FALSE for UTC.
  953. * Optional, defaults to FALSE.
  954. * @return A Timestamp_t representing the current time in
  955. * microseconds in UTC or local time, depending on
  956. * the argument.
  957. */
  958. EXPORT Timestamp_t CurrentTimestamp(BOOLEAN in_local_time = FALSE) :=
  959. TimeLib.CurrentTimestamp(in_local_time);
  960. /**
  961. * Returns the beginning and ending dates for the month surrounding the given date.
  962. *
  963. * @param as_of_date The reference date from which the month will be
  964. * calculated. This date must be a date within the
  965. * Gregorian calendar. Optional, defaults to the
  966. * current date in UTC.
  967. * @return Module with exported attributes for startDate and endDate.
  968. */
  969. EXPORT DatesForMonth(Date_t as_of_date = CurrentDate(FALSE)) := FUNCTION
  970. lastDay := TimeLib.GetLastDayOfMonth(as_of_date);
  971. firstDay := (lastDay DIV 100) * 100 + 1;
  972. result := MODULE
  973. EXPORT Date_t startDate := firstDay;
  974. EXPORT Date_t endDate := lastDay;
  975. END;
  976. RETURN result;
  977. END;
  978. /**
  979. * Returns the beginning and ending dates for the week surrounding the given date
  980. * (Sunday marks the beginning of a week).
  981. *
  982. * @param as_of_date The reference date from which the week will be
  983. * calculated. This date must be a date within the
  984. * Gregorian calendar. Optional, defaults to the
  985. * current date in UTC.
  986. * @return Module with exported attributes for startDate and endDate.
  987. */
  988. EXPORT DatesForWeek(Date_t as_of_date = CurrentDate(FALSE)) := FUNCTION
  989. lastWeekDates := ROW(TimeLib.DatesForWeek(as_of_date));
  990. result := MODULE
  991. EXPORT Date_t startDate := lastWeekDates.startDate;
  992. EXPORT Date_t endDate := lastWeekDates.endDate;
  993. END;
  994. RETURN result;
  995. END;
  996. /**
  997. * Tests whether a date is valid, both by range-checking the year and by
  998. * validating each of the other individual components.
  999. *
  1000. * @param date The date to validate.
  1001. * @param yearLowerBound The minimum acceptable year.
  1002. * Optional; defaults to 1800.
  1003. * @param yearUpperBound The maximum acceptable year.
  1004. * Optional; defaults to 2100.
  1005. * @return TRUE if the date is valid, FALSE otherwise.
  1006. */
  1007. EXPORT BOOLEAN IsValidDate(Date_t date,
  1008. INTEGER2 yearLowerBound = 1800,
  1009. INTEGER2 yearUpperBound = 2100) := FUNCTION
  1010. yearInBounds := (Year(date) BETWEEN yearLowerBound AND yearUpperBound);
  1011. monthInBounds := (Month(date) BETWEEN 1 AND 12);
  1012. maxDayInMonth := CHOOSE(Month(date),31,IF(IsLeapYear(Year(date)),29,28),31,30,31,30,31,31,30,31,30,31);
  1013. dayInBounds := (Day(date) BETWEEN 1 AND maxDayInMonth);
  1014. RETURN yearInBounds AND monthInBounds AND dayInBounds;
  1015. END;
  1016. /**
  1017. * Tests whether a date is valid in the Gregorian calendar. The year
  1018. * must be between 1601 and 30827.
  1019. *
  1020. * @param date The Date_t to validate.
  1021. * @return TRUE if the date is valid, FALSE otherwise.
  1022. */
  1023. EXPORT BOOLEAN IsValidGregorianDate(Date_t date) := FUNCTION
  1024. yearInBounds := (Year(date) BETWEEN 1601 AND 30827);
  1025. matchesNormalized := (date = AdjustDate(date)); // AdjustDate normalizes, so this is a validation check
  1026. RETURN yearInBounds AND matchesNormalized;
  1027. END;
  1028. /**
  1029. * Tests whether a time is valid.
  1030. *
  1031. * @param time The time to validate.
  1032. * @return TRUE if the time is valid, FALSE otherwise.
  1033. */
  1034. EXPORT BOOLEAN IsValidTime(Time_t time) := FUNCTION
  1035. hourInBounds := (Hour(time) <= 23);
  1036. minuteInBounds := (Minute(time) <= 59);
  1037. secondInBounds := (Second(time) <= 59);
  1038. RETURN hourInBounds AND minuteInBounds AND secondInBounds;
  1039. END;
  1040. //------------------------------------------------------------------------------
  1041. // Transforms
  1042. //------------------------------------------------------------------------------
  1043. /**
  1044. * A transform to create a Date_rec from the individual elements
  1045. *
  1046. * @param year The year
  1047. * @param month The month (1-12).
  1048. * @param day The day (1..daysInMonth).
  1049. * @return A transform that creates a Date_rec containing the date.
  1050. */
  1051. EXPORT Date_rec CreateDate(INTEGER2 year, UNSIGNED1 month, UNSIGNED1 day) := TRANSFORM
  1052. SELF.year := year;
  1053. SELF.month := month;
  1054. SELF.day := day;
  1055. END;
  1056. /**
  1057. * A transform to create a Date_rec from a Seconds_t value.
  1058. *
  1059. * @param seconds The number seconds since epoch.
  1060. * @param is_local_time TRUE if seconds is expressed in local time
  1061. * rather than UTC, FALSE if seconds is expressed
  1062. * in UTC. Optional, defaults to FALSE.
  1063. * @return A transform that creates a Date_rec containing
  1064. * the date.
  1065. */
  1066. EXPORT Date_rec CreateDateFromSeconds(Seconds_t seconds, BOOLEAN is_local_time = FALSE) := TRANSFORM
  1067. timeParts := SecondsToParts(seconds, is_local_time);
  1068. SELF.year := timeParts.year;
  1069. SELF.month := timeParts.month;
  1070. SELF.day := timeParts.day;
  1071. END;
  1072. /**
  1073. * A transform to create a Time_rec from the individual elements
  1074. *
  1075. * @param hour The hour (0-23).
  1076. * @param minute The minute (0-59).
  1077. * @param second The second (0-59).
  1078. * @return A transform that creates a Time_rec containing the time of day.
  1079. */
  1080. EXPORT Time_rec CreateTime(UNSIGNED1 hour, UNSIGNED1 minute, UNSIGNED1 second) := TRANSFORM
  1081. SELF.hour := hour;
  1082. SELF.minute := minute;
  1083. SELF.second := second;
  1084. END;
  1085. /**
  1086. * A transform to create a Time_rec from a Seconds_t value.
  1087. *
  1088. * @param seconds The number seconds since epoch.
  1089. * @param is_local_time TRUE if seconds is expressed in local time
  1090. * rather than UTC, FALSE if seconds is expressed
  1091. * in UTC. Optional, defaults to FALSE.
  1092. * @return A transform that creates a Time_rec containing
  1093. * the time of day.
  1094. */
  1095. EXPORT Time_rec CreateTimeFromSeconds(Seconds_t seconds, BOOLEAN is_local_time = FALSE) := TRANSFORM
  1096. timeParts := SecondsToParts(seconds, is_local_time);
  1097. SELF.hour := timeParts.hour;
  1098. SELF.minute := timeParts.minute;
  1099. SELF.second := timeParts.second;
  1100. END;
  1101. /**
  1102. * A transform to create a DateTime_rec from the individual elements
  1103. *
  1104. * @param year The year
  1105. * @param month The month (1-12).
  1106. * @param day The day (1..daysInMonth).
  1107. * @param hour The hour (0-23).
  1108. * @param minute The minute (0-59).
  1109. * @param second The second (0-59).
  1110. * @return A transform that creates a DateTime_rec containing date
  1111. * and time components.
  1112. */
  1113. EXPORT DateTime_rec CreateDateTime(INTEGER2 year,
  1114. UNSIGNED1 month,
  1115. UNSIGNED1 day,
  1116. UNSIGNED1 hour,
  1117. UNSIGNED1 minute,
  1118. UNSIGNED1 second) := TRANSFORM
  1119. SELF.year := year;
  1120. SELF.month := month;
  1121. SELF.day := day;
  1122. SELF.hour := hour;
  1123. SELF.minute := minute;
  1124. SELF.second := second;
  1125. END;
  1126. /**
  1127. * A transform to create a DateTime_rec from a Seconds_t value.
  1128. *
  1129. * @param seconds The number seconds since epoch.
  1130. * @param is_local_time TRUE if seconds is expressed in local time
  1131. * rather than UTC, FALSE if seconds is expressed
  1132. * in UTC. Optional, defaults to FALSE.
  1133. * @return A transform that creates a DateTime_rec
  1134. * containing date and time components.
  1135. */
  1136. EXPORT DateTime_rec CreateDateTimeFromSeconds(Seconds_t seconds, BOOLEAN is_local_time = FALSE) := TRANSFORM
  1137. timeParts := SecondsToParts(seconds, is_local_time);
  1138. SELF.year := timeParts.year;
  1139. SELF.month := timeParts.month;
  1140. SELF.day := timeParts.day;
  1141. SELF.hour := timeParts.hour;
  1142. SELF.minute := timeParts.minute;
  1143. SELF.second := timeParts.second;
  1144. END;
  1145. //------------------------------------------------------------------------------
  1146. // Time Zone Module
  1147. //------------------------------------------------------------------------------
  1148. EXPORT TimeZone := MODULE, FORWARD
  1149. /**
  1150. * Record definition for exported time zone information
  1151. */
  1152. EXPORT TZDataLayout := RECORD
  1153. STRING5 tzAbbrev; // Time zone abbreviation; always uppercase; may be duplicated between records
  1154. INTEGER4 secondsOffset; // Number of seconds east (positive) or west (negative) of UTC
  1155. SET OF STRING15 locations; // Names of locations that use the given time zone abbreviation
  1156. END;
  1157. /**
  1158. * Hardcoded time zone definitions; a general description of each time zone
  1159. * is included as a line comment. This information was collected from
  1160. * https://www.timeanddate.com/time/zones/ with one modification (see below).
  1161. *
  1162. * The IST abbreviation can indicate three different time zones:
  1163. * India Standard Time
  1164. * Irish Standard Time
  1165. * Israel Standard Time
  1166. *
  1167. * Unfortunately, two of those IST time zones lie in the same location: ASIA.
  1168. * That makes it impossible to differentiate between them, and they have very
  1169. * different offsets. As a consequence, locations for Israel Standard Time and
  1170. * Israel Daylight Time have been changed from ASIA to ISRAEL.
  1171. */
  1172. EXPORT TZ_DATA := DATASET
  1173. (
  1174. [
  1175. {'A', 3600, ['MILITARY']}, // Alpha Time Zone
  1176. {'ACDT', 37800, ['AUSTRALIA']}, // Australian Central Daylight Time
  1177. {'ACST', 34200, ['AUSTRALIA']}, // Australian Central Standard Time
  1178. {'ACT', -18000, ['SOUTH AMERICA']}, // Acre Time
  1179. {'ACT', 34200, ['AUSTRALIA']}, // Australian Central Time
  1180. {'ACWST', 31500, ['AUSTRALIA']}, // Australian Central Western Standard Time
  1181. {'ADT', 10800, ['ASIA']}, // Arabia Daylight Time
  1182. {'ADT', -10800, ['NORTH AMERICA', 'ATLANTIC']}, // Atlantic Daylight Time
  1183. {'AEDT', 39600, ['AUSTRALIA']}, // Australian Eastern Daylight Time
  1184. {'AEST', 36000, ['AUSTRALIA']}, // Australian Eastern Standard Time
  1185. {'AET', 36000, ['AUSTRALIA']}, // Australian Eastern Time
  1186. {'AFT', 16200, ['ASIA']}, // Afghanistan Time
  1187. {'AKDT', -28800, ['NORTH AMERICA']}, // Alaska Daylight Time
  1188. {'AKST', -32400, ['NORTH AMERICA']}, // Alaska Standard Time
  1189. {'ALMT', 21600, ['ASIA']}, // Alma-Ata Time
  1190. {'AMST', -10800, ['SOUTH AMERICA']}, // Amazon Summer Time
  1191. {'AMST', 18000, ['ASIA']}, // Armenia Summer Time
  1192. {'AMT', -14400, ['SOUTH AMERICA']}, // Amazon Time
  1193. {'AMT', 14400, ['ASIA']}, // Armenia Time
  1194. {'ANAST', 43200, ['ASIA']}, // Anadyr Summer Time
  1195. {'ANAT', 43200, ['ASIA']}, // Anadyr Time
  1196. {'AOE', -43200, ['PACIFIC']}, // Anywhere on Earth
  1197. {'AQTT', 18000, ['ASIA']}, // Aqtobe Time
  1198. {'ART', -10800, ['ANTARCTICA', 'SOUTH AMERICA']}, // Argentina Time
  1199. {'AST', 7200, ['ASIA']}, // Arabia Standard Time
  1200. {'AST', -14400, ['NORTH AMERICA', 'ATLANTIC' , 'CARIBBEAN']}, // Atlantic Standard Time
  1201. {'AT', -14400, ['NORTH AMERICA', 'ATLANTIC', 'CARIBBEAN']}, // Atlantic Time
  1202. {'AWDT', 32400, ['AUSTRALIA']}, // Australian Western Daylight Time
  1203. {'AWST', 28800, ['AUSTRALIA']}, // Australian Western Standard Time
  1204. {'AZOST', 0, ['ATLANTIC']}, // Azores Summer Time
  1205. {'AZOT', -3600, ['ATLANTIC']}, // Azores Time
  1206. {'AZST', 18000, ['ASIA']}, // Azerbaijan Summer Time
  1207. {'AZT', 14400, ['ASIA']}, // Azerbaijan Time
  1208. {'B', 7200, ['MILITARY']}, // Bravo Time Zone
  1209. {'BNT', 28800, ['ASIA']}, // Brunei Darussalam Time
  1210. {'BOT', -14400, ['SOUTH AMERICA']}, // Bolivia Time
  1211. {'BRST', -7200, ['SOUTH AMERICA']}, // Brazil Summer Time
  1212. {'BRT', -10800, ['SOUTH AMERICA']}, // Brazil Time
  1213. {'BST', 21600, ['ASIA']}, // Bangladesh Standard Time
  1214. {'BST', 39600, ['PACIFIC']}, // Bougainville Standard Time
  1215. {'BST', 3600, ['EUROPE']}, // British Summer Time
  1216. {'BTT', 21600, ['ASIA']}, // Bhutan Time
  1217. {'C', 10800, ['MILITARY']}, // Charlie Time Zone
  1218. {'CAST', 28800, ['ANTARCTICA']}, // Casey Time
  1219. {'CAT', 7200, ['AFRICA']}, // Central Africa Time
  1220. {'CCT', 23400, ['INDIAN OCEAN']}, // Cocos Islands Time
  1221. {'CDT', -18000, ['NORTH AMERICA']}, // Central Daylight Time
  1222. {'CDT', -14400, ['CARIBBEAN']}, // Cuba Daylight Time
  1223. {'CEST', 7200, ['EUROPE', 'ANTARCTICA']}, // Central European Summer Time
  1224. {'CET', 3600, ['EUROPE', 'AFRICA']}, // Central European Time
  1225. {'CHADT', 49500, ['PACIFIC']}, // Chatham Island Daylight Time
  1226. {'CHAST', 45900, ['PACIFIC']}, // Chatham Island Standard Time
  1227. {'CHOST', 32400, ['ASIA']}, // Choibalsan Summer Time
  1228. {'CHOT', 28800, ['ASIA']}, // Choibalsan Time
  1229. {'ChST', 36000, ['PACIFIC']}, // Chamorro Standard Time
  1230. {'CHUT', 36000, ['PACIFIC']}, // Chuuk Time
  1231. {'CIDST', -14400, ['CARIBBEAN']}, // Cayman Islands Daylight Saving Time
  1232. {'CIST', -18000, ['CARIBBEAN']}, // Cayman Islands Standard Time
  1233. {'CKT', -36000, ['PACIFIC']}, // Cook Island Time
  1234. {'CLST', -10800, ['SOUTH AMERICA', 'ANTARCTICA']}, // Chile Summer Time
  1235. {'CLT', -14400, ['SOUTH AMERICA', 'ANTARCTICA']}, // Chile Standard Time
  1236. {'COT', -18000, ['SOUTH AMERICA']}, // Colombia Time
  1237. {'CST', -21600, ['NORTH AMERICA', 'CENTRAL AMERICA']}, // Central Standard Time
  1238. {'CST', 28800, ['ASIA']}, // China Standard Time
  1239. {'CST', -18000, ['CARIBBEAN']}, // Cuba Standard Time
  1240. {'CT', -21600, ['NORTH AMERICA', 'CENTRAL AMERICA']}, // Central Time
  1241. {'CVT', -3600, ['AFRICA']}, // Cape Verde Time
  1242. {'CXT', 25200, ['AUSTRALIA']}, // Christmas Island Time
  1243. {'D', 14400, ['MILITARY']}, // Delta Time Zone
  1244. {'DAVT', 25200, ['ANTARCTICA']}, // Davis Time
  1245. {'DDUT', 36000, ['ANTARCTICA']}, // Dumont-d'Urville Time
  1246. {'E', 18000, ['MILITARY']}, // Echo Time Zone
  1247. {'EASST', -18000, ['PACIFIC']}, // Easter Island Summer Time
  1248. {'EAST', -21600, ['PACIFIC']}, // Easter Island Standard Time
  1249. {'EAT', 10800, ['AFRICA', 'INDIAN OCEAN']}, // Eastern Africa Time
  1250. {'ECT', -18000, ['SOUTH AMERICA']}, // Ecuador Time
  1251. {'EDT', -14400, ['NORTH AMERICA', 'CARIBBEAN']}, // Eastern Daylight Time
  1252. {'EEST', 10800, ['EUROPE', 'ASIA']}, // Eastern European Summer Time
  1253. {'EET', 7200, ['EUROPE', 'ASIA', 'AFRICA']}, // Eastern European Time
  1254. {'EGST', 0, ['NORTH AMERICA']}, // Eastern Greenland Summer Time
  1255. {'EGT', -3600, ['NORTH AMERICA']}, // East Greenland Time
  1256. {'EST', -18000, ['NORTH AMERICA', 'CARIBBEAN', 'CENTRAL AMERICA']}, // Eastern Standard Time
  1257. {'ET', -18000, ['NORTH AMERICA', 'CARIBBEAN', 'CENTRAL AMERICA']}, // Eastern Time
  1258. {'F', 21600, ['MILITARY']}, // Foxtrot Time Zone
  1259. {'FET', 10800, ['EUROPE']}, // Further-Eastern European Time
  1260. {'FJST', 46800, ['PACIFIC']}, // Fiji Summer Time
  1261. {'FJT', 43200, ['PACIFIC']}, // Fiji Time
  1262. {'FKST', -10800, ['SOUTH AMERICA']}, // Falkland Islands Summer Time
  1263. {'FKT', -14400, ['SOUTH AMERICA']}, // Falkland Island Time
  1264. {'FNT', -7200, ['SOUTH AMERICA']}, // Fernando de Noronha Time
  1265. {'G', 25200, ['MILITARY']}, // Golf Time Zone
  1266. {'GALT', -21600, ['PACIFIC']}, // Galapagos Time
  1267. {'GAMT', -32400, ['PACIFIC']}, // Gambier Time
  1268. {'GET', 14400, ['ASIA']}, // Georgia Standard Time
  1269. {'GFT', -10800, ['SOUTH AMERICA']}, // French Guiana Time
  1270. {'GILT', 43200, ['PACIFIC']}, // Gilbert Island Time
  1271. {'GMT', 0, ['EUROPE', 'AFRICA', 'NORTH AMERICA', 'ANTARCTICA']}, // Greenwich Mean Time
  1272. {'GST', 14400, ['ASIA']}, // Gulf Standard Time
  1273. {'GST', -7200, ['SOUTH AMERICA']}, // South Georgia Time
  1274. {'GYT', -14400, ['SOUTH AMERICA']}, // Guyana Time
  1275. {'H', 28800, ['MILITARY']}, // Hotel Time Zone
  1276. {'HADT', -32400, ['NORTH AMERICA']}, // Hawaii-Aleutian Daylight Time
  1277. {'HAST', -36000, ['NORTH AMERICA', 'PACIFIC']}, // Hawaii-Aleutian Standard Time
  1278. {'HKT', 28800, ['ASIA']}, // Hong Kong Time
  1279. {'HOVST', 28800, ['ASIA']}, // Hovd Summer Time
  1280. {'HOVT', 25200, ['ASIA']}, // Hovd Time
  1281. {'I', 32400, ['MILITARY']}, // India Time Zone
  1282. {'ICT', 25200, ['ASIA']}, // Indochina Time
  1283. {'IDT', 10800, ['ISRAEL']}, // Israel Daylight Time; location was ASIA
  1284. {'IOT', 21600, ['INDIAN OCEAN']}, // Indian Chagos Time
  1285. {'IRDT', 16200, ['ASIA']}, // Iran Daylight Time
  1286. {'IRKST', 32400, ['ASIA']}, // Irkutsk Summer Time
  1287. {'IRKT', 28800, ['ASIA']}, // Irkutsk Time
  1288. {'IRST', 12600, ['ASIA']}, // Iran Standard Time
  1289. {'IST', 19800, ['ASIA']}, // India Standard Time
  1290. {'IST', 3600, ['EUROPE']}, // Irish Standard Time
  1291. {'IST', 7200, ['ISRAEL']}, // Israel Standard Time; location was ASIA
  1292. {'JST', 32400, ['ASIA']}, // Japan Standard Time
  1293. {'K', 36000, ['MILITARY']}, // Kilo Time Zone
  1294. {'KGT', 21600, ['ASIA']}, // Kyrgyzstan Time
  1295. {'KOST', 39600, ['PACIFIC']}, // Kosrae Time
  1296. {'KRAST', 28800, ['ASIA']}, // Krasnoyarsk Summer Time
  1297. {'KRAT', 25200, ['ASIA']}, // Krasnoyarsk Time
  1298. {'KST', 32400, ['ASIA']}, // Korea Standard Time
  1299. {'KUYT', 14400, ['EUROPE']}, // Kuybyshev Time
  1300. {'L', 39600, ['MILITARY']}, // Lima Time Zone
  1301. {'LHDT', 39600, ['AUSTRALIA']}, // Lord Howe Daylight Time
  1302. {'LHST', 37800, ['AUSTRALIA']}, // Lord Howe Standard Time
  1303. {'LINT', 50400, ['PACIFIC']}, // Line Islands Time
  1304. {'M', 43200, ['MILITARY']}, // Mike Time Zone
  1305. {'MAGST', 43200, ['ASIA']}, // Magadan Summer Time
  1306. {'MAGT', 39600, ['ASIA']}, // Magadan Time
  1307. {'MART', -34200, ['PACIFIC']}, // Marquesas Time
  1308. {'MAWT', 18000, ['ANTARCTICA']}, // Mawson Time
  1309. {'MDT', -21600, ['NORTH AMERICA']}, // Mountain Daylight Time
  1310. {'MHT', 43200, ['PACIFIC']}, // Marshall Islands Time
  1311. {'MMT', 23400, ['ASIA']}, // Myanmar Time
  1312. {'MSD', 14400, ['EUROPE']}, // Moscow Daylight Time
  1313. {'MSK', 10800, ['EUROPE', 'ASIA']}, // Moscow Standard Time
  1314. {'MST', -25200, ['NORTH AMERICA']}, // Mountain Standard Time
  1315. {'MT', -25200, ['NORTH AMERICA']}, // Mountain Time
  1316. {'MUT', 14400, ['AFRICA']}, // Mauritius Time
  1317. {'MVT', 18000, ['ASIA']}, // Maldives Time
  1318. {'MYT', 28800, ['ASIA']}, // Malaysia Time
  1319. {'N', -3600, ['MILITARY']}, // November Time Zone
  1320. {'NCT', 39600, ['PACIFIC']}, // New Caledonia Time
  1321. {'NDT', -9000, ['NORTH AMERICA']}, // Newfoundland Daylight Time
  1322. {'NFT', 39600, ['AUSTRALIA']}, // Norfolk Time
  1323. {'NOVST', 25200, ['ASIA']}, // Novosibirsk Summer Time
  1324. {'NOVT', 21600, ['ASIA']}, // Novosibirsk Time
  1325. {'NPT', 20700, ['ASIA']}, // Nepal Time
  1326. {'NRT', 43200, ['PACIFIC']}, // Nauru Time
  1327. {'NST', -12600, ['NORTH AMERICA']}, // Newfoundland Standard Time
  1328. {'NUT', -39600, ['PACIFIC']}, // Niue Time
  1329. {'NZDT', 46800, ['PACIFIC', 'ANTARCTICA']}, // New Zealand Daylight Time
  1330. {'NZST', 43200, ['PACIFIC', 'ANTARCTICA']}, // New Zealand Standard Time
  1331. {'O', -7200, ['MILITARY']}, // Oscar Time Zone
  1332. {'OMSST', 25200, ['ASIA']}, // Omsk Summer Time
  1333. {'OMST', 21600, ['ASIA']}, // Omsk Standard Time
  1334. {'ORAT', 18000, ['ASIA']}, // Oral Time
  1335. {'P', -10800, ['MILITARY']}, // Papa Time Zone
  1336. {'PDT', -25200, ['NORTH AMERICA']}, // Pacific Daylight Time
  1337. {'PET', -18000, ['SOUTH AMERICA']}, // Peru Time
  1338. {'PETST', 43200, ['ASIA']}, // Kamchatka Summer Time
  1339. {'PETT', 43200, ['ASIA']}, // Kamchatka Time
  1340. {'PGT', 36000, ['PACIFIC']}, // Papua New Guinea Time
  1341. {'PHOT', 46800, ['PACIFIC']}, // Phoenix Island Time
  1342. {'PHT', 28800, ['ASIA']}, // Philippine Time
  1343. {'PKT', 18000, ['ASIA']}, // Pakistan Standard Time
  1344. {'PMDT', -7200, ['NORTH AMERICA']}, // Pierre & Miquelon Daylight Time
  1345. {'PMST', -10800, ['NORTH AMERICA']}, // Pierre & Miquelon Standard Time
  1346. {'PONT', 39600, ['PACIFIC']}, // Pohnpei Standard Time
  1347. {'PST', -28800, ['NORTH AMERICA']}, // Pacific Standard Time
  1348. {'PST', -28800, ['PACIFIC']}, // Pitcairn Standard Time
  1349. {'PT', -28800, ['NORTH AMERICA']}, // Pacific Time
  1350. {'PWT', 32400, ['PACIFIC']}, // Palau Time
  1351. {'PYST', -10800, ['SOUTH AMERICA']}, // Paraguay Summer Time
  1352. {'PYT', -14400, ['SOUTH AMERICA']}, // Paraguay Time
  1353. {'PYT', 30600, ['ASIA']}, // Pyongyang Time
  1354. {'Q', -14400, ['MILITARY']}, // Quebec Time Zone
  1355. {'QYZT', 21600, ['ASIA']}, // Qyzylorda Time
  1356. {'R', -18000, ['MILITARY']}, // Romeo Time Zone
  1357. {'RET', 14400, ['AFRICA']}, // Reunion Time
  1358. {'ROTT', -10800, ['ANTARCTICA']}, // Rothera Time
  1359. {'S', -21600, ['MILITARY']}, // Sierra Time Zone
  1360. {'SAKT', 39600, ['ASIA']}, // Sakhalin Time
  1361. {'SAMT', 14400, ['EUROPE']}, // Samara Time
  1362. {'SAST', 7200, ['AFRICA']}, // South Africa Standard Time
  1363. {'SBT', 39600, ['PACIFIC']}, // Solomon Islands Time
  1364. {'SCT', 14400, ['AFRICA']}, // Seychelles Time
  1365. {'SGT', 28800, ['ASIA']}, // Singapore Time
  1366. {'SRET', 39600, ['ASIA']}, // Srednekolymsk Time
  1367. {'SRT', -10800, ['SOUTH AMERICA']}, // Suriname Time
  1368. {'SST', -39600, ['PACIFIC']}, // Samoa Standard Time
  1369. {'SYOT', 10800, ['ANTARCTICA']}, // Syowa Time
  1370. {'T', -25200, ['MILITARY']}, // Tango Time Zone
  1371. {'TAHT', -36000, ['PACIFIC']}, // Tahiti Time
  1372. {'TFT', 18000, ['INDIAN OCEAN']}, // French Southern and Antarctic Time
  1373. {'TJT', 18000, ['ASIA']}, // Tajikistan Time
  1374. {'TKT', 46800, ['PACIFIC']}, // Tokelau Time
  1375. {'TLT', 32400, ['ASIA']}, // East Timor Time
  1376. {'TMT', 18000, ['ASIA']}, // Turkmenistan Time
  1377. {'TOST', 50400, ['PACIFIC']}, // Tonga Summer Time
  1378. {'TOT', 46800, ['PACIFIC']}, // Tonga Time
  1379. {'TRT', 10800, ['ASIA', 'EUROPE']}, // Turkey Time
  1380. {'TVT', 43200, ['PACIFIC']}, // Tuvalu Time
  1381. {'U', -28800, ['MILITARY']}, // Uniform Time Zone
  1382. {'ULAST', 32400, ['ASIA']}, // Ulaanbaatar Summer Time
  1383. {'ULAT', 28800, ['ASIA']}, // Ulaanbaatar Time
  1384. {'UTC', 0, ['WORLDWIDE']}, // Coordinated Universal Time
  1385. {'UYST', -7200, ['SOUTH AMERICA']}, // Uruguay Summer Time
  1386. {'UYT', -10800, ['SOUTH AMERICA']}, // Uruguay Time
  1387. {'UZT', 18000, ['ASIA']}, // Uzbekistan Time
  1388. {'V', -32400, ['MILITARY']}, // Victor Time Zone
  1389. {'VET', -14400, ['SOUTH AMERICA']}, // Venezuelan Standard Time
  1390. {'VLAST', 39600, ['ASIA']}, // Vladivostok Summer Time
  1391. {'VLAT', 36000, ['ASIA']}, // Vladivostok Time
  1392. {'VOST', 21600, ['ANTARCTICA']}, // Vostok Time
  1393. {'VUT', 39600, ['PACIFIC']}, // Vanuatu Time
  1394. {'W', -36000, ['MILITARY']}, // Whiskey Time Zone
  1395. {'WAKT', 43200, ['PACIFIC']}, // Wake Time
  1396. {'WARST', -10800, ['SOUTH AMERICA']}, // Western Argentine Summer Time
  1397. {'WAST', 7200, ['AFRICA']}, // West Africa Summer Time
  1398. {'WAT', 3600, ['AFRICA']}, // West Africa Time
  1399. {'WEST', 3600, ['EUROPE', 'AFRICA']}, // Western European Summer Time
  1400. {'WET', 0, ['EUROPE', 'AFRICA']}, // Western European Time
  1401. {'WFT', 43200, ['PACIFIC']}, // Wallis and Futuna Time
  1402. {'WGST', -7200, ['NORTH AMERICA']}, // Western Greenland Summer Time
  1403. {'WGT', -10800, ['NORTH AMERICA']}, // West Greenland Time
  1404. {'WIB', 25200, ['ASIA']}, // Western Indonesian Time
  1405. {'WIT', 32400, ['ASIA']}, // Eastern Indonesian Time
  1406. {'WITA', 28800, ['ASIA']}, // Central Indonesian Time
  1407. {'WST', 50400, ['PACIFIC']}, // West Samoa Time
  1408. {'WST', 3600, ['AFRICA']}, // Western Sahara Summer Time
  1409. {'WT', 0, ['AFRICA']}, // Western Sahara Standard Time
  1410. {'X', -39600, ['MILITARY']}, // X-ray Time Zone
  1411. {'Y', -43200, ['MILITARY']}, // Yankee Time Zone
  1412. {'YAKST', 36000, ['ASIA']}, // Yakutsk Summer Time
  1413. {'YAKT', 32400, ['ASIA']}, // Yakutsk Time
  1414. {'YAPT', 36000, ['PACIFIC']}, // Yap Time
  1415. {'YEKST', 21600, ['ASIA']}, // Yekaterinburg Summer Time
  1416. {'YEKT', 18000, ['ASIA']}, // Yekaterinburg Time
  1417. {'Z', 0, ['MILITARY']} // Zulu Time Zone
  1418. ],
  1419. TZDataLayout
  1420. );
  1421. /**
  1422. * Return a list of unique time zone abbreviations from the hardcoded dataset.
  1423. * All abbreviations are in uppercase.
  1424. *
  1425. * @return A new DATASET({STRING5 tzAbbrev}) containing the
  1426. * unique time zone abbreviations.
  1427. */
  1428. EXPORT UniqueTZAbbreviations() := FUNCTION
  1429. RETURN TABLE(TZ_DATA, {tzAbbrev}, tzAbbrev);
  1430. END;
  1431. /**
  1432. * Return a list of unique location names from the hardcoded dataset.
  1433. * All names are in uppercase.
  1434. *
  1435. * @return A new DATASET({STRING name}) containing the
  1436. * unique location names.
  1437. */
  1438. EXPORT UniqueTZLocations() := FUNCTION
  1439. NameRec := {STRING name};
  1440. // Gather all locations as a collection of child datasets
  1441. collectedNames := PROJECT
  1442. (
  1443. TZ_DATA,
  1444. TRANSFORM
  1445. (
  1446. {
  1447. DATASET(NameRec) names
  1448. },
  1449. SELF.names := DATASET(LEFT.locations, NameRec)
  1450. )
  1451. );
  1452. // Flatten collected names, so there is one name per record
  1453. flattenedNames := NORMALIZE
  1454. (
  1455. collectedNames,
  1456. LEFT.names,
  1457. TRANSFORM
  1458. (
  1459. NameRec,
  1460. SELF.name := RIGHT.name
  1461. )
  1462. );
  1463. // Deduplicate the names
  1464. ds3 := TABLE(flattenedNames, {name}, name);
  1465. RETURN ds3;
  1466. END;
  1467. /**
  1468. * Finds the time zone records for a given location.
  1469. *
  1470. * @param location The name of the location to search for; must be a
  1471. * non-empty uppercase string; REQUIRED
  1472. * @return A new DATASET(STRING5 tzAbbrev, INTEGER4 secondsOffset)
  1473. * containing the found records
  1474. * @see FindTZData
  1475. */
  1476. EXPORT TZDataForLocation(STRING location) := FUNCTION
  1477. ResultRec := RECORD
  1478. STRING5 tzAbbrev;
  1479. INTEGER4 secondsOffset;
  1480. END;
  1481. foundRecs := TZ_DATA(location IN locations);
  1482. foundTrimmed := PROJECT
  1483. (
  1484. foundRecs,
  1485. TRANSFORM
  1486. (
  1487. ResultRec,
  1488. SELF := LEFT
  1489. )
  1490. );
  1491. RETURN foundTrimmed;
  1492. END;
  1493. /**
  1494. * Finds the time zone records for a given abbreviation and optional location.
  1495. * A location should be provided as a method of differentiation if the
  1496. * abbreviation has duplicate entries.
  1497. *
  1498. * @param timeZoneAbbrev The time zone abbreviation to search for;
  1499. * must be a non-empty uppercase string; REQUIRED
  1500. * @param location The name of the location to search for; if a
  1501. * location is not provided or is an empty string,
  1502. * all records matching only the abbreviation are
  1503. * returned; OPTIONAL, defaults to an empty string
  1504. * @return A new DATASET(TZDataLayout) containing the found
  1505. * records
  1506. * @see TZDataForLocation
  1507. */
  1508. EXPORT DATASET(TZDataLayout) FindTZData(STRING5 timeZoneAbbrev, STRING location = '') := FUNCTION
  1509. RETURN TZ_DATA(tzAbbrev = timeZoneAbbrev AND (location = '' OR location IN locations));
  1510. END;
  1511. /**
  1512. * Compute the offset, in seconds, between two different time zones. Each
  1513. * time zone is designated by a required time zone abbreviation and an
  1514. * optional location name. The result is the number of seconds (which can be
  1515. * either positive or negative) that would have to be applied to a time when
  1516. * traveling from 'fromTimeZoneAbbrev' to 'toTimeZoneAbbrev'.
  1517. *
  1518. * Be aware that some time zones explicitly represent daylight savings time, so
  1519. * it is entirely possible to change not only time zones but DST observance as
  1520. * well in a single call.
  1521. *
  1522. * @param fromTimeZoneAbbrev The time zone abbreviation designated as the
  1523. * starting point; must be a non-empty uppercase
  1524. * string; REQUIRED
  1525. * @param toTimeZoneAbbrev The time zone abbreviation designated as the
  1526. * ending point; must be a non-empty uppercase
  1527. * string; REQUIRED
  1528. * @param fromLocation The name of the location that goes along with
  1529. * fromTimeZoneAbbrev; if a location is not
  1530. * provided or is an empty string, the first
  1531. * record matching fromTimeZoneAbbrev will be used;
  1532. * OPTIONAL, defaults to an empty string
  1533. * @param toLocation The name of the location that goes along with
  1534. * toTimeZoneAbbrev; if a location is not
  1535. * provided or is an empty string, the first
  1536. * record matching toTimeZoneAbbrev will be used;
  1537. * OPTIONAL, defaults to an empty string
  1538. * @return The number of seconds between the two time
  1539. * zones; will return zero if either time zone
  1540. * cannot be found
  1541. * @see AdjustTimeTZ
  1542. */
  1543. EXPORT INTEGER4 SecondsBetweenTZ(STRING5 fromTimeZoneAbbrev,
  1544. STRING5 toTimeZoneAbbrev,
  1545. STRING fromLocation = '',
  1546. STRING toLocation = '') := FUNCTION
  1547. fromTZ := FindTZData(fromTimeZoneAbbrev, fromLocation);
  1548. toTZ := FindTZData(toTimeZoneAbbrev, toLocation);
  1549. hasTZInfo := EXISTS(fromTZ) AND EXISTS(toTZ);
  1550. fromSecondsOffset := fromTZ[1].secondsOffset;
  1551. toSecondsOffset := toTZ[1].secondsOffset;
  1552. RETURN IF
  1553. (
  1554. hasTZInfo,
  1555. toSecondsOffset - fromSecondsOffset,
  1556. 0
  1557. );
  1558. END;
  1559. /**
  1560. * Adjust a given Time_t time value for another time zone. Both the given time
  1561. * and the destination time zone are designated by a required time zone
  1562. * abbreviation and an optional location name.
  1563. *
  1564. * @param time The time value to adjust; REQUIRED
  1565. * @param fromTimeZoneAbbrev The time zone abbreviation that the time
  1566. * value is assumed to be within; must be a
  1567. * non-empty uppercase string; REQUIRED
  1568. * @param toTimeZoneAbbrev The time zone abbreviation designated as the
  1569. * ending point; must be a non-empty uppercase
  1570. * string; REQUIRED
  1571. * @param fromLocation The name of the location that goes along with
  1572. * fromTimeZoneAbbrev; if a location is not
  1573. * provided or is an empty string, the first
  1574. * record matching fromTimeZoneAbbrev will be used;
  1575. * OPTIONAL, defaults to an empty string
  1576. * @param toLocation The name of the location that goes along with
  1577. * toTimeZoneAbbrev; if a location is not
  1578. * provided or is an empty string, the first
  1579. * record matching toTimeZoneAbbrev will be used;
  1580. * OPTIONAL, defaults to an empty string
  1581. * @return The given time value adjusted by the difference
  1582. * between the two given time zones; if either
  1583. * time zone cannot be found then the original
  1584. * time value will be returned unchanged
  1585. * @see SecondsBetweenTZ
  1586. */
  1587. EXPORT Time_t AdjustTimeTZ(Time_t time,
  1588. STRING5 fromTimeZoneAbbrev,
  1589. STRING5 toTimeZoneAbbrev,
  1590. STRING fromLocation = '',
  1591. STRING toLocation = '') := FUNCTION
  1592. diff := SecondsBetweenTZ(fromTimeZoneAbbrev, toTimeZoneAbbrev, fromLocation, toLocation);
  1593. newTime := AdjustTime(time, second_delta := diff);
  1594. RETURN newTime;
  1595. END;
  1596. /**
  1597. * Converts a UTC time to a time designated by a time zone abbreviation and
  1598. * optional location.
  1599. *
  1600. * @param utcTime The UTC time value to adjust; REQUIRED
  1601. * @param toTimeZoneAbbrev The time zone abbreviation designated as the
  1602. * ending point; must be a non-empty uppercase
  1603. * string; REQUIRED
  1604. * @param toLocation The name of the location that goes along with
  1605. * toTimeZoneAbbrev; if a location is not
  1606. * provided or is an empty string, the first
  1607. * record matching toTimeZoneAbbrev will be used;
  1608. * OPTIONAL, defaults to an empty string
  1609. * @return The given UTC time value adjusted to the time
  1610. * zone defined by toTimeZoneAbbrev and toLocation;
  1611. * if the time zone cannot be found then the
  1612. * original time value will be returned unchanged
  1613. * @see AdjustTimeTZ
  1614. * @see ToUTCTime
  1615. */
  1616. EXPORT Time_t ToLocalTime(Time_t utcTime,
  1617. STRING5 toTimeZoneAbbrev,
  1618. STRING toLocation = '') := FUNCTION
  1619. RETURN AdjustTimeTZ(utcTime, 'UTC', toTimeZoneAbbrev, toLocation := toLocation);
  1620. END;
  1621. /**
  1622. * Converts a local time, defined with a time zone abbreviation and optional
  1623. * location, to a UTC time.
  1624. *
  1625. * @param localTime The time value to adjust; REQUIRED
  1626. * @param fromTimeZoneAbbrev The time zone abbreviation that the localTime
  1627. * value is assumed to be within; must be a
  1628. * non-empty uppercase string; REQUIRED
  1629. * @param fromLocation The name of the location that goes along with
  1630. * fromTimeZoneAbbrev; if a location is not
  1631. * provided or is an empty string, the first
  1632. * record matching fromTimeZoneAbbrev will be used;
  1633. * OPTIONAL, defaults to an empty string
  1634. * @return The given local time value adjusted to UTC time;
  1635. * if the given time zone cannot be found then the
  1636. * original UTC time value will be returned
  1637. * unchanged
  1638. * @see AdjustTimeTZ
  1639. * @see ToLocalTime
  1640. */
  1641. EXPORT Time_t ToUTCTime(Time_t localTime,
  1642. STRING5 fromTimeZoneAbbrev,
  1643. STRING fromLocation = '') := FUNCTION
  1644. RETURN AdjustTimeTZ(localTime, fromTimeZoneAbbrev, 'UTC', fromLocation := fromLocation);
  1645. END;
  1646. /**
  1647. * Given a dataset that contains a time zone abbreviation and optional location,
  1648. * this function macro appends four new attributes to the dataset that contain
  1649. * useful information for translating a time value into another time zone.
  1650. * This could be useful as an ETL step where time data is made common in
  1651. * respect to one particular time zone (e.g. UTC).
  1652. *
  1653. * The actions within this function macro are conceptually similar to
  1654. * SecondsBetweenTZ() but applied to an entire dataset, and somewhat more
  1655. * efficiently.
  1656. *
  1657. * Note: In order for this function macro to execute correctly, the calling
  1658. * code must import the Std library.
  1659. *
  1660. * @param inFile The dataset to process; REQUIRED
  1661. * @param timeZoneAbbrevField The attribute within inFile that contains
  1662. * the time zone abbreviation to use for matching;
  1663. * the values in this attribute should be in
  1664. * uppercase; this is not a string; REQUIRED
  1665. * @param newOffsetField The attribute that will be appended to inFile
  1666. * and will contain the number of seconds offset
  1667. * from UTC; this is not a string; REQUIRED
  1668. * @param fromLocationField The attribute within inFile that contains the
  1669. * time zone location for the time zone cited by
  1670. * timeZoneAbbrevField; this is not a string;
  1671. * OPTIONAL, defaults to a null value (indicating
  1672. * that there is no time zone location attribute)
  1673. * @param toTimeZoneAbbrev The 'to' time zone abbreviation to use for all
  1674. * calculations, as a string; OPTIONAL, defaults
  1675. * to 'UTC'
  1676. * @param toLocation The name of the location that goes along with
  1677. * toTimeZoneAbbrev; if a location is not
  1678. * provided or is an empty string, the first
  1679. * record matching toTimeZoneAbbrev will be used;
  1680. * OPTIONAL, defaults to an empty string
  1681. * @return A new dataset with the same record definition
  1682. * as inFile but with four new attributes added;
  1683. * the new attributes are named based on the name
  1684. * given as the newOffsetField attribute:
  1685. * INTEGER4 <newOffsetField> // Offset, in seconds, between original time zone and toTimeZoneAbbrev
  1686. * BOOLEAN <newOffsetField>_is_valid // TRUE if <newOffsetField> contains a valid value
  1687. * STRING5 <newOffsetField>_tz // The value of toTimeZoneAbbrev
  1688. * STRING15 <newOffsetField>_location // The time zone location for <newOffsetField>_tz
  1689. * If <newOffsetField>_is_valid is FALSE then
  1690. * <newOffsetField> will be zero.
  1691. * @see AppendTZAdjustedTime
  1692. *
  1693. * Examples:
  1694. *
  1695. * ds := DATASET
  1696. * (
  1697. * [
  1698. * {120000, 'CT'},
  1699. * {120000, 'ET'}
  1700. * ],
  1701. * {Std.Date.Time_t time, STRING tz}
  1702. * );
  1703. *
  1704. * utcOffsetDS := Std.Date.TimeZone.AppendTZOffset(ds, tz, seconds_to_utc);
  1705. * OUTPUT(utcOffsetDS, NAMED('offset_to_utc_result'));
  1706. *
  1707. * ptOffsetDS := Std.Date.TimeZone.AppendTZOffset
  1708. * (
  1709. * ds,
  1710. * tz,
  1711. * seconds_to_pacific_time,
  1712. * toTimeZoneAbbrev := 'PT',
  1713. * toLocation := 'NORTH AMERICA'
  1714. * );
  1715. * OUTPUT(ptOffsetDS, NAMED('offset_to_pacific_time_result'));
  1716. */
  1717. EXPORT AppendTZOffset(inFile,
  1718. timeZoneAbbrevField,
  1719. newOffsetField,
  1720. fromLocationField = '',
  1721. toTimeZoneAbbrev = '\'UTC\'',
  1722. toLocation = '\'\'') := FUNCTIONMACRO
  1723. // Find the destination time zone information just once
  1724. #UNIQUENAME(destOffsetDS);
  1725. LOCAL %destOffsetDS% := Std.Date.TimeZone.FindTZData(toTimeZoneAbbrev, toLocation);
  1726. #UNIQUENAME(destOffsetFound);
  1727. LOCAL %destOffsetFound% := EXISTS(%destOffsetDS%);
  1728. #UNIQUENAME(destLocation);
  1729. LOCAL %destLocation% := IF(toLocation != '', toLocation, %destOffsetDS%[1].locations[1]);
  1730. #UNIQUENAME(destOffset);
  1731. LOCAL %destOffset% := %destOffsetDS%[1].secondsOffset;
  1732. RETURN JOIN
  1733. (
  1734. inFile,
  1735. Std.Date.TimeZone.TZ_DATA,
  1736. LEFT.timeZoneAbbrevField = RIGHT.tzAbbrev
  1737. #IF(#TEXT(fromLocationField) != '')
  1738. AND LEFT.fromLocationField IN RIGHT.locations
  1739. #END
  1740. AND %destOffsetFound%,
  1741. TRANSFORM
  1742. (
  1743. {
  1744. RECORDOF(inFile),
  1745. INTEGER4 newOffsetField,
  1746. BOOLEAN #EXPAND(#TEXT(newOffsetField) + '_is_valid'),
  1747. STRING5 #EXPAND(#TEXT(newOffsetField) + '_tz'),
  1748. STRING15 #EXPAND(#TEXT(newOffsetField) + '_location')
  1749. },
  1750. wasFound := RIGHT.tzAbbrev != '';
  1751. SELF.newOffsetField := IF(wasFound, %destOffset% - RIGHT.secondsOffset, 0),
  1752. SELF.#EXPAND(#TEXT(newOffsetField) + '_is_valid') := wasFound,
  1753. SELF.#EXPAND(#TEXT(newOffsetField) + '_tz') := toTimeZoneAbbrev,
  1754. SELF.#EXPAND(#TEXT(newOffsetField) + '_location') := %destLocation%,
  1755. SELF := LEFT
  1756. ),
  1757. LEFT OUTER, LOOKUP
  1758. );
  1759. ENDMACRO;
  1760. /**
  1761. * Given a dataset that contains a time (in Time_t format), a time zone
  1762. * abbreviation, and an optional time zone location, this function macro
  1763. * appends four new attributes to the dataset: A new Time_t attribute
  1764. * containing the original time expressed in a different time zone, and three
  1765. * attributes providing information regarding that destination time zone and
  1766. * the validity of the translation. This could be useful as an ETL step where
  1767. * time data is made common in respect to one particular time zone (e.g. UTC).
  1768. *
  1769. * The actions within this function macro are conceptually similar to
  1770. * AdjustTimeTZ() but applied to an entire dataset, and somewhat more
  1771. * efficiently.
  1772. *
  1773. * Note: In order for this function macro to execute correctly, the calling
  1774. * code must import the Std library.
  1775. *
  1776. * @param inFile The dataset to process; REQUIRED
  1777. * @param timeField The attribute within inFile that contains a
  1778. * time represented in Time_t format; this is not
  1779. * a string; REQUIRED
  1780. * @param timeZoneAbbrevField The attribute within inFile that contains
  1781. * the time zone abbreviation to use for matching;
  1782. * the values in this attribute should be in
  1783. * uppercase; this is not a string; REQUIRED
  1784. * @param newTimeField The attribute that will be appended to inFile
  1785. * and will contain the adjusted value of timeField;
  1786. * this is not a string; REQUIRED
  1787. * @param fromLocationField The attribute within inFile that contains the
  1788. * time zone location for the time zone cited by
  1789. * timeZoneAbbrevField; this is not a string;
  1790. * OPTIONAL, defaults to a null value (indicating
  1791. * that there is no time zone location attribute)
  1792. * @param toTimeZoneAbbrev The 'to' time zone abbreviation to use for all
  1793. * calculations, as a string; OPTIONAL, defaults
  1794. * to 'UTC'
  1795. * @param toLocation The name of the location that goes along with
  1796. * toTimeZoneAbbrev; if a location is not
  1797. * provided or is an empty string, the first
  1798. * record matching toTimeZoneAbbrev will be used;
  1799. * OPTIONAL, defaults to an empty string
  1800. * @return A new dataset with the same record definition
  1801. * as inFile but with four new attributes added;
  1802. * the new attributes are named based on the name
  1803. * given as the newOffsetField attribute:
  1804. * Std.Date.Time_t <newOffsetField> // Value of timeField expressed in new time zone
  1805. * BOOLEAN <newOffsetField>_is_valid // TRUE if <newOffsetField> contains a valid value
  1806. * STRING5 <newOffsetField>_tz // The value of toTimeZoneAbbrev
  1807. * STRING15 <newOffsetField>_location // The time zone location for <newOffsetField>_tz
  1808. * If <newOffsetField>_is_valid is FALSE then
  1809. * <newOffsetField> will have the same value as
  1810. * timeField.
  1811. * @see AppendTZOffset
  1812. *
  1813. * Example:
  1814. *
  1815. * ds := DATASET
  1816. * (
  1817. * [
  1818. * {120000, 'CT'},
  1819. * {120000, 'ET'}
  1820. * ],
  1821. * {Std.Date.Time_t time, STRING tz}
  1822. * );
  1823. *
  1824. * utcRewriteDS := Std.Date.TimeZone.AppendTZAdjustedTime(ds, time, tz, utc_time);
  1825. * OUTPUT(utcRewriteDS, NAMED('utc_result'));
  1826. *
  1827. * ptRewriteDS := Std.Date.TimeZone.AppendTZAdjustedTime
  1828. * (
  1829. * ds,
  1830. * time,
  1831. * tz,
  1832. * pacific_time,
  1833. * toTimeZoneAbbrev := 'PT',
  1834. * toLocation := 'NORTH AMERICA'
  1835. * );
  1836. * OUTPUT(ptRewriteDS, NAMED('pacific_time_result'));
  1837. */
  1838. EXPORT AppendTZAdjustedTime(inFile,
  1839. timeField,
  1840. timeZoneAbbrevField,
  1841. newTimeField,
  1842. fromLocationField = '',
  1843. toTimeZoneAbbrev = '\'UTC\'',
  1844. toLocation = '\'\'') := FUNCTIONMACRO
  1845. // Find the destination time zone information just once
  1846. #UNIQUENAME(destOffsetDS);
  1847. LOCAL %destOffsetDS% := Std.Date.TimeZone.FindTZData(toTimeZoneAbbrev, toLocation);
  1848. #UNIQUENAME(destOffsetFound);
  1849. LOCAL %destOffsetFound% := EXISTS(%destOffsetDS%);
  1850. #UNIQUENAME(destLocation);
  1851. LOCAL %destLocation% := IF(toLocation != '', toLocation, %destOffsetDS%[1].locations[1]);
  1852. #UNIQUENAME(destOffset);
  1853. LOCAL %destOffset% := %destOffsetDS%[1].secondsOffset;
  1854. RETURN JOIN
  1855. (
  1856. inFile,
  1857. Std.Date.TimeZone.TZ_DATA,
  1858. LEFT.timeZoneAbbrevField = RIGHT.tzAbbrev
  1859. #IF(#TEXT(fromLocationField) != '')
  1860. AND LEFT.fromLocationField IN RIGHT.locations
  1861. #END
  1862. AND %destOffsetFound%,
  1863. TRANSFORM
  1864. (
  1865. {
  1866. RECORDOF(inFile),
  1867. Std.Date.Time_t newTimeField,
  1868. BOOLEAN #EXPAND(#TEXT(newTimeField) + '_is_valid'),
  1869. STRING5 #EXPAND(#TEXT(newTimeField) + '_tz'),
  1870. STRING15 #EXPAND(#TEXT(newTimeField) + '_location')
  1871. },
  1872. wasFound := RIGHT.tzAbbrev != '';
  1873. SELF.newTimeField := IF
  1874. (
  1875. wasFound,
  1876. Std.Date.AdjustTime(LEFT.timeField, second_delta := (%destOffset% - RIGHT.secondsOffset)),
  1877. LEFT.timeField
  1878. ),
  1879. SELF.#EXPAND(#TEXT(newTimeField) + '_is_valid') := wasFound,
  1880. SELF.#EXPAND(#TEXT(newTimeField) + '_tz') := toTimeZoneAbbrev,
  1881. SELF.#EXPAND(#TEXT(newTimeField) + '_location') := %destLocation%,
  1882. SELF := LEFT
  1883. ),
  1884. LEFT OUTER, LOOKUP
  1885. );
  1886. ENDMACRO;
  1887. END; // TimeZone Module
  1888. END; // Date Module