{-# LANGUAGE DeriveGeneric #-}modulePenny.Lincoln.Bits.DateTime(TimeZoneOffset(offsetToMins),minsToOffset,noOffset,Hours(unHours),intToHours,zeroHours,Minutes(unMinutes),intToMinutes,zeroMinutes,Seconds(unSeconds),intToSeconds,zeroSeconds,midnight,DateTime(..),dateTimeMidnightUTC,toUTC,toZonedTime,fromZonedTime,sameInstant,showDateTime)whereimportqualifiedControl.MonadasMimportqualifiedData.TimeasTimportqualifiedData.BinaryasBimportData.Binary(get,put)importGHC.Generics(Generic)importqualifiedPenny.Lincoln.EquivalentasEv-- | The number of minutes that this timezone is offset from UTC. Can-- be positive, negative, or zero.newtypeTimeZoneOffset=TimeZoneOffset{offsetToMins::Int}deriving(Eq,Ord,Show,Generic)instanceB.BinaryTimeZoneOffset-- | Convert minutes to a time zone offset. I'm having a hard time-- deciding whether to be liberal or strict in what to accept-- here. Currently it is somewhat strict in that it will fail if-- absolute value is greater than 840 minutes; currently the article-- at http://en.wikipedia.org/wiki/List_of_time_zones_by_UTC_offset-- says there is no offset greater than 14 hours, or 840 minutes.minsToOffset::Int->MaybeTimeZoneOffsetminsToOffsetm=ifabsm>840thenNothingelseJust$TimeZoneOffsetmnoOffset::TimeZoneOffsetnoOffset=TimeZoneOffset0newtypeHours=Hours{unHours::Int}deriving(Eq,Ord,Show,Generic)instanceB.BinaryHoursnewtypeMinutes=Minutes{unMinutes::Int}deriving(Eq,Ord,Show,Generic)instanceB.BinaryMinutesnewtypeSeconds=Seconds{unSeconds::Int}deriving(Eq,Ord,Show,Generic)instanceB.BinarySeconds-- | succeeds if 0 <= x < 24intToHours::Int->MaybeHoursintToHoursh=ifh>=0&&h<24thenJust.Hours$helseNothingzeroHours::HourszeroHours=Hours0-- | succeeds if 0 <= x < 60intToMinutes::Int->MaybeMinutesintToMinutesm=ifm>=0&&m<60thenJust.Minutes$melseNothingzeroMinutes::MinuteszeroMinutes=Minutes0-- | succeeds if 0 <= x < 61 (to allow for leap seconds)intToSeconds::Int->MaybeSecondsintToSecondss=ifs>=0&&s<61thenJust.Seconds$selseNothingzeroSeconds::SecondszeroSeconds=Seconds0midnight::(Hours,Minutes,Seconds)midnight=(zeroHours,zeroMinutes,zeroSeconds)-- | A DateTime is a a local date and time, along with a time zone-- offset. The Eq and Ord instances are derived; therefore, two-- DateTime instances will not be equivalent if the time zone offsets-- are different, even if they are the same instant. To compare one-- DateTime to another, you probably want to use 'toUTC' and compare-- those. To see if two DateTime are the same instant, use-- 'sameInstant'.dataDateTime=DateTime{day::T.Day,hours::Hours,minutes::Minutes,seconds::Seconds,timeZone::TimeZoneOffset}deriving(Eq,Ord,Show)instanceB.BinaryDateTimewhereget=M.liftM5DateTime(fmapT.ModifiedJulianDayB.get)getgetgetgetput(DateTimedhmst)=put(T.toModifiedJulianDayd)>>puth>>putm>>puts>>puttdateTimeMidnightUTC::T.Day->DateTimedateTimeMidnightUTCd=DateTimedhmszwhere(h,m,s)=midnightz=noOffsettoZonedTime::DateTime->T.ZonedTimetoZonedTimedt=T.ZonedTimelttzwhered=daydtlt=T.LocalTimedtodtod=T.TimeOfDay(unHours.hours$dt)(unMinutes.minutes$dt)(fromIntegral.unSeconds.seconds$dt)tz=T.TimeZone(offsetToMins.timeZone$dt)False""fromZonedTime::T.ZonedTime->MaybeDateTimefromZonedTime(T.ZonedTime(T.LocalTimedtod)tz)=doh<-intToHours.T.todHour$todm<-intToMinutes.T.todMin$todlet(sWhole,_)=properFraction.T.todSec$tods<-intToSecondssWholetzo<-minsToOffset.T.timeZoneMinutes$tzreturn$DateTimedhmstzotoUTC::DateTime->T.UTCTimetoUTCdt=T.localTimeToUTCtzltwheretz=T.minutesToTimeZone.offsetToMins.timeZone$dttod=T.TimeOfDay(unHoursh)(unMinutesm)(fromIntegral.unSeconds$s)DateTimedhms_=dtlt=T.LocalTimedtod-- | Are these DateTimes the same instant in time, after adjusting for-- local timezones?sameInstant::DateTime->DateTime->BoolsameInstantt1t2=toUTCt1==toUTCt2instanceEv.EquivalentDateTimewhereequivalent=sameInstantcompareEvxy=compare(toUTCx)(toUTCy)-- | Shows a DateTime in a pretty way.showDateTime::DateTime->StringshowDateTime(DateTimedhmstz)=ds++" "++hmss++" "++showOffsetwhereds=showdhmss=hs++":"++ms++":"++sshs=pad0.show.unHours$hms=pad0.show.unMinutes$mss=pad0.show.unSeconds$spad0str=iflengthstr<2then'0':strelsestrshowOffset=let(zoneHr,zoneMin)=abs(offsetToMinstz)`divMod`60sign=ifoffsetToMinstz<0then"-"else"+"insign++pad0(showzoneHr)++pad0(showzoneMin)