{"id":23580689,"url":"https://github.com/bdkosher/goodtimes","last_synced_at":"2025-05-06T21:06:17.476Z","repository":{"id":75356187,"uuid":"78299647","full_name":"bdkosher/goodtimes","owner":"bdkosher","description":"Java 8 Date/Time API enhancements for Groovy","archived":false,"fork":false,"pushed_at":"2020-01-28T21:50:21.000Z","size":242,"stargazers_count":35,"open_issues_count":0,"forks_count":1,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-05-06T21:06:04.602Z","etag":null,"topics":["date-time","groovy","java-8","java8"],"latest_commit_sha":null,"homepage":null,"language":"Groovy","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/bdkosher.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2017-01-07T19:39:25.000Z","updated_at":"2024-06-07T06:33:02.000Z","dependencies_parsed_at":null,"dependency_job_id":"7d0d01cf-661c-4f2f-bce0-d0da1e8a8c91","html_url":"https://github.com/bdkosher/goodtimes","commit_stats":null,"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bdkosher%2Fgoodtimes","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bdkosher%2Fgoodtimes/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bdkosher%2Fgoodtimes/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bdkosher%2Fgoodtimes/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/bdkosher","download_url":"https://codeload.github.com/bdkosher/goodtimes/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":252769396,"owners_count":21801376,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["date-time","groovy","java-8","java8"],"created_at":"2024-12-27T00:11:40.451Z","updated_at":"2025-05-06T21:06:17.469Z","avatar_url":"https://github.com/bdkosher.png","language":"Groovy","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Goodtimes\n*Java 8 Date/Time API enhancements for Groovy 2.4 and earlier*  [![Maven Central](https://img.shields.io/maven-central/v/com.github.bdkosher/goodtimes.svg)](http://repo1.maven.org/maven2/com/github/bdkosher/goodtimes/1.2/goodtimes-1.2.jar)  [![GitHub release](https://img.shields.io/github/tag/bdkosher/goodtimes.svg)](https://github.com/bdkosher/goodtimes/releases/tag/v1.2)\n\n![goodtimes logo](https://raw.githubusercontent.com/bdkosher/goodtimes/master/logo.gif)\n\nThe [Groovy JDK](http://groovy-lang.org/gdk.html) adds useful methods to [`java.util.Date`](http://docs.groovy-lang.org/latest/html/groovy-jdk/java/util/Date.html) and [`java.util.Calendar`](http://docs.groovy-lang.org/latest/html/groovy-jdk/java/util/Calendar.html) but as of yet does not include comparable methods for the newer Java 8 Date/Time API classes.\n\nGoodtimes fills this gap by providing these `java.time` extension methods, as well as new methods on `java.util.Date` and `java.time.Calendar` for converting to `java.time` equivalents.\n\nNote: As of Groovy 2.5, Goodtimes-provided methods are now part of Groovy JDK itself.\n\n## Contents\n * [Prerequisites](#prerequisites)\n * [Installation](#installation)\n * [Building from Source](#building-from-source) \n * [API Features](#api-features)\n   * [Overloaded Operators](#overloaded-operators)\n   * [Accessor Properties](#accessor-properties)   \n   * [Groovy JDK Mimicking Methods](#groovy-jdk-mimicking-methods)\n   * [Java 8 and Legacy API Bridging Methods](#java-8-and-legacy-api-bridging-methods)\n   * [Parsing Methods](#parsing-methods)\n * [Future Changes](#future-changes)\n\n## Prerequisites\n\nGoodtimes requires Java 8 or later.\n\n## Installation\n\nAdd the goodtimes jar to the classpath in your preferred way and you're set.\n\n### Grape\n```groovy\n@Grab('com.github.bdkosher:goodtimes:1.2') \n```\n\n### Gradle\n```groovy\ncompile group: 'com.github.bdkosher', name: 'goodtimes', version: '1.2'\n```\n\n### Maven\n```xml\n \u003cdependency\u003e\n    \u003cgroupId\u003ecom.github.bdkosher\u003c/groupId\u003e\n    \u003cartifactId\u003egoodtimes\u003c/artifactId\u003e\n    \u003cversion\u003e1.2\u003c/version\u003e\n\u003c/dependency\u003e\n```\n\n## Building from Source\n\nClone the repo or [download a source release](https://github.com/bdkosher/goodtimes/archive/v1.2.tar.gz) and build with Gradle. \n\n```bash\n    gradlew install\n    cp build/libs/goodtimes-1.2.jar $USER_HOME/.groovy/lib\n```\n\n## API Features\n\nConsult the [goodtimes 1.2 Groovydocs](http://bdkosher.github.io/goodtimes/v1.2/groovydoc/) for complete API information. See the [Groovy metaprogramming documentation](http://groovy-lang.org/metaprogramming.html#_instance_methods) for details on how these methods manifest themselves at runtime.\n\n### Overloaded Operators\n\nMost extension methods are used to overload operators on the `java.time` types.\n\n#### The `++` and `--` Operators\n\nIncrement or decrement `Instant`, `LocalTime`, `LocalDateTime`, `OffsetTime`, `OffsetDateTime`, `ZoneDateTime`, and `Duration` by one second. For `LocalDate`, `Period`, and `DayOfWeek`, increment or decrement by one day. Increment or decrement by one hour for `ZoneOffset`, one month for `Month`, and one year for `Year`.\n\n```groovy\n    def now = LocalTime.now()\n    def today = LocalDate.now()\n    def march = Month.MARCH\n    def utc = ZoneOffset.UTC\n\n    LocalTime oneSecondAgo = --now\n    LocalDate tomorrow = today++\n    Month april = ++march\n    ZoneOffset utcMinusOne = --utc\n```\n\n#### The `+` and `-` Binary Operators\n\nA `long` or `int` operand adds seconds directly to  `Instant`, `LocalTime`, `LocalDateTime`, `OffsetTime`, `OffsetDateTime`, `ZoneDateTime`, and `Duration`. Add or subtract days for `LocalDate`, `Period`, and `DayOfWeek`. Add or subtract hours for `ZoneOffset`. Add or subtract months for `Month`. Add or subtract years for `Year`.\n\n```groovy\n    def now = LocalDateTime.now()\n    def today = LocalDate.now()\n    def march = Month.MARCH\n    def utc = ZoneOffset.UTC\n\n    LocalDateTime oneMinuteAgo = now - 60\n    LocalDate oneWeekFromToday = today + 7\n    Month january = march - 2\n    ZoneOffset utcPlusFive = utc + 5\n```\n\n#### The `[]` Operator\n\nThis operator delegates to the `java.time` types' `get()` or `getLong()` methods, enabling retrieval of the specified `TemporalField` (for `Instant`, `MonthDay`, `YearMonth`, `LocalTime`, `LocalDateTime`, `OffsetDateTime`, and `ZonedDateTime`) or `TemporalUnit`(for `Period` and `Duration`).\n\n```groovy\n    def sixtySeconds = Duration.parse('PT60S')\n    assert sixtySeconds[ChronoUnit.SECONDS] == 60\n```\n\nIn addition to supporting `TemporalField` arguments, the `LocalDate`, `LocalTime`, `LocalDateTime`, `OffsetTime`, `OffsetDateTime`, and `ZonedDateTime` classes can accept `java.util.Calendar` constants:\n\n```groovy\n    def lastChristmas = LocalDate.of(2016, 12, 25)\n\n    assert lastChristmas[Calendar.YEAR] == 2016\n    assert lastChristmas[Calendar.MONTH] == Calendar.DECEMBER\n    assert lastChristmas[Calendar.DATE] == 25\n```\n\n#### The `\u003c\u003c` Operator\n\nLeft shifting can be used to merge two different `java.time` types into a larger aggregate type. For example, left-shifting a `LocalTime` into a `LocalDate` results in a `LocalDateTime`. Left shifting is reflexive so that `A \u003c\u003c B` yields the same result as `B \u003c\u003c A`.\n\n```groovy\n    def thisYear = Year.of(2017)\n    def noon = LocalTime.of(12, 0, 0)\n\n    YearMonth december2017 = thisYear \u003c\u003c Month.DECEMBER\n    LocalDate christmas = december2017 \u003c\u003c 25\n    OffsetTime noonInGreenwich = noon \u003c\u003c ZoneOffset.ofHours(0)\n    LocalDateTime christmasAtNoon = christmas \u003c\u003c noon\n    ZonedDateTime chirstmasAtNoonInNYC = christmasAtNoon \u003c\u003c ZoneId.of('America/New_York')\n    OffsetDateTime chirstmasAtNoonInGreenwich = christmasAtNoon \u003c\u003c ZoneOffset.UTC\n```\n\n#### The `\u003e\u003e` Operator\n\nThe right shift operator, when read as meaning \"through\" or \"to\", is used to create a `Period` from two `LocalDate`, `YearMonth`, or `Year` instances. Similarly, the `\u003e\u003e` operator produces a `Duration` from two `Instant`, `LocalTime`, `LocalDateTime`, `OffsetTime`, `OffsetDateTime`, or `ZonedDateTime` instances.\n\n```groovy\n    def today = LocalDate.now()\n    def tomorrow = today + 1\n\n    Period oneDay = today \u003e\u003e tomorrow\n    Period negOneDay = tomorrow \u003e\u003e today\n```\n\n#### The `*` and `/` Operators\n\nA `Period` and `Duration` can be multiplied by a scalar. Only a `Duration` can be divided.\n\n```groovy\n    def week = Period.ofDays(7)\n    def minute = Duration.ofMinutes(1)\n\n    Period fortnight = week * 2\n    Duration thirtySeconds = minute / 2\n```\n\n#### The  `+` and `-` Unary Operators\n\nA `Period`, `Duration`, or `Year` can be made positive or negated via the `+` and `-` operators.\n\n```groovy\n    def oneWeek = Period.ofDays(7)\n    def oneHour = Duration.ofHours(1)\n\n    assert +oneWeek == oneWeek\n    assert -oneHour == Duration.ofHours(-1)\n```\n\n### Accessor Properties\n\nA `getDay` method exists on `LocalDate`, `LocalDateTime`, `MonthDay`, `OffsetDateTime`, and `ZoendDateTime` as an alias for `getDayOfMonth`.\n\n```groovy\n    def independenceDay = LocalDate.of(2017, Month.JULY, 4)\n\n    assert independenceDay.day == 4\n    assert independenceDay.day == independenceDay.dayOfMonth\n```\n\nThe `ZoneOffset` has getters to obtain the hours, minutes, and seconds values of the offset.\n\n```groovy\n    def zoneOffset = ZoneOffset.ofHoursMinutesSeconds(5, 10, 20)\n\n    assert zoneOffset.hours == 5\n    assert zoneOffset.minutes == 10\n    assert zoneOffset.seconds == 20\n```\n\nThe legacy `Calendar` class has getters to obtain the time zone information as a `ZoneId` and `ZoneOffset`.\n\n```groovy\n    def cal = Calendar.getInstance(TimeZone.getTimeZone('GMT'))\n\n    assert cal.zoneId == ZoneId.of('GMT')\n    assert cal.zoneOffset == ZoneOffset.ofHours(0)\n```\n\nAdditionally, `Calendar` has `getYear`, `getYearMonth`, `getMonth`, `getMonthDay`, and `getDayOfMonth` methods that return the correspondingly-typed `java.time` instances.\n\n```groovy\n    def cal = Date.parse('yyyyMMdd', '20170204').toCalendar()\n\n    assert cal.year == Year.of(2017)\n    assert cal.month == Month.FEBRUARY\n    assert cal.yearMonth == YearMonth.of(2017, Month.FEBRUARY)\n    assert cal.dayOfWeek == DayOfWeek.SATURDAY\n    assert cal.monthDay == MonthDay.of(Month.FEBRUARY, 4)\n```\n\n### Groovy JDK Mimicking Methods\n\nOther extension methods seek to mimic those found in the Groovy JDK for [`java.util.Date`](http://docs.groovy-lang.org/latest/html/groovy-jdk/java/util/Date.html) and [`java.util.Calendar`](http://docs.groovy-lang.org/latest/html/groovy-jdk/java/util/Calendar.html).\n\n#### Iterating Methods\n\nThe `upto()` and `downto()` methods of `LocalTime`, `LocalDateTime`, `OffsetTime`, `OffsetDateTime`, `ZonedDateTime`, and `Instant` iterate on a per second basis. The methods on `LocalDate` iterate on a per day basis.\n\n```groovy\n    def now = LocalTime.now()\n    def aMinuteAgo = now - 60\n\n    now.downto(aMinuteAgo) { LocalTime t -\u003e\n        // this closure will be called 61 times for each sceond between a minute ago and now\n    }\n\n    def today = LocalDate.now()\n    def tomorrow = today + 1\n\n    today.upto(tomorrow) { LocalDate d -\u003e \n        // this closure will be called twice, once for today and once for tomorrow\n    }\n```\n\nA static `eachMonth` method exists on `Month` for iterating through every month. Similarly, a static `eachDay` method exists on `DayOfWeek` for iterating through the days of the week.\n\n#### Formatting Methods\n\n * The `getDateString` method\n   * Exists for `LocalDate`, `LocalDateTime`, `OffsetDateTime`, and `ZonedDateTime`\n   * Equivalent to calling `localDate.format(DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT))`\n   * Example: `5/5/17`\n * The `getTimeString` method\n   * Exists for `LocalTime`, `LocalDateTime`, `OffsetTime`, `OffsetDateTime`, and `ZonedDateTime`\n   * Equivalent to calling `localTime.format(DateTimeFormatter.ofLocalizedTime(FormatStyle.SHORT))`\n   * Example: `10:59 PM`\n * The `getDateTimeString` method\n   * Exists for `LocalDateTime`, `OffsetDateTime`, and `ZonedDateTime`\n   * Equivalent to calling `localDateTime.format(DateTimeFormatter.ofLocalizedDateTime(FormatStyle.SHORT))`\n   * Example: `5/5/17 10:59 PM`\n * The `format(String pattern)` method\n   * Exists on `LocalDate`, `LocalTime`, `LocalDateTime`, `OffsetDateTime`, and `ZonedDateTime`\n   * Equivalent to `.format(DateTimeFormatter.ofPattern(pattern))`\n * The `format(String pattern, Locale locale)` method\n   * Exists on `LocalDate`, `LocalTime`, `LocalDateTime`, `OffsetTime`, `OffsetDateTime`, and `ZonedDateTime`\n   * Equivalent to `.format(DateTimeFormatter.ofPattern(pattern, locale))`\n\n#### Other Methods\n\n**clearTime()** Similar to `Date`, the `LocalDateTime`, `OffsetDateTime`, and `ZonedDateTime` classes have a `clearTime()` method that returns a new instance of the same type with the hours, minutes, seconds, and nanos set to zero. \n\n**describe()** The `Duration` class has a `describe()` method which returns the normalized String representation as a map of `ChronoUnit` keys: `DAYS`, `HOURS`, `MINUTES`, `SECONDS`, and `NANOS`. The `Period` class also has a `describe()` method but with the `ChronoUnit` keys of `YEARS`, `MONTHS`, and `DAYS`.\n\n```groovy\n    Map\u003cTemporalUnit, Long\u003e durationDesc = Duration.parse('P2DT3H4M5.000000006S').describe()\n    assert durationDesc[ChronoUnit.DAYS] == 2\n    assert durationDesc[ChronoUnit.HOURS] == 3\n    assert durationDesc[ChronoUnit.MINUTES] == 4\n    assert durationDesc[ChronoUnit.SECONDS] == 5\n    assert durationDesc[ChronoUnit.NANOS] == 6\n\n    Map\u003cTemporalUnit, Long\u003e periodDesc = Period.parse('P6Y3M1D').describe()\n    assert periodDesc[ChronoUnit.DAYS] == 1\n    assert periodDesc[ChronoUnit.MONTHS] == 3\n    assert periodDesc[ChronoUnit.YEARS] == 6\n```\n\n### Java 8 and Legacy API Bridging Methods \n\nExtension methods exist on `Date` and `Calendar` that produce a reasonably equivalent `java.time` type.\n\n```groovy\n    def c = Calendar.instance\n    def d = new Date()\n\n    Instant cInstant = c.toInstant()\n    Instant dInstant = d.toInstant()\n\n    LocalDate cLocalDate = c.toLocalDate()\n    LocalDate dLocalDate = d.toLocalDate()\n    \n    LocalTime cLocalTime = c.toLocalTime()\n    LocalTime dLocalTime = d.toLocalTime()\n\n    LocalDateTime cLocalDateTime = c.toLocalDateTime()\n    LocalDateTime dLocalDateTime = d.toLocalDateTime()\n\n    ZonedDateTime cZonedDateTime = c.toZonedDateTime()\n    ZonedDateTime dZonedDateTime = d.toZonedDateTime()\n```\n\nAn optional `ZoneOffset`, `ZoneId`, or `java.util.TimeZone` may be passed to the above conversion methods to alter the Time Zone of the returned `Calendar` or to adjust the Time Zone of reference for the returned `Date`.\n\n```groovy\n    def d = new Date()\n    def offset = ZoneOffset.UTC\n    def zoneId = ZoneId.of('America/Resolute')\n    def timeZone = TimeZone.getTimeZone('US/Eastern')\n\n    LocalDate localDateUTC = d.toLocalDate(offset)\n    LocalDate localDateResolute = d.toLocalDate(zoneId)\n    LocalDate localDateUSEastern = d.toLocalDate(timeZone)\n```\n\nThe `toOffsetDateTime` method requires a `ZoneOffset` argument.\n\n```groovy\n    def c = Calendar.instance\n    def d = new Date()\n    def offset = ZoneOffset.UTC\n\n    OffsetDateTime cOffsetDateTime = c.toOffsetDateTime(offset)\n    OffsetDateTime dOffsetDateTime = d.toOffsetDateTime(offset)\n```\n\nThe various `java.time` Date/Time types have `toDate()` and `toCalendar()` methods as well. Nanoseconds are truncated to the nearest millisecond.\n\n```groovy\n    def nows = [LocalDate.now(), LocalTime.now(), LocalDateTime.now(), OffsetDateTime.now(), ZonedDateTime.now()]\n\n    List\u003cDate\u003e dates = nows.collect { it.toDate() }\n    List\u003cCalendar\u003e cals = nows.collect { it.toCalendar() } \n```\n\n### Parsing Methods\n\nEach java.time type already having a `parse(CharSequence input, DateTimeFormatter formatter)` method gains two additional static methods:\n\n * `parse(CharSequence input, String format)` - the format String is used to instantiate a new `DateTimeFormatter` of that formatting pattern.\n * `parse(CharSequence input, String format, ZoneId zone)` - same as above plus the instantiated `DateTimeFormatter` is adjusted to the proivded zone via its `withZone` method.\n\n ```groovy\n    def date = '2017-07-15'\n    def offsetDatetime = '111213 141516 +171819'\n\n    LocalDate parsedDate = LocalDate.parse(date, 'yyyy-MM-dd')\n    OffsetDateTime parsed = OffsetDateTime.parse(offsetDatetime, 'MMddyy HHmmss XX', ZoneId.of('UTC+0500'))\n```\n\n## Future Changes\n\n * Provide an equivalent to `groovy.time.TimeCategory`\n * TimeZone/ZoneId convenience methods (e.g. get all time zones at a given offset)\n * Consider adding missing Date/Calendar methods from Groovy JDK (e.g. `set` and `copyWith`)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbdkosher%2Fgoodtimes","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbdkosher%2Fgoodtimes","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbdkosher%2Fgoodtimes/lists"}