Build Status Coverage Status Codacy Badge License Maven Central

wen

'Wen considered the nature of time and understood that the universe is, instant by instant, recreated anew. Therefore, he understood, there is in truth no past, only a memory of the past. Blink your eyes, and the world you see next did not exist when you closed them. Therefore, he said, the only appropriate state of the mind is surprise. The only appropriate state of the heart is joy. The sky you see now, you have never seen before. The perfect moment is now. Be glad of it.'

― Terry Pratchett, Thief of Time

Date and time types and instances

Getting Started

Wen is available for Scala 2.12. You can add wen to your sbt project by adding the following to your build.sbt:

libraryDependencies += "dev.mlopes" %% "wen" % "1.0.0-M1"
libraryDependencies += "dev.mlopes" %% "wen-cats" % "1.0.0-M1" // For cats instances
libraryDependencies += "dev.mlopes" %% "wen-circe" % "1.0.0-M1" // For circe encoders and decoders

What is Wen?

Wen provides types to safely represent months, weekdays, years, epoch, hours, minutes, seconds and milliseconds, as well as instances of the Order, Eq and Show cats type classes.

Should cats ever support the Enum and Bounded type classes, and we should implement those as well, for the relevant types.

What Wen isn't?

Wen does not aim for date/time related functionality like the one provided by the java.time. Its purpose is to provide stateless representations for date/time data types.

Types

Wen provides types to represent date/time components, as well as types for full time and date representations, and auxiliary refined types for numeric representation of date/time components.

Date/Time Components

Day

Constructors
Day(day: NumericDay): Day
Day.fromInt(day: Int): Option[Day]
Instances
Order[Day]
Eq[Day]
Show[Day]

Usage

import wen.types._
import eu.timepit.refined.{W, refineMV}
import eu.timepit.refined.numeric.Interval

val day = Day.fromInt(31)
// day: Option[wen.types.Day] = Some(Day(31))

val notDay = Day.fromInt(32)
// notDay: Option[wen.types.Day] = None

// You need to use refineV when refining non-literal values
val refinedDay = Day(refineMV[Interval.Closed[W.`1`.T, W.`31`.T]](22))
// refinedDay: wen.types.Day = Day(22)

import eu.timepit.refined.auto._


// This works because we add the type annotation to `autoRefinedDay`
// so Scala infers that we're using the constructor
// Day(numericDay: NumericDay): Day
// refined.auto provides the necessary implicit conversion from Int.
val autoRefinedDay: Day = Day(25)
// autoRefinedDay: wen.types.Day = Day(25)

Because instances of cats's Eq, Order and Show are available, we can also do the following:

import cats.implicits._ // for cats syntax
import wen.implicits._ // for wen's instances

refinedDay.show
// res0: String = 22

autoRefinedDay >= refinedDay
// res1: Boolean = true

refinedDay =!= autoRefinedDay
// res2: Boolean = true

Month

Constructors
January
February
March
April
May
June
July
August
September
October
November
December
Month(numericMonth: NumericMonth): Month
Month.fromInt(month: Int): Option[Month]
Month.fromString(month: String): Option[Month]
Instances
Order[Month]
Eq[Month]
Show[Month]

Members

Month.asInt: Int

Returns the ordinal number of a Month, starting at 1 for January and ending in 12 for December.

Usage

import wen.types._
import eu.timepit.refined.{W, refineMV}
import eu.timepit.refined.numeric.Interval

val month: Month = December
// month: wen.types.Month = December

val monthFromInt = Month.fromInt(7)
// monthFromInt: Option[wen.types.Month] = Some(July)

val monthFromString = Month.fromString("July")
// monthFromString: Option[wen.types.Month] = Some(July)

val notMonth = Month.fromInt(24)
// notMonth: Option[wen.types.Month] = None

val notMonthFromString = Month.fromString("Grune")
// notMonthFromString: Option[wen.types.Month] = None

// You need to use refineV when refining non-literal values
val refinedMonth = Month(refineMV[Interval.Closed[W.`1`.T, W.`12`.T]](4))
// refinedMonth: wen.types.Month = April

val monthInt = June.asInt
// monthInt: Int = 6

import eu.timepit.refined.auto._

val autoRefinedMonth: Month =  Month(8)
// autoRefinedMonth: wen.types.Month = August

Because instances of cats's Eq, Order and Show are available, we can also do the following:

import cats.implicits._ // for cats syntax
import wen.implicits._ // for wen's instances

refinedMonth.show
// res0: String = April

refinedMonth > autoRefinedMonth
// res1: Boolean = false

refinedMonth === autoRefinedMonth
// res2: Boolean = false

// Note that we have to specify the type Month so that Scala doesn't infer the type as July
(July : Month).show
// res3: String = July

Year

Constructors
Year(year: NumericYear): Year
Year(year: NumericYear, epoch: Epoch): Year
Year.fromInt(year: Int): Option[Year]
Year.fromIntWithEpoch(year: Int, epoch: Epoch): Option[Year]
Instances
Order[Year]
Eq[Year]
Show[Year]

Usage

import wen.types._
import eu.timepit.refined.{W, refineMV}
import eu.timepit.refined.numeric.{Interval, Positive}

val adYear = Year.fromIntWithEpoch(2019, AD)
// adYear: Option[wen.types.Year] = Some(Year(2019,AD))

val yearWithDefaultEpoch1 = Year.fromInt(2012)
// yearWithDefaultEpoch1: Option[wen.types.Year] = Some(Year(2012,AD))

val bcYear = Year.fromIntWithEpoch(19, BC)
// bcYear: Option[wen.types.Year] = Some(Year(19,BC))

val notYear = Year.fromIntWithEpoch(-21, AD)
// notYear: Option[wen.types.Year] = None

// You need to use refineV when refining non-literal values
val refinedYear = Year(refineMV[Positive](2019), AD)
// refinedYear: wen.types.Year = Year(2019,AD)

val yearWithDefaultEpoch = Year(refineMV[Positive](2011))
// yearWithDefaultEpoch: wen.types.Year = Year(2011,AD)

import eu.timepit.refined.auto._

val autoRefinedYear: Year = Year(2000, AD)
// autoRefinedYear: wen.types.Year = Year(2000,AD)

Because instances of cats's Eq, Order and Show are available, we can also do the following:

import cats.implicits._ // for cats syntax
import wen.implicits._ // for wen's instances

refinedYear.show
// res0: String = 2019

yearWithDefaultEpoch.show
// res1: String = 2011

refinedYear <= refinedYear
// res2: Boolean = true

refinedYear ===  autoRefinedYear
// res3: Boolean = false

Epoch

Constructors
BC
AD
Epoch.fromString(epoch: String)
Instances
Order[Epoch]
Eq[Epoch]
Show[Epoch]
val epochFromString = Epoch.fromString("AD")
// epochFromString: Option[wen.types.Epoch] = Some(AD)

val notEpochFromString = Epoch.fromString("ACDC")
// notEpochFromString: Option[wen.types.Epoch] = None

WeekDay

Constructors
Sunday
Monday
Tuesday
Wednesday
Thursday
Friday
Saturday
WeekDay.fromString(weekDay: String): Option[WeekDay]
Instances
Order[WeekDay]
Eq[WeekDay]
Show[WeekDay]

The provided Order instance starts on Sunday and ends on Saturday.

Because instances of cats's Eq, Order and Show are available, we can do the following:

Usage

import cats.implicits._ // for cats syntax
import wen.implicits._ // for wen's instances

val oneDay: WeekDay = Tuesday
// oneDay: wen.types.WeekDay = Tuesday

val otherDay: WeekDay = Friday
// otherDay: wen.types.WeekDay = Friday

val yetAnotherDay: WeekDay = WeekDay.fromString("Saturday")
// yetAnotherDay: Option[wen.types.WeekDay] = Some(Saturday)

val notAnotherDay: WeekDay = WeekDay.fromString("Caturday")
// notAnotherDay: Option[wen.types.WeekDay] = None

otherDay < oneDay
// res0: Boolean = false

otherDay ===  oneDay
// res1: Boolean = false

otherDay.show
// res2: String = Friday

// Note that we have to specify the type WeekDay so that Scala doesn't infer the type as Saturday
(Saturday: WeekDay).show
// res3: String = Saturday

There's also an Order[WeekDay] instance starting on Monday, but it requires the user to explicitly import it and make it implicit. Note that if you import the default Order instance for weekday, it will conflict with this one and the compiler won't be able to infer which one to use.

Usage

import cats.implicits._

implicit val orderInstance = wen.instances.WeekDayInstances.mondayFirstWeekDayOrderInstance
orderInstance: cats.Order[wen.types.WeekDay] = wen.instances.WeekDayInstances$$anon$1@7da2d02d

import wen.types._

val oneDay: WeekDay = Sunday
// oneDay: wen.types.WeekDay = Sunday

val otherDay: WeekDay = Monday
// otherDay: wen.types.WeekDay = Monday

otherDay < oneDay
// res0: Boolean = true

Hour

Constructors
Hour(hour: NumericHour): Hour
Hour.fromInt(hour: Int): Option[Hour]
Instances
Order[Hour]
Eq[Hour]
Show[Hour]

Usage

import wen.types._
import eu.timepit.refined.{W, refineMV}
import eu.timepit.refined.numeric.{Interval, Positive}

val hour = Hour.fromInt(23)
// hour: Option[wen.types.Hour] = Some(Hour(23))

val notHour = Hour.fromInt(25)
// notHour: Option[wen.types.Hour] = None

// You need to use refineV when refining non-literal values
val refinedHour = Hour(refineMV[Interval.Closed[W.`0`.T, W.`23`.T]](10))
// refinedHour: wen.types.Hour = Hour(10)

import eu.timepit.refined.auto._

val autoRefinedHour: Hour = Hour(12)
// autoRefinedHour: wen.types.Hour = Hour(12)

Because instances of cats's Eq, Order and Show are available, we can also do the following:

import cats.implicits._ // for cats syntax
import wen.implicits._ // for wen's instances

refinedHour.show
// res0: String = 10

refinedHour > autoRefinedHour
// res1: Boolean = false

autoRefinedHour === autoRefinedHour
// res2: Boolean = true

Minute

Constructors
Minute(minute: NumericMinute): Minute
Minute.fromInt(minute: Int): Option[Minute]
Instances
Order[Minute]
Eq[Minute]
Show[Minute]

Usage

import wen.types._
import eu.timepit.refined.{W, refineMV}
import eu.timepit.refined.numeric.{Interval, Positive}

val minute = Minute.fromInt(0)
// minute: Option[wen.types.Minute] = Some(Minute(0))

val notMinute = Minute.fromInt(-12)
// notMinute: Option[wen.types.Minute] = None

// You need to use refineV when refining non-literal values
val refinedMinute: Minute = Minute(refineMV[Interval.Closed[W.`0`.T, W.`59`.T]](15))
// refinedMinute: wen.types.Minute = Minute(15)

import eu.timepit.refined.auto._

val autoRefinedMinute: Minute = Minute(45)
// autoRefinedMinute: wen.types.Minute = Minute(45)

Because instances of cats's Eq, Order and Show are available, we can also do the following:

import cats.implicits._ // for cats syntax
import wen.implicits._ // for wen's instances

autoRefinedMinute.show
// res0: String = 45

autoRefinedMinute > refinedMinute
// res1: Boolean = true

autoRefinedMinute =!= refinedMinute
// res2: Boolean = true

Second

Constructors
Second(second: NumericSecond): Second
Second.fromInt(second: Int): Option[Second]
Instances
Order[Second]
Eq[Second]
Show[Second]

Usage

import wen.types._
import eu.timepit.refined.{W, refineMV}
import eu.timepit.refined.numeric.{Interval, Positive}

val second = Second.fromInt(42)
// second: Option[wen.types.Second] = Some(Second(42))

val notSecond = Second.fromInt(591)
// notSecond: Option[wen.types.Second] = None

// You need to use refineV when refining non-literal values
val refinedSecond = Second(refineMV[Interval.Closed[W.`0`.T, W.`59`.T]](20))
// refinedSecond: wen.types.Second = Second(20)

import eu.timepit.refined.auto._

val autoRefinedSecond: Second = Second(12)
// autoRefinedSecond: wen.types.Second = Second(12)

Because instances of cats's Eq, Order and Show are available, we can also do the following:

import cats.implicits._ // for cats syntax
import wen.implicits._ // for wen's instances

refinedSecond.show
// res0: String = Some(20)

refinedSecond > autoRefinedSecond
// res1: Boolean = true

refinedSecond === autoRefinedSecond
// res2: Boolean = false

Millisecond

Constructors
Millisecond(millisecond: NumericMillisecond): Millisecond
Millisecond.fromInt(millisecond: Int): Option[Millisecond]
Instances
Order[Millisecond]
Eq[Millisecond]
Show[Millisecond]

Usage

import wen.types._
import eu.timepit.refined.{W, refineMV}
import eu.timepit.refined.numeric.{Interval, Positive}

val millisecond = Millisecond.fromInt(456)
// millisecond: Option[wen.types.Millisecond] = Some(Millisecond(456))

val notMillisecond = Millisecond.fromInt(-1)
// notMillisecond: Option[wen.types.Millisecond] = None

// You need to use refineV when refining non-literal values
val refinedMillisecond = Millisecond(refineMV[Interval.Closed[W.`0`.T,
W.`999`.T]](999))
// refinedMillisecond: wen.types.Millisecond = Millisecond(999)

import eu.timepit.refined.auto._

val autoRefinedMillisecond: Millisecond = Millisecond(999)
// autoRefinedMillisecond: wen.types.Millisecond = Millisecond(999)

Because instances of cats's Eq, Order and Show are available, we can also do the following:

import cats.implicits._ // for cats syntax
import wen.implicits._ // for wen's instances

refinedMillisecond.show
// res0: String = 999

refinedMillisecond >= autoRefinedMillisecond
// res1: Boolean = true

refinedMillisecond === autoRefinedMillisecond
// res2: Boolean = true

Full Representations

Time

Constructors
Time(hour: Hour, minute: Minute, second: Second, millisecond: Millisecond): Time
Time(hour: Hour, minute: Minute, second: Second): Time
Time(hour: Hour, minute: Minute): Time
Time(hour: Hour): Time
Time(time: LocalTime): Time
Instances
Order[Time]
Eq[Time]
Show[Time]

Values for the properties that are not specified, will default to 0.

Usage

import wen.types._
import eu.timepit.refined.auto._
import wen.datetime._

val time1 = Time(Hour(7), Minute(5), Second(10), Millisecond(200))
// time1: wen.datetime.Time = Time(Hour(7),Minute(5),Second(10),Millisecond(200))

val time2 = Time(Hour(7), Minute(5), Second(10))
// time2: wen.datetime.Time = Time(Hour(7),Minute(5),Second(10),Millisecond(0))

val time3 = Time(Hour(7), Minute(5))
// time3: wen.datetime.Time = Time(Hour(7),Minute(5),Second(0),Millisecond(0))

val time4 = Time(Hour(7))
// time4: wen.datetime.Time = Time(Hour(7),Minute(0),Second(0),Millisecond(0))

import java.time.LocalTime

val time5 = Time(LocalTime.now)
// time5: wen.datetime.Time = Time(Hour(7),Minute(2),Second(48),Millisecond(159))

Because instances of cats's Eq, Order and Show are available, we can also do the following:

import cats.implicits._ // for cats syntax
import wen.implicits._ // for wen's instances

time1.show
// res0: String = 07:05:10.200

time3.show
// res1: String = 07:05:00.0

time1 > time3
// res2: Boolean = true

time2 =!= time4
// res3: Boolean = true

To create a Time from non-refined values, you can do the following:

import wen.types._
import wen.datetime._

val optionTime = for {
  hour <- Hour.fromInt(12)
  minute <- Minute.fromInt(15)
  second <- Second.fromInt(59)
  millisecond <- Millisecond.fromInt(908)
} yield Time(hour, minute, second, millisecond)
// optionTime: Option[wen.datetime.Time] = Some(Time(Hour(12),Minute(15),Second(59),Millisecond(908)))

ZoneTime

Constructors
ZoneTime(time: Time, offset: Offset): ZoneTime
ZoneTime(offsetTime: OffsetTime): ZoneTime
Instances
Order[ZoneTime]
Eq[ZoneTime]
Show[ZoneTime]
Offset
Constructors
Offset(offsetType: OffsetType, hour: Hour, minute: Minute): Offset
Offset(zoneOffset: ZoneOffset): Offset

Members

UTC: Offset = Offset([UTCPlus](#OffsetType), Hour(0), Minute(0))

Because instances of cats's Eq, Order and Show are available, we can also do the following:

import wen.datetime._
import wen.implicits._
import cats.implicits._
import wen.types.

val offset1 = Offset.UTC
// offset1: wen.datetime.Offset = Offset(UTCPlus,Hour(0),Minute(0))

val offset2 = Offset(Offset.UTCMinus, Hour.fromInt(5).get, Minute.fromInt(45).get)
// offset2: wen.datetime.Offset = Offset(UTCMinus,Hour(5),Minute(45))

val offset3 = Offset(Offset.UTCPlus, Hour.fromInt(12).get, Minute.fromInt(20).get)
// offset3: wen.datetime.Offset = Offset(UTCPlus,Hour(12),Minute(20))


offset1 > offset2
// res0: Boolean = true

offset3 > offset2
// res1: Boolean = true

offset2 > offset3
// res2: Boolean = false

offset1 === offset2
// res3: Boolean = false

offset1 =!= offset3
// res4: Boolean = true

offset1.show
// res5: String = +00:00

offset2.show
// res6: String = -05:45

offset3.show
// res7: String = +12:20

A Show instance in ISO format is also available:

import wen.types._
import cats.implicits._
import wen.datetime._
import wen.instances.iso.isoOffsetShowInstance

val offset1 = Offset.UTC
// offset1: wen.datetime.Offset = Offset(UTCPlus,Hour(0),Minute(0))

val offset2 = Offset(Offset.UTCMinus, Hour.fromInt(5).get, Minute.fromInt(45).get)
// offset2: wen.datetime.Offset = Offset(UTCMinus,Hour(5),Minute(45))

val offset3 = Offset(Offset.UTCPlus, Hour.fromInt(12).get, Minute.fromInt(20).get)
// offset3: wen.datetime.Offset = Offset(UTCPlus,Hour(12),Minute(20))

offset1.show
// res0: String = Z

offset2.show
// res1: String = -05:45

offset3.show
// res2: String = +12:20
OffsetType
Constructors
UTCMinus
UTCPlus

Usage

import wen.types._
import eu.timepit.refined.auto._
import wen.datetime._

val time1 = Time(Hour(10), Minute(11), Second(12), Millisecond(13))
// time1: wen.datetime.Time = Time(Hour(10),Minute(11),Second(12),Millisecond(13))

val zoneTime1 = ZoneTime(time1, Offset.UTC)
// zoneTime1: wen.datetime.ZoneTime = ZoneTime(Time(Hour(10),Minute(11),Second(12),Millisecond(13)),Offset(UTCPlus,Hour(0),Minute(0)))

val offset1 = Offset(Offset.UTCPlus, Hour(1), Minute(0))
// offset1: wen.datetime.Offset = Offset(UTCPlus,Hour(1),Minute(0))

val offset2 = Offset(Offset.UTCMinus, Hour(1), Minute(0))
// offset2: wen.datetime.Offset = Offset(UTCMinus,Hour(1),Minute(0))

val zoneTime2 = ZoneTime(time1, offset1)
// zoneTime2: wen.datetime.ZoneTime = ZoneTime(Time(Hour(10),Minute(11),Second(12),Millisecond(13)),Offset(UTCPlus,Hour(1),Minute(0)))

val zoneTime3 = ZoneTime(time1, offset2)
// zoneTime3: wen.datetime.ZoneTime = ZoneTime(Time(Hour(10),Minute(11),Second(12),Millisecond(13)),Offset(UTCMinus,Hour(1),Minute(0)))

val time2 = Time(Hour(9), Minute(11), Second(12), Millisecond(13))
time2: wen.datetime.Time = Time(Hour(9),Minute(11),Second(12),Millisecond(13))

val zoneTime4 = ZoneTime(time2, offset1)
// zoneTime4: wen.datetime.ZoneTime = ZoneTime(Time(Hour(9),Minute(11),Second(12),Millisecond(13)),Offset(UTCPlus,Hour(1),Minute(0)))

import java.time.ZoneOffset

val offset3 = Offset(ZoneOffset.MIN)
// offset3: wen.datetime.Offset = Offset(UTCMinus,Hour(18),Minute(0))

import java.time.OffsetTime

val zoneTime5 = ZoneTime(OffsetTime.now)
// zoneTime5: wen.datetime.ZoneTime = ZoneTime(Time(Hour(21),Minute(33),Second(10),Millisecond(1)),Offset(UTCPlus,Hour(1),Minute(0)))

Because instances of cats's Eq, Order and Show are available, we can also do the following:

import cats.implicits._ // for cats syntax
import wen.implicits._ // for wen's instances

zoneTime1.show
// res0: String = 10:11:12.13 +00:00

zoneTime2.show
// res1: String = 10:11:12.13 +01:00

zoneTime4.show
// res2: String = 09:11:12.13 +01:00

zoneTime1 < zoneTime2
// res3: Boolean = true

zoneTime1 === zoneTime4
// res4: Boolean = true

Date

Constructors
Date(localDate: LocalDate): Date
Date.safe(day: Day, month: Month, year: Year): Option[Date]
Date.unsafe(day: Day, month: Month, year: Year): Date
Instances
Order[Date]
Eq[Date]
Show[Date]

The constructor Date.safe(day: Day, month: Month, year: Year): Option[Date] only allows the creation of valid dates, and will return None for invalid dates. The unsafe constructor Date.unsafe(day: Day, month: Month, year: Year): Date allows for creating invalid date combinations such as 30 February 2019.

Dates created from java.time.LocalDate are unsafe, as java.time.LocalDateallow for invalid dates in the BC epoch. See [issue #19](https://github.com/mlopes/wen/issues/19) for details of why. For all AD, and non leap year BC dates, date creation should yield valid dates only. This is a problem withjava.time.LocalDate`, if you wish to avoid this you'll need to use one of the other safe constructors.

Usage

import wen.types._
import eu.timepit.refined.auto._
import wen.datetime._

val date = Date.safe(Day(12), Month(8), Year(2016, AD)) 
// date: Option[wen.datetime.Date] = Some(Date(Day(12),August,Year(2016,AD)))

val notDate = Date.safe((31), September, Year(2000, AD))
// notDate: Option[wen.datetime.Date] = None

val unsafeDate = Date.unsafe(Day(31), August, Year(2019, AD))
// unsafeDate: wen.datetime.Date = Date(Day(31),August,Year(2019,AD))

// This creates an invalid date
val unsafeNotDate = Date.unsafe(Day(31), February, Year(2018, AD))
// unsafeNotDate: wen.datetime.Date = Date(Day(31),February,Year(2018,AD))

import java.time.LocalDate

val date2 = Date(LocalDate.now)
// date2: wen.datetime.Date = Date(Day(2),May,Year(2019,AD))

Because instances of cats's Eq, Order and Show are available, we can also do the following:

import cats.implicits._ // for cats syntax
import wen.implicits._ // for wen's instances

 unsafeDate.show
// res0: String = 31 August 2019

val unsafeDate1 = Date.unsafe(Day(31), August, Year(2018, AD))
// unsafeDate1: wen.datetime.Date = Date(Day(31),August,Year(2018,AD))

unsafeDate < unsafeDate1
// res1: Boolean = false

unsafeDate === unsafeDate1
// res1: Boolean = false

DateTime

Constructors
DateTime(date: Date, time: Time): DateTime
DateTime(localDateTime: LocalDateTime): DateTime
Instances
Order[DateTime]
Eq[DateTime]
Show[DateTime]

Usage

import wen.types._
import eu.timepit.refined.auto._
import wen.datetime._

val date1 = Date.unsafe(Day(12), Month(8), Year(2016, AD))
// date1: wen.datetime.Date = Date(Day(12),August,Year(2016,AD))

val date2 = Date.unsafe(Day(22), Month(8), Year(1982, AD))
// date2: wen.datetime.Date = Date(Day(22),August,Year(1982,AD))

val time1 = Time(Hour(7), Minute(5))
// time1: wen.datetime.Time = Time(Hour(7),Minute(5),Second(0),Millisecond(0))

val time2 = Time(Hour(20), Minute(30))
// time2: wen.datetime.Time = Time(Hour(20),Minute(30),Second(0),Millisecond(0))

val dateTime1 = DateTime(date1, time1)
// dateTime1: wen.datetime.DateTime = DateTime(Date(Day(12),August,Year(2016,AD)),Time(Hour(7),Minute(5),Second(0),Millisecond(0)))

val dateTime2 = DateTime(date2, time2)
// dateTime2: wen.datetime.DateTime = DateTime(Date(Day(22),August,Year(1982,AD)),Time(Hour(20),Minute(30),Second(0),Millisecond(0)))

val dateTime3 = DateTime(date1, time2)
// dateTime3: wen.datetime.DateTime = DateTime(Date(Day(12),August,Year(2016,AD)),Time(Hour(20),Minute(30),Second(0),Millisecond(0)))

import java.time.LocalDateTime

val dateTime4 = DateTime(LocalDateTime.now)
// dateTime4: wen.datetime.DateTime = DateTime(Date(Day(2),May,Year(2019,AD)),Time(Hour(20),Minute(40),Second(16),Millisecond(612)))

Because instances of cats's Eq, Order and Show are available, we can also do the following:

import cats.implicits._ // for cats syntax
import wen.implicits._ // for wen's instances

dateTime1.show
// res0: String = 12 August 2016 07:05:00.0

dateTime1 > dateTime2
// res1: Boolean = true

dateTime3 > dateTime1
// res2: Boolean = true

dateTime1 =!= dateTime2
// res3: Boolean = true

ZoneDateTime

Constructors
ZoneDateTime(date: Date, zoneTime: ZoneTime): ZoneDateTime
ZoneDateTime(offsetDateTime: OffsetDateTime): ZoneDateTime
ZoneDateTime(instant: Instant): ZoneDateTime
Instances
Order[ZoneDateTime]
Eq[ZoneDateTime]
Show[ZoneDateTime]

Usage

import wen.types._
import eu.timepit.refined.auto._
import wen.datetime._

val date1 = Date.unsafe(Day(12), Month(8), Year(2016, AD))
// date1: wen.datetime.Date = Date(Day(12),August,Year(2016,AD))

val time1 = ZoneTime(Time(Hour(7), Minute(5)), Offset.UTC)
// time1: wen.datetime.ZoneTime = ZoneTime(Time(Hour(7),Minute(5),Second(0),Millisecond(0)),Offset(UTCPlus,Hour(0),Minute(0)))

val time2 = ZoneTime(Time(Hour(7), Minute(5)), Offset(Offset.UTCMinus, Hour(1), Minute(0)))
// time2: wen.datetime.ZoneTime = ZoneTime(Time(Hour(7),Minute(5),Second(0),Millisecond(0)),Offset(UTCMinus,Hour(1),Minute(0)))

val time3 = ZoneTime(Time(Hour(8), Minute(5)), Offset(Offset.UTCMinus, Hour(1), Minute(0)))
// time3: wen.datetime.ZoneTime = ZoneTime(Time(Hour(8),Minute(5),Second(0),Millisecond(0)),Offset(UTCMinus,Hour(1),Minute(0)))

val zoneDateTime1 = ZoneDateTime(date1, time1)
// zoneDateTime1: wen.datetime.ZoneDateTime = ZoneDateTime(Date(Day(12),August,Year(2016,AD)),ZoneTime(Time(Hour(7),Minute(5),Second(0),Millisecond(0)),Offset(UTCPlus,Hour(0),Minute(0))))

val zoneDateTime2 = ZoneDateTime(date1, time2)
// zoneDateTime2: wen.datetime.ZoneDateTime = ZoneDateTime(Date(Day(12),August,Year(2016,AD)),ZoneTime(Time(Hour(7),Minute(5),Second(0),Millisecond(0)),Offset(UTCMinus,Hour(1),Minute(0))))

val zoneDateTime3 = ZoneDateTime(date1, time3)
// zoneDateTime3: wen.datetime.ZoneDateTime = ZoneDateTime(Date(Day(12),August,Year(2016,AD)),ZoneTime(Time(Hour(8),Minute(5),Second(0),Millisecond(0)),Offset(UTCMinus,Hour(1),Minute(0))))

import java.time.OffsetDateTime
import java.time.Instant

val zoneDateTime4 = ZoneDateTime(OffsetDateTime.now)
// zoneDateTime4: wen.datetime.ZoneDateTime = ZoneDateTime(Date(Day(2),May,Year(2019,AD)),ZoneTime(Time(Hour(22),Minute(44),Second(34),Millisecond(833)),Offset(UTCPlus,Hour(1),Minute(0))))

val zoneDateTime5 = ZoneDateTime(Instant.now)
// zoneDateTime5: wen.datetime.ZoneDateTime = ZoneDateTime(Date(Day(2),May,Year(2019,AD)),ZoneTime(Time(Hour(21),Minute(45),Second(7),Millisecond(704)),Offset(UTCPlus,Hour(0),Minute(0))))

Because instances of cats's Eq, Order and Show are available, we can also do the following:

import cats.implicits._ // for cats syntax
import wen.implicits._ // for wen's instances

zoneDateTime1.show
// res0: String = 12 August 2016 07:05:00.0 +00:00

scala> zoneDateTime2.show
// res1: String = 12 August 2016 07:05:00.0 -01:00

scala> zoneDateTime3.show
// res2: String = 12 August 2016 08:05:00.0 -01:00

zoneDateTime1 > zoneDateTime2
// res3: Boolean = true

zoneDateTime1 === zoneDateTime3
// res4: Boolean = true

Numeric types

Numeric types use refined for type safe representation of date/time components as integers. These types are available under the wen.types.NumericTypes namespace.

NumericDay

type NumericDay = Int Refined Interval.Closed[W.`1`.T, W.`31`.T]

NumericMonth

type NumericMonth =  Int Refined Interval.Closed[W.`1`.T, W.`12`.T]

NumericYear

type NumericYear = Int Refined Positive

NumericHour

type NumericHour = Int Refined Interval.Closed[W.`0`.T, W.`23`.T]

NumericMinute

type NumericMinute = Int Refined Interval.Closed[W.`0`.T, W.`59`.T]

NumericSecond

type NumericSecond = Int Refined Interval.Closed[W.`0`.T, W.`59`.T]

NumericMillisecond

type NumericMillisecond = Int Refined Interval.Closed[W.`0`.T, W.`999`.T]

Refinement Helpers

Refinement helpers are made available under the wen.refine namespace.

Their purpose is to make it easier to refine integer values into NumericTypes, without the user having to know about un-specialised refinement types.

The following refinement helper functions are available:

functions
def refineHour(hour: Int): Either[String, NumericHour]
def refineMinute(minute: Int): Either[String, NumericMinute]
def refineSecond(second: Int): Either[String, NumericSecond]
def refineMilliSecond(millisecond: Int): Either[String, NumericMillisecond]
def refineYear(year: Int): Either[String, NumericYear]
def refineMonth(month: Int): Either[String, NumericMonth]
def refineDay(day: Int): Either[String, NumericDay]

Usage

def time(hour: Int, minute: Int, second: Int): Either[String, Time] =
  for {
    h <- refineHour(hour)
    m <- refineMinute(minute)
    s <- refineSecond(second)
    ms <- refineMilliSecond(millisecond)
  } yield new Time(Hour(h), Minute(m), Second(s), Millisecond(ms))

ISO-8601 instances

ISO-8601 instances are made available under the wen.instances.iso namespace.

ISO Instances
Show[Date]
Show[Time]
Show[ZoneTime]
Show[DateTime]
Show[ZoneDateTime]

ISO show instances are an alternative to the default ones provided in wen.implicits. Unlike the default instances which return the dates/times in human readable format, ISO instances return the dates in ISO-8601 format.

See here for the wikipedia entry on ISO-8601.

Note that when using the ISO instances you can't import the instances under wen.implicits or wen.instances otherwise the compiler won't be able to infer which instances to use for Show.

Usage

import cats.implicits._
import wen.instances.iso._
import wen.datetime._
import wen.types._
import eu.timepit.refined.auto._

val date1 = Date.unsafe(Day(25), July, Year(1975, AD))
// date1: wen.datetime.Date = Date(Day(25),July,Year(1975,AD))

val date2 = Date.unsafe(Day(1), January, Year(2131, BC))
// date2: wen.datetime.Date = Date(Day(1),January,Year(2131,BC))

date1.show
// res0: String = 1975-07-25

scala> date2.show
// res1: String = -2130-01-01

val date3 = Date.unsafe(Day(2), March, Year(1, BC))
// date3: wen.datetime.Date = Date(Day(2),March,Year(1,BC))

val time = Time(Hour(8), Minute(53), Second(23), Millisecond(900))
// time: wen.datetime.Time = Time(Hour(8),Minute(53),Second(23),Millisecond(900))

val dateTime1 = DateTime(date1, time)
// dateTime1: wen.datetime.DateTime = DateTime(Date(Day(25),July,Year(1975,AD)),Time(Hour(8),Minute(53),Second(23),Millisecond(900)))

val dateTime2 = DateTime(date2, time)
// dateTime2: wen.datetime.DateTime = DateTime(Date(Day(1),January,Year(2131,BC)),Time(Hour(8),Minute(53),Second(23),Millisecond(900)))

val dateTime3 = DateTime(date3, time)
// dateTime3: wen.datetime.DateTime = DateTime(Date(Day(2),March,Year(1,BC)),Time(Hour(8),Minute(53),Second(23),Millisecond(900)))

dateTime1.show
// res2: String = 1975-07-25T08:53:23

dateTime2.show
// res3: String = -2130-01-01T08:53:23

dateTime3.show
// res4: String = 0000-03-02T08:53:23

val zoneTime1 = ZoneTime(Time(Hour(8), Minute(15), Second(2), Millisecond(33)),
                         Offset.UTC)
// zoneTime1: wen.datetime.ZoneTime = ZoneTime(Time(Hour(8),Minute(15),Second(2),Millisecond(33)),Offset(UTCPlus,Hour(0),Minute(0)))

val zoneTime2 = ZoneTime(Time(Hour(10), Minute(31), Second(23), Millisecond(606)),
                         Offset(Offset.UTCMinus, Hour(1), Minute(0)))
// zoneTime2: wen.datetime.ZoneTime = ZoneTime(Time(Hour(10),Minute(31),Second(23),Millisecond(606)),Offset(UTCMinus,Hour(1),Minute(0)))

val zoneTime3 = ZoneTime(Time(Hour(7), Minute(53), Second(23), Millisecond(900)),
                         Offset(Offset.UTCPlus, Hour(0), Minute(30)))
// zoneTime3: wen.datetime.ZoneTime = ZoneTime(Time(Hour(7),Minute(53),Second(23),Millisecond(900)),Offset(UTCPlus,Hour(0),Minute(30)))

zoneTime1.show
// res5: String = 08:15:02Z

zoneTime2.show
// res6: String = 10:31:23-01:00

zoneTime3.show
// res7: String = 07:53:23+00:30

val zoneTime1 = ZoneTime(time, Offset.UTC)
// zoneTime1: wen.datetime.ZoneTime = ZoneTime(Time(Hour(8),Minute(53),Second(23),Millisecond(900)),Offset(UTCPlus,Hour(0),Minute(0)))

val zoneTime2 = ZoneTime(time, Offset(Offset.UTCMinus, Hour(1), Minute(0)))
// zoneTime2: wen.datetime.ZoneTime = ZoneTime(Time(Hour(8),Minute(53),Second(23),Millisecond(900)),Offset(UTCMinus,Hour(1),Minute(0)))

val zoneTime3 = ZoneTime(time, Offset(Offset.UTCPlus, Hour(2), Minute(45)))
// zoneTime3: wen.datetime.ZoneTime = ZoneTime(Time(Hour(8),Minute(53),Second(23),Millisecond(900)),Offset(UTCPlus,Hour(2),Minute(45)))

val zoneDateTime1 = ZoneDateTime(date1, zoneTime1)
// zoneDateTime1: wen.datetime.ZoneDateTime = ZoneDateTime(Date(Day(25),July,Year(1975,AD)),ZoneTime(Time(Hour(8),Minute(53),Second(23),Millisecond(900)),Offset(UTCPlus,Hour(0),Minute(0))))

val zoneDateTime2 = ZoneDateTime(date2, zoneTime2)
// zoneDateTime2: wen.datetime.ZoneDateTime = ZoneDateTime(Date(Day(1),January,Year(2131,BC)),ZoneTime(Time(Hour(8),Minute(53),Second(23),Millisecond(900)),Offset(UTCMinus,Hour(1),Minute(0))))

val zoneDateTime3 = ZoneDateTime(date3, zoneTime3)
// zoneDateTime3: wen.datetime.ZoneDateTime = ZoneDateTime(Date(Day(2),March,Year(1,BC)),ZoneTime(Time(Hour(8),Minute(53),Second(23),Millisecond(900)),Offset(UTCPlus,Hour(2),Minute(45))))

zoneDateTime1.show
// res9: String = 1975-07-25T08:53:23Z

zoneDateTime2.show
// res10: String = -2130-01-01T08:53:23-01:00

zoneDateTime3.show
// res11: String = 0000-03-02T08:53:23+02:45

Circe Instances

Circe ISO-8601 encoders and decoders are provided for Date, Time, DateTime, ZoneTime, Offset, and ZoneDateTime. Circe encoders and decoders are also provided for Day, WeekDay, Month, Year, Epoch, Hour, Minute, Second, and Millisecond.