├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── documentation.json ├── elm.json ├── examples ├── Basic.elm ├── Language.elm ├── README.md ├── Relative.elm └── elm.json ├── src ├── DateFormat.elm └── DateFormat │ ├── Language.elm │ └── Relative.elm └── tests ├── Fuzzers.elm ├── RelativeTests.elm └── Tests.elm /.gitignore: -------------------------------------------------------------------------------- 1 | elm-stuff/ -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: node 3 | 4 | cache: 5 | directories: 6 | - elm-stuff/0.19.0/ 7 | - sysconfcpus 8 | 9 | before_install: 10 | - | # epic build time improvement - see https://github.com/elm-lang/elm-compiler/issues/1473#issuecomment-245704142 11 | if [ ! -d sysconfcpus/bin ]; 12 | then 13 | git clone https://github.com/obmarg/libsysconfcpus.git; 14 | cd libsysconfcpus; 15 | ./configure --prefix=$TRAVIS_BUILD_DIR/sysconfcpus; 16 | make && make install; 17 | cd ..; 18 | fi 19 | 20 | install: 21 | - npm install -g elm@0.19.0 elm-test@elm0.19.0 elm-format@elm0.19.0 22 | - mv $(npm config get prefix)/bin/elm $(npm config get prefix)/bin/elm-old 23 | - printf '%s\n\n' '#!/bin/bash' 'echo "Running elm with sysconfcpus -n 2"' '$TRAVIS_BUILD_DIR/sysconfcpus/bin/sysconfcpus -n 2 elm-old "$@"' > $(npm config get prefix)/bin/elm 24 | - chmod +x $(npm config get prefix)/bin/elm 25 | 26 | script: 27 | # - elm-format --validate src tests examples 28 | - elm-test 29 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | > Detailed information about changes from minor and major releases. 3 | 4 | If you'd like to see differences between your version and the one you want to upgrade, you can run: 5 | 6 | `elm diff ryan-haskell/date-format ` 7 | 8 | --- 9 | 10 | ### `2.3.0` 11 | 12 | This is a MINOR change. 13 | 14 | #### DateFormat.Language - MINOR 15 | 16 | Added: 17 | portuguese : DateFormat.Language.Language 18 | 19 | --- 20 | 21 | ### `2.2.0` 22 | 23 | This is a MINOR change. 24 | 25 | #### DateFormat.Language - MINOR 26 | 27 | Added: 28 | swedish : DateFormat.Language.Language 29 | 30 | --- 31 | 32 | ### `2.1.0` 33 | 34 | This is a MINOR change. 35 | 36 | #### DateFormat.Language - MINOR 37 | 38 | Added: 39 | dutch : DateFormat.Language.Language 40 | 41 | --- 42 | 43 | ### `2.0.0` 44 | This is a MAJOR change. 45 | 46 | #### ADDED MODULES - MINOR 47 | 48 | DateFormat.Language 49 | 50 | 51 | #### DateFormat - MAJOR 52 | 53 | Added: 54 | dayOfWeekNameAbbreviated : Token 55 | formatWithLanguage : Language -> List Token -> Zone -> Posix -> String 56 | millisecondFixed : Token 57 | millisecondNumber : Token 58 | monthNameAbbreviated : Token 59 | 60 | Removed: 61 | type alias FormatOptions = 62 | { fullMonthName : Month -> String, dayOfWeekName : Weekday -> String 63 | } 64 | dayOfWeekNameFirstThree : Token 65 | dayOfWeekNameFirstTwo : Token 66 | formatWithOptions : 67 | FormatOptions -> List Token -> Zone -> Posix -> String 68 | monthNameFirstThree : Token 69 | 70 | 71 | #### DateFormat.Relative - MINOR 72 | 73 | Added: 74 | defaultRelativeOptions : RelativeTimeOptions 75 | 76 | --- 77 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018-present, Ryan Haskell-Glatz 2 | 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above 12 | copyright notice, this list of conditions and the following 13 | disclaimer in the documentation and/or other materials provided 14 | with the distribution. 15 | 16 | * Neither the name of Ryan Haskell-Glatz nor the names of other 17 | contributors may be used to endorse or promote products derived 18 | from this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 23 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 24 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 25 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 26 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 28 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # date-format 2 | > A reliable way to format dates and times with Elm. 3 | 4 | [![Build Status](https://travis-ci.org/ryan-haskell/date-format.svg?branch=master)](https://travis-ci.org/ryan-haskell/date-format) 5 | 6 | 7 | ### Using the [elm package](http://package.elm-lang.org/packages/ryan-haskell/date-format/latest) 8 | 9 | ``` 10 | elm install ryan-haskell/date-format 11 | ``` 12 | 13 | 14 | ### What is `date-format`? 15 | 16 | If you're coming from Javascript, you might have heard of [MomentJS](https://momentjs.com). 17 | 18 | MomentJS is a great library for formatting dates! 19 | 20 | `date-format` has similar [formatting options](https://momentjs.com/docs/#/displaying/format/) as Moment, but it uses Elm's awesome type system to provide __human readable__ names, and catch typos for you at compile time! 21 | 22 | No need to remember the difference between `mm` and `MM` and `M`! 23 | 24 | 25 | ### A quick example 26 | 27 | ```elm 28 | import DateFormat 29 | import Time exposing (Posix, Zone, utc) 30 | 31 | 32 | 33 | -- Let's create a custom formatter we can use later: 34 | 35 | 36 | ourFormatter : Zone -> Posix -> String 37 | ourFormatter = 38 | DateFormat.format 39 | [ DateFormat.monthNameFull 40 | , DateFormat.text " " 41 | , DateFormat.dayOfMonthSuffix 42 | , DateFormat.text ", " 43 | , DateFormat.yearNumber 44 | ] 45 | 46 | 47 | 48 | -- With our formatter, we can format any date as a string! 49 | 50 | 51 | ourTimezone : Zone 52 | ourTimezone = 53 | utc 54 | 55 | 56 | 57 | -- 2018-05-20T19:18:24.911Z 58 | 59 | 60 | ourPosixTime : Posix 61 | ourPosixTime = 62 | Time.millisToPosix 1526843861289 63 | 64 | 65 | ourPrettyDate : String 66 | ourPrettyDate = 67 | ourFormatter ourTimezone ourPosixTime 68 | 69 | ``` 70 | 71 | Would make `ourPrettyDate` return: 72 | 73 | ``` 74 | "May 20th, 2018" : String 75 | ``` 76 | 77 | ### Want more examples? 78 | 79 | I've created a few more examples in the `examples/` folder for this repo. 80 | 81 | Here's how you can try them out: 82 | 83 | 1. `git clone https://github.com/ryan-haskell/date-format` 84 | 85 | 1. `cd date-format/examples` 86 | 87 | 1. `elm reactor` 88 | 89 | 1. Go to [http://localhost:8000](http://localhost:8000) 90 | 91 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /documentation.json: -------------------------------------------------------------------------------- 1 | [{"name":"DateFormat","comment":" A reliable way to format dates and times with Elm.\n\n\n# Formatting dates\n\n@docs format\n\n\n# Supporting a different language?\n\n@docs formatWithLanguage\n\n\n# Available formatting options\n\n@docs Token\n\n\n## Month\n\n@docs monthNumber, monthSuffix, monthFixed, monthNameAbbreviated, monthNameFull\n\n\n## Day of the Month\n\n@docs dayOfMonthNumber, dayOfMonthSuffix, dayOfMonthFixed\n\n\n## Day of the Year\n\n@docs dayOfYearNumber, dayOfYearSuffix, dayOfYearFixed\n\n\n## Day of the Week\n\n@docs dayOfWeekNumber, dayOfWeekSuffix, dayOfWeekNameAbbreviated, dayOfWeekNameFull\n\n\n## Year\n\n@docs yearNumberLastTwo, yearNumber\n\n\n## Quarter of the Year\n\n@docs quarterNumber, quarterSuffix\n\n\n## Week of the Year\n\n@docs weekOfYearNumber, weekOfYearSuffix, weekOfYearFixed\n\n\n## AM / PM\n\n@docs amPmUppercase, amPmLowercase\n\n\n## Hour\n\n@docs hourMilitaryNumber, hourMilitaryFixed, hourNumber, hourFixed, hourMilitaryFromOneNumber, hourMilitaryFromOneFixed\n\n\n## Minute\n\n@docs minuteNumber, minuteFixed\n\n\n## Second\n\n@docs secondNumber, secondFixed\n\n\n## Millisecond\n\n@docs millisecondNumber, millisecondFixed\n\n\n## Other Stuff\n\n@docs text\n\n","unions":[{"name":"Token","comment":" These are the available tokens to help you format dates.\n","args":[],"cases":[]}],"aliases":[],"values":[{"name":"amPmLowercase","comment":" Get the AM / PM value of the hour, in uppercase.\n\nExamples: `am, pm`\n\n","type":"DateFormat.Token"},{"name":"amPmUppercase","comment":" Get the AM / PM value of the hour, in uppercase.\n\nExamples: `AM, PM`\n\n","type":"DateFormat.Token"},{"name":"dayOfMonthFixed","comment":" Get the numeric value of the day of the month, fixed to two places.\n\nExamples: `01, 02, 03, ... 30, 31`\n\n","type":"DateFormat.Token"},{"name":"dayOfMonthNumber","comment":" Get the numeric value of the day of the month.\n\nExamples: `1, 2, 3, ... 30, 31`\n\n","type":"DateFormat.Token"},{"name":"dayOfMonthSuffix","comment":" Get the numeric value of the day of the month, with a suffix at the end.\n\nExamples: `1st, 2nd, 3rd, ... 30th, 31st`\n\n","type":"DateFormat.Token"},{"name":"dayOfWeekNameAbbreviated","comment":" Gets the name of the day of the week, but just the first two letters.\n\nExamples: `Su, Mo, Tu, ... Fr, Sa`\n\n","type":"DateFormat.Token"},{"name":"dayOfWeekNameFull","comment":" Gets the full name of the day of the week.\n\nExamples: `Sunday, Monday, ... Friday, Saturday`\n\n","type":"DateFormat.Token"},{"name":"dayOfWeekNumber","comment":" Get the numeric value of the day of the week.\n\nExamples: `0, 1, 2, ... 5, 6`\n\n","type":"DateFormat.Token"},{"name":"dayOfWeekSuffix","comment":" Get the numeric value of the day of the week, with a suffix at the end.\n\nExamples: `0th, 1st, 2nd, ... 5th, 6th`\n\n","type":"DateFormat.Token"},{"name":"dayOfYearFixed","comment":" Get the numeric value of the day of the year, fixed to three places.\n\nExamples: `001, 002, 003, ... 364, 365`\n\n","type":"DateFormat.Token"},{"name":"dayOfYearNumber","comment":" Get the numeric value of the day of the year.\n\nExamples: `1, 2, 3, ... 364, 365`\n\n","type":"DateFormat.Token"},{"name":"dayOfYearSuffix","comment":" Get the numeric value of the day of the year, with a suffix at the end.\n\nExamples: `1st, 2nd, 3rd, ... 364th, 365th`\n\n","type":"DateFormat.Token"},{"name":"format","comment":" This function takes in a list of tokens, [`Zone`](/packages/elm-lang/time/latest/Time#Zone), and [`Posix`](/packages/elm-lang/time/latest/Time#Posix) to create your formatted string!\n\nLet's say `ourPosixValue` is November 15, 1993 at 15:06.\n\n -- \"15:06\"\n format\n [ hourMilitaryFixed\n , text \":\"\n , minuteFixed\n ]\n utc\n ourPosixValue\n\n\n -- \"3:06 pm\"\n format\n [ hourNumber\n , text \":\"\n , minuteFixed\n , text \" \"\n , amPmLowercase\n ]\n utc\n ourPosixValue\n\n\n -- \"Nov 15th, 1993\"\n format\n [ monthNameFirstThree\n , text \" \"\n , dayOfMonthSuffix\n , text \", \"\n , yearNumber\n ]\n utc\n ourPosixValue\n\n","type":"List.List DateFormat.Token -> Time.Zone -> Time.Posix -> String.String"},{"name":"formatWithLanguage","comment":" If our users don't speak English, printing out \"Monday\" or \"Tuesday\" might not be a great fit.\n\nThanks to a great recommendation, `date-format` now supports multilingual output!\n\nAll you need to do is provide your own options, and format will use your preferences instead:\n\nFor a complete example, check out the [`FormatWithOptions.elm` in the examples folder](https://github.com/ryan-haskell/date-format/blob/master/examples/FormatWithOptions.elm).\n\n","type":"DateFormat.Language.Language -> List.List DateFormat.Token -> Time.Zone -> Time.Posix -> String.String"},{"name":"hourFixed","comment":" Get the hour of the 12-hour day, fixed to two places.\n\nExamples: `00, 01, 02, ... 11, 12`\n\n","type":"DateFormat.Token"},{"name":"hourMilitaryFixed","comment":" Get the hour of the 24-hour day, fixed to two places.\n\nExamples: `00, 01, 02, ... 22, 23`\n\n","type":"DateFormat.Token"},{"name":"hourMilitaryFromOneFixed","comment":" Get the hour of the 24-hour day, starting from one, fixed to two places.\n\nExamples: `01, 02, ... 23, 24`\n\n","type":"DateFormat.Token"},{"name":"hourMilitaryFromOneNumber","comment":" Get the hour of the 24-hour day, starting from one.\n\nExamples: `1, 2, ... 23, 24`\n\n","type":"DateFormat.Token"},{"name":"hourMilitaryNumber","comment":" Get the hour of the 24-hour day.\n\nExamples: `0, 1, 2, ... 22, 23`\n\n","type":"DateFormat.Token"},{"name":"hourNumber","comment":" Get the hour of the 12-hour day.\n\nExamples: `0, 1, 2, ... 11, 12`\n\n","type":"DateFormat.Token"},{"name":"millisecondFixed","comment":" Get the milliseconds of the second, fixed to three places.\n\nExamples: `000, 001, 002, ... 998, 999`\n\n","type":"DateFormat.Token"},{"name":"millisecondNumber","comment":" Get the milliseconds of the second.\n\nExamples: `0, 1, 2, ... 998, 999`\n\n","type":"DateFormat.Token"},{"name":"minuteFixed","comment":" Get the minute of the hour, fixed to two places.\n\nExamples: `00, 01, 02, ... 58, 59`\n\n","type":"DateFormat.Token"},{"name":"minuteNumber","comment":" Get the minute of the hour.\n\nExamples: `0, 1, 2, ... 58, 59`\n\n","type":"DateFormat.Token"},{"name":"monthFixed","comment":" Get the numeric value of the month, fixed to two places.\n\nExamples: `01, 02, 03, ... 11, 12`\n\n","type":"DateFormat.Token"},{"name":"monthNameAbbreviated","comment":" Get the name of the month, but abbreviated (abbreviation function comes from the language\nsettings)\n\nExamples: `Jan, Feb, Mar, ... Nov, Dec`\n\n","type":"DateFormat.Token"},{"name":"monthNameFull","comment":" Get the full name of the month.\n\nExamples: `January, February, ... December`\n\n","type":"DateFormat.Token"},{"name":"monthNumber","comment":" Get the numeric value of the month.\n\nExamples: `1, 2, 3, ... 11, 12`\n\n","type":"DateFormat.Token"},{"name":"monthSuffix","comment":" Get the numeric value of the month, with a suffix at the end.\n\nExamples: `1st, 2nd, 3rd, ... 11th, 12th`\n\n","type":"DateFormat.Token"},{"name":"quarterNumber","comment":" Get the numeric value for the quarter of the year.\n\nExamples: `1, 2, 3, 4`\n\n","type":"DateFormat.Token"},{"name":"quarterSuffix","comment":" Get the numeric value for the quarter of the year, with a suffix.\n\nExamples: `1st, 2nd, 3rd, 4th`\n\n","type":"DateFormat.Token"},{"name":"secondFixed","comment":" Get the second of the minute, fixed to two places.\n\nExamples: `00, 01, 02, ... 58, 59`\n\n","type":"DateFormat.Token"},{"name":"secondNumber","comment":" Get the second of the minute.\n\nExamples: `0, 1, 2, ... 58, 59`\n\n","type":"DateFormat.Token"},{"name":"text","comment":" Represent a string value\n\n formatter : Zone -> Posix -> String\n formatter =\n DateFormat.format\n [ DateFormat.hourMilitaryFixed\n , DateFormat.text \":\"\n , DateFormat.minuteFixed\n ]\n\nWhen given a [`Zone`](/packages/elm-lang/time/latest/Time#Zone) and [`Posix`](/packages/elm-lang/time/latest/Time#Posix), this will return something like `\"23:15\"` or `\"04:43\"`\n\n","type":"String.String -> DateFormat.Token"},{"name":"weekOfYearFixed","comment":" Get the numeric value for the week of the year, fixed to two places.\n\nExamples: `01, 02, 03, ... 51, 52`\n\n","type":"DateFormat.Token"},{"name":"weekOfYearNumber","comment":" Get the numeric value for the week of the year.\n\nExamples: `1, 2, 3, ... 51, 52`\n\n","type":"DateFormat.Token"},{"name":"weekOfYearSuffix","comment":" Get the numeric value for the week of the year, with a suffix at the end.\n\nExamples: `1st, 2nd, 3rd, ... 51st, 52nd`\n\n","type":"DateFormat.Token"},{"name":"yearNumber","comment":" Get the year.\n\nExamples: `1970, 1971, ... 2018, ... 9999, ...`\n\n","type":"DateFormat.Token"},{"name":"yearNumberLastTwo","comment":" Get the year, but just the last two letters.\n\nExamples: `70, 71, ... 29, 30`\n\n","type":"DateFormat.Token"}],"binops":[]},{"name":"DateFormat.Language","comment":"\n\n\n## Fun fact: Some people don't know english.\n\nThat's why it's important to include alternative date formatting options for other languages!\n\nThis module exposes `Language`, along with a few implementations.\n\n(If you want to see `french`, `german`, or `greek`, please add them in! I'm happy to make your language a part of the package!)\n\n\n### Language\n\n@docs Language\n\n\n### Languages\n\n@docs english, spanish\n\n","unions":[],"aliases":[{"name":"Language","comment":" A record with options for your language.\n","args":[],"type":"{ toMonthName : Time.Month -> String.String, toMonthAbbreviation : Time.Month -> String.String, toWeekdayName : Time.Weekday -> String.String, toWeekdayAbbreviation : Time.Weekday -> String.String, toAmPm : Basics.Int -> String.String, toOrdinalSuffix : Basics.Int -> String.String }"}],"values":[{"name":"english","comment":" The english language! (used by default)\n","type":"DateFormat.Language.Language"},{"name":"spanish","comment":" The spanish language!\n","type":"DateFormat.Language.Language"}],"binops":[]},{"name":"DateFormat.Relative","comment":" A reliable way to get a pretty message for the relative time difference between two dates.\n\n\n# Getting relative time for two dates\n\n@docs relativeTime, relativeTimeWithOptions, RelativeTimeOptions, defaultRelativeOptions\n\n","unions":[],"aliases":[{"name":"RelativeTimeOptions","comment":" Options for configuring your own relative message formats!\n\nFor example, here is how `someSecondsAgo` is implemented by default:\n\n defaultSomeSecondsAgo : Int -> String\n defaultSomeSecondsAgo seconds =\n if seconds < 30 then\n \"just now\"\n\n else\n toString seconds ++ \" seconds ago\"\n\nAnd here is how `inSomeHours` might look:\n\n defaultInSomeHours : Int -> String\n defaultInSomeHours hours =\n if hours < 2 then\n \"in an hour\"\n\n else\n \"in \" ++ toString hours ++ \" hours\"\n\n","args":[],"type":"{ someSecondsAgo : Basics.Int -> String.String, someMinutesAgo : Basics.Int -> String.String, someHoursAgo : Basics.Int -> String.String, someDaysAgo : Basics.Int -> String.String, someMonthsAgo : Basics.Int -> String.String, someYearsAgo : Basics.Int -> String.String, rightNow : String.String, inSomeSeconds : Basics.Int -> String.String, inSomeMinutes : Basics.Int -> String.String, inSomeHours : Basics.Int -> String.String, inSomeDays : Basics.Int -> String.String, inSomeMonths : Basics.Int -> String.String, inSomeYears : Basics.Int -> String.String }"}],"values":[{"name":"defaultRelativeOptions","comment":" If there is something you'd like to tweak based off of the defaults, this record might be a good starting point!\n","type":"DateFormat.Relative.RelativeTimeOptions"},{"name":"relativeTime","comment":" This function takes in two times and returns the relative difference!\n\nHere are a few examples to help:\n\n relativeTime now tenSecondsAgo == \"just now\"\n\n relativeTime now tenSecondsFromNow == \"in a few seconds\"\n\n relativeTime now fortyThreeMinutesAgo == \"43 minutes ago\"\n\n relativeTime now oneHundredDaysAgo == \"100 days ago\"\n\n relativeTime now oneHundredDaysFromNow == \"in 100 days\"\n\n\n -- Order matters!\n relativeTime now tenSecondsAgo == \"just now\"\n\n relativeTime tenSecondsAgo now == \"in a few seconds\"\n\n","type":"Time.Posix -> Time.Posix -> String.String"},{"name":"relativeTimeWithOptions","comment":" Maybe `relativeTime` is too lame. (Or maybe you speak a different language than English!)\n\nWith `relativeTimeWithOptions`, you can provide your own custom messages for each time range.\n\n(That's what `relativeTime` uses under the hood!)\n\nYou can provide a set of your own custom options, and use `relativeTimeWithOptions` instead.\n\n","type":"DateFormat.Relative.RelativeTimeOptions -> Time.Posix -> Time.Posix -> String.String"}],"binops":[]}] -------------------------------------------------------------------------------- /elm.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "package", 3 | "name": "ryan-haskell/date-format", 4 | "summary": "A reliable way to format dates and times with Elm.", 5 | "license": "BSD-3-Clause", 6 | "version": "1.0.0", 7 | "exposed-modules": [ 8 | "DateFormat", 9 | "DateFormat.Language", 10 | "DateFormat.Relative" 11 | ], 12 | "elm-version": "0.19.0 <= v < 0.20.0", 13 | "dependencies": { 14 | "elm/core": "1.0.0 <= v < 2.0.0", 15 | "elm/time": "1.0.0 <= v < 2.0.0" 16 | }, 17 | "test-dependencies": { 18 | "elm-explorations/test": "1.0.0 <= v < 2.0.0" 19 | } 20 | } -------------------------------------------------------------------------------- /examples/Basic.elm: -------------------------------------------------------------------------------- 1 | module Basic exposing (main) 2 | 3 | import Browser exposing (sandbox) 4 | import DateFormat 5 | import Html exposing (text) 6 | import Time exposing (Posix, Zone, utc) 7 | 8 | 9 | 10 | -- Let's create a custom formatter we can use later: 11 | 12 | 13 | ourFormatter : Zone -> Posix -> String 14 | ourFormatter = 15 | DateFormat.format 16 | [ DateFormat.monthNameFull 17 | , DateFormat.text " " 18 | , DateFormat.dayOfMonthSuffix 19 | , DateFormat.text ", " 20 | , DateFormat.yearNumber 21 | ] 22 | 23 | 24 | 25 | -- With our formatter, we can format any date as a string! 26 | 27 | 28 | ourTimezone : Zone 29 | ourTimezone = 30 | utc 31 | 32 | 33 | 34 | -- 2018-05-20T19:18:24.911Z 35 | 36 | 37 | ourPosixTime : Posix 38 | ourPosixTime = 39 | Time.millisToPosix 1526843861289 40 | 41 | 42 | ourPrettyDate : String 43 | ourPrettyDate = 44 | ourFormatter ourTimezone ourPosixTime 45 | 46 | 47 | 48 | -- Show on the screen 49 | 50 | 51 | main = 52 | sandbox 53 | { init = Nothing 54 | , update = \_ _ -> Nothing 55 | , view = \_ -> text ourPrettyDate 56 | } 57 | -------------------------------------------------------------------------------- /examples/Language.elm: -------------------------------------------------------------------------------- 1 | module Language exposing (main) 2 | 3 | import Browser exposing (sandbox) 4 | import DateFormat 5 | import DateFormat.Language exposing (Language, spanish) 6 | import Html exposing (div, p, strong, text) 7 | import Time exposing (Month(..), Posix, Weekday(..), Zone, utc) 8 | 9 | 10 | tokens : List DateFormat.Token 11 | tokens = 12 | [ DateFormat.monthNameFull 13 | , DateFormat.text " " 14 | , DateFormat.dayOfMonthSuffix 15 | , DateFormat.text ", " 16 | , DateFormat.yearNumber 17 | , DateFormat.text " (" 18 | , DateFormat.dayOfWeekNameFull 19 | , DateFormat.text ")" 20 | ] 21 | 22 | 23 | defaultFormatter : Zone -> Posix -> String 24 | defaultFormatter = 25 | DateFormat.format tokens 26 | 27 | 28 | spanishFormatter : Zone -> Posix -> String 29 | spanishFormatter = 30 | DateFormat.formatWithLanguage spanish tokens 31 | 32 | 33 | 34 | -- With our formatter, we can format any date as a string! 35 | 36 | 37 | ourTimezone : Zone 38 | ourTimezone = 39 | utc 40 | 41 | 42 | 43 | -- 2018-05-20T19:18:24.911Z 44 | 45 | 46 | ourPosixTime : Posix 47 | ourPosixTime = 48 | Time.millisToPosix 1526843861289 49 | 50 | 51 | ourDefaultDate : String 52 | ourDefaultDate = 53 | defaultFormatter ourTimezone ourPosixTime 54 | 55 | 56 | ourSpanishDate : String 57 | ourSpanishDate = 58 | spanishFormatter ourTimezone ourPosixTime 59 | 60 | 61 | 62 | -- Show on the screen 63 | 64 | 65 | main = 66 | sandbox 67 | { init = Nothing 68 | , update = \_ _ -> Nothing 69 | , view = 70 | \_ -> 71 | div [] 72 | [ p [] 73 | [ strong [] [ text "Default: " ] 74 | , text ourDefaultDate 75 | ] 76 | , p [] 77 | [ strong [] [ text "Spanish: " ] 78 | , text ourSpanishDate 79 | ] 80 | ] 81 | } 82 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # `date-format` examples 2 | 3 | ### Examples 4 | 5 | - __[`Basic.elm`](https://github.com/ryan-haskell/date-format/blob/master/examples/Basic.elm)__ - An example of formatting a single time. 6 | 7 | - __[`Language.elm`](https://github.com/ryan-haskell/date-format/blob/master/examples/Language.elm)__ - An example of formatting a single time in another language. 8 | 9 | - __[`Relative.elm`](https://github.com/ryan-haskell/date-format/blob/master/examples/Relative.elm)__ - An example with relative times. 10 | 11 | 12 | 13 | ### Local Setup 14 | 15 | Here's how you can try them out: 16 | 17 | 1. `git clone https://github.com/ryan-haskell/date-format` 18 | 19 | 1. `cd date-format/examples` 20 | 21 | 1. `elm reactor` 22 | 23 | 1. Go to [http://localhost:8000](http://localhost:8000) 24 | -------------------------------------------------------------------------------- /examples/Relative.elm: -------------------------------------------------------------------------------- 1 | module Relative exposing (main) 2 | 3 | import Browser 4 | import DateFormat.Relative as Relative 5 | import Html exposing (..) 6 | import Task 7 | import Time exposing (Posix, Zone, utc) 8 | 9 | 10 | main = 11 | Browser.element 12 | { init = init 13 | , view = view 14 | , update = update 15 | , subscriptions = subscriptions 16 | } 17 | 18 | 19 | type alias Model = 20 | { timeSinceExample : Posix 21 | , initialTime : Maybe Posix 22 | , now : Maybe Posix 23 | } 24 | 25 | 26 | init : () -> ( Model, Cmd Msg ) 27 | init _ = 28 | ( Model 29 | (Time.millisToPosix 1526852818792) 30 | Nothing 31 | Nothing 32 | , Task.perform SetInitialTime Time.now 33 | ) 34 | 35 | 36 | view : Model -> Html Msg 37 | view model = 38 | div [] 39 | [ h3 [] [ text "Model" ] 40 | , p [] [ text <| Debug.toString model ] 41 | , case ( model.initialTime, model.now ) of 42 | ( Just initialTime, Just now ) -> 43 | p [] 44 | [ strong [] [ text "Time since you loaded this page: " ] 45 | , span [] [ text <| Relative.relativeTime now initialTime ] 46 | ] 47 | 48 | ( _, _ ) -> 49 | p [] [ text "Getting times..." ] 50 | , case model.now of 51 | Just now -> 52 | p [] 53 | [ strong [] [ text "Time since Ryan wrote this example: " ] 54 | , span [] [ text <| Relative.relativeTime now model.timeSinceExample ] 55 | ] 56 | 57 | Nothing -> 58 | p [] [ text "Getting now time..." ] 59 | ] 60 | 61 | 62 | type Msg 63 | = SetInitialTime Posix 64 | | SetNow Posix 65 | 66 | 67 | update : Msg -> Model -> ( Model, Cmd Msg ) 68 | update msg model = 69 | case msg of 70 | SetInitialTime initialTime -> 71 | ( { model 72 | | initialTime = Just initialTime 73 | , now = Just initialTime 74 | } 75 | , Cmd.none 76 | ) 77 | 78 | SetNow now -> 79 | ( { model | now = Just now }, Cmd.none ) 80 | 81 | 82 | subscriptions : Model -> Sub Msg 83 | subscriptions model = 84 | Time.every 1000 SetNow 85 | -------------------------------------------------------------------------------- /examples/elm.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "application", 3 | "source-directories": [ 4 | ".", 5 | "../src" 6 | ], 7 | "elm-version": "0.19.0", 8 | "dependencies": { 9 | "direct": { 10 | "elm/browser": "1.0.0", 11 | "elm/core": "1.0.0", 12 | "elm/html": "1.0.0", 13 | "elm/time": "1.0.0" 14 | }, 15 | "indirect": { 16 | "elm/json": "1.0.0", 17 | "elm/url": "1.0.0", 18 | "elm/virtual-dom": "1.0.2" 19 | } 20 | }, 21 | "test-dependencies": { 22 | "direct": {}, 23 | "indirect": {} 24 | } 25 | } -------------------------------------------------------------------------------- /src/DateFormat.elm: -------------------------------------------------------------------------------- 1 | module DateFormat exposing 2 | ( format 3 | , formatWithLanguage 4 | , Token 5 | , monthNumber, monthSuffix, monthFixed, monthNameAbbreviated, monthNameFull 6 | , dayOfMonthNumber, dayOfMonthSuffix, dayOfMonthFixed 7 | , dayOfYearNumber, dayOfYearSuffix, dayOfYearFixed 8 | , dayOfWeekNumber, dayOfWeekSuffix, dayOfWeekNameAbbreviated, dayOfWeekNameFull 9 | , yearNumberLastTwo, yearNumber 10 | , quarterNumber, quarterSuffix 11 | , weekOfYearNumber, weekOfYearSuffix, weekOfYearFixed 12 | , amPmUppercase, amPmLowercase 13 | , hourMilitaryNumber, hourMilitaryFixed, hourNumber, hourFixed, hourMilitaryFromOneNumber, hourMilitaryFromOneFixed 14 | , minuteNumber, minuteFixed 15 | , secondNumber, secondFixed 16 | , millisecondNumber, millisecondFixed 17 | , text 18 | ) 19 | 20 | {-| A reliable way to format dates and times with Elm. 21 | 22 | 23 | # Formatting dates 24 | 25 | @docs format 26 | 27 | 28 | # Supporting a different language? 29 | 30 | @docs formatWithLanguage 31 | 32 | 33 | # Available formatting options 34 | 35 | @docs Token 36 | 37 | 38 | ## Month 39 | 40 | @docs monthNumber, monthSuffix, monthFixed, monthNameAbbreviated, monthNameFull 41 | 42 | 43 | ## Day of the Month 44 | 45 | @docs dayOfMonthNumber, dayOfMonthSuffix, dayOfMonthFixed 46 | 47 | 48 | ## Day of the Year 49 | 50 | @docs dayOfYearNumber, dayOfYearSuffix, dayOfYearFixed 51 | 52 | 53 | ## Day of the Week 54 | 55 | @docs dayOfWeekNumber, dayOfWeekSuffix, dayOfWeekNameAbbreviated, dayOfWeekNameFull 56 | 57 | 58 | ## Year 59 | 60 | @docs yearNumberLastTwo, yearNumber 61 | 62 | 63 | ## Quarter of the Year 64 | 65 | @docs quarterNumber, quarterSuffix 66 | 67 | 68 | ## Week of the Year 69 | 70 | @docs weekOfYearNumber, weekOfYearSuffix, weekOfYearFixed 71 | 72 | 73 | ## AM / PM 74 | 75 | @docs amPmUppercase, amPmLowercase 76 | 77 | 78 | ## Hour 79 | 80 | @docs hourMilitaryNumber, hourMilitaryFixed, hourNumber, hourFixed, hourMilitaryFromOneNumber, hourMilitaryFromOneFixed 81 | 82 | 83 | ## Minute 84 | 85 | @docs minuteNumber, minuteFixed 86 | 87 | 88 | ## Second 89 | 90 | @docs secondNumber, secondFixed 91 | 92 | 93 | ## Millisecond 94 | 95 | @docs millisecondNumber, millisecondFixed 96 | 97 | 98 | ## Other Stuff 99 | 100 | @docs text 101 | 102 | -} 103 | 104 | import DateFormat.Language exposing (Language, english) 105 | import Time 106 | exposing 107 | ( Month(..) 108 | , Posix 109 | , Weekday(..) 110 | , Zone 111 | ) 112 | 113 | 114 | {-| Get the numeric value of the month. 115 | 116 | Examples: `1, 2, 3, ... 11, 12` 117 | 118 | -} 119 | monthNumber : Token 120 | monthNumber = 121 | MonthNumber 122 | 123 | 124 | {-| Get the numeric value of the month, with a suffix at the end. 125 | 126 | Examples: `1st, 2nd, 3rd, ... 11th, 12th` 127 | 128 | -} 129 | monthSuffix : Token 130 | monthSuffix = 131 | MonthSuffix 132 | 133 | 134 | {-| Get the numeric value of the month, fixed to two places. 135 | 136 | Examples: `01, 02, 03, ... 11, 12` 137 | 138 | -} 139 | monthFixed : Token 140 | monthFixed = 141 | MonthFixed 142 | 143 | 144 | {-| Get the name of the month, but abbreviated (abbreviation function comes from the language 145 | settings) 146 | 147 | Examples: `Jan, Feb, Mar, ... Nov, Dec` 148 | 149 | -} 150 | monthNameAbbreviated : Token 151 | monthNameAbbreviated = 152 | MonthNameAbbreviated 153 | 154 | 155 | {-| Get the full name of the month. 156 | 157 | Examples: `January, February, ... December` 158 | 159 | -} 160 | monthNameFull : Token 161 | monthNameFull = 162 | MonthNameFull 163 | 164 | 165 | {-| Get the numeric value of the day of the month. 166 | 167 | Examples: `1, 2, 3, ... 30, 31` 168 | 169 | -} 170 | dayOfMonthNumber : Token 171 | dayOfMonthNumber = 172 | DayOfMonthNumber 173 | 174 | 175 | {-| Get the numeric value of the day of the month, with a suffix at the end. 176 | 177 | Examples: `1st, 2nd, 3rd, ... 30th, 31st` 178 | 179 | -} 180 | dayOfMonthSuffix : Token 181 | dayOfMonthSuffix = 182 | DayOfMonthSuffix 183 | 184 | 185 | {-| Get the numeric value of the day of the month, fixed to two places. 186 | 187 | Examples: `01, 02, 03, ... 30, 31` 188 | 189 | -} 190 | dayOfMonthFixed : Token 191 | dayOfMonthFixed = 192 | DayOfMonthFixed 193 | 194 | 195 | {-| Get the numeric value of the day of the year. 196 | 197 | Examples: `1, 2, 3, ... 364, 365` 198 | 199 | -} 200 | dayOfYearNumber : Token 201 | dayOfYearNumber = 202 | DayOfYearNumber 203 | 204 | 205 | {-| Get the numeric value of the day of the year, with a suffix at the end. 206 | 207 | Examples: `1st, 2nd, 3rd, ... 364th, 365th` 208 | 209 | -} 210 | dayOfYearSuffix : Token 211 | dayOfYearSuffix = 212 | DayOfYearSuffix 213 | 214 | 215 | {-| Get the numeric value of the day of the year, fixed to three places. 216 | 217 | Examples: `001, 002, 003, ... 364, 365` 218 | 219 | -} 220 | dayOfYearFixed : Token 221 | dayOfYearFixed = 222 | DayOfYearFixed 223 | 224 | 225 | {-| Get the numeric value of the day of the week. 226 | 227 | Examples: `0, 1, 2, ... 5, 6` 228 | 229 | -} 230 | dayOfWeekNumber : Token 231 | dayOfWeekNumber = 232 | DayOfWeekNumber 233 | 234 | 235 | {-| Get the numeric value of the day of the week, with a suffix at the end. 236 | 237 | Examples: `0th, 1st, 2nd, ... 5th, 6th` 238 | 239 | -} 240 | dayOfWeekSuffix : Token 241 | dayOfWeekSuffix = 242 | DayOfWeekSuffix 243 | 244 | 245 | {-| Gets the name of the day of the week, but just the first two letters. 246 | 247 | Examples: `Su, Mo, Tu, ... Fr, Sa` 248 | 249 | -} 250 | dayOfWeekNameAbbreviated : Token 251 | dayOfWeekNameAbbreviated = 252 | DayOfWeekNameAbbreviated 253 | 254 | 255 | {-| Gets the full name of the day of the week. 256 | 257 | Examples: `Sunday, Monday, ... Friday, Saturday` 258 | 259 | -} 260 | dayOfWeekNameFull : Token 261 | dayOfWeekNameFull = 262 | DayOfWeekNameFull 263 | 264 | 265 | {-| Get the year, but just the last two letters. 266 | 267 | Examples: `70, 71, ... 29, 30` 268 | 269 | -} 270 | yearNumberLastTwo : Token 271 | yearNumberLastTwo = 272 | YearNumberLastTwo 273 | 274 | 275 | {-| Get the year. 276 | 277 | Examples: `1970, 1971, ... 2018, ... 9999, ...` 278 | 279 | -} 280 | yearNumber : Token 281 | yearNumber = 282 | YearNumber 283 | 284 | 285 | {-| Get the numeric value for the quarter of the year. 286 | 287 | Examples: `1, 2, 3, 4` 288 | 289 | -} 290 | quarterNumber : Token 291 | quarterNumber = 292 | QuarterNumber 293 | 294 | 295 | {-| Get the numeric value for the quarter of the year, with a suffix. 296 | 297 | Examples: `1st, 2nd, 3rd, 4th` 298 | 299 | -} 300 | quarterSuffix : Token 301 | quarterSuffix = 302 | QuarterSuffix 303 | 304 | 305 | {-| Get the numeric value for the week of the year. 306 | 307 | Examples: `1, 2, 3, ... 51, 52` 308 | 309 | -} 310 | weekOfYearNumber : Token 311 | weekOfYearNumber = 312 | WeekOfYearNumber 313 | 314 | 315 | {-| Get the numeric value for the week of the year, with a suffix at the end. 316 | 317 | Examples: `1st, 2nd, 3rd, ... 51st, 52nd` 318 | 319 | -} 320 | weekOfYearSuffix : Token 321 | weekOfYearSuffix = 322 | WeekOfYearSuffix 323 | 324 | 325 | {-| Get the numeric value for the week of the year, fixed to two places. 326 | 327 | Examples: `01, 02, 03, ... 51, 52` 328 | 329 | -} 330 | weekOfYearFixed : Token 331 | weekOfYearFixed = 332 | WeekOfYearFixed 333 | 334 | 335 | {-| Get the AM / PM value of the hour, in uppercase. 336 | 337 | Examples: `AM, PM` 338 | 339 | -} 340 | amPmUppercase : Token 341 | amPmUppercase = 342 | AmPmUppercase 343 | 344 | 345 | {-| Get the AM / PM value of the hour, in uppercase. 346 | 347 | Examples: `am, pm` 348 | 349 | -} 350 | amPmLowercase : Token 351 | amPmLowercase = 352 | AmPmLowercase 353 | 354 | 355 | {-| Get the hour of the 24-hour day. 356 | 357 | Examples: `0, 1, 2, ... 22, 23` 358 | 359 | -} 360 | hourMilitaryNumber : Token 361 | hourMilitaryNumber = 362 | HourMilitaryNumber 363 | 364 | 365 | {-| Get the hour of the 24-hour day, fixed to two places. 366 | 367 | Examples: `00, 01, 02, ... 22, 23` 368 | 369 | -} 370 | hourMilitaryFixed : Token 371 | hourMilitaryFixed = 372 | HourMilitaryFixed 373 | 374 | 375 | {-| Get the hour of the 12-hour day. 376 | 377 | Examples: `0, 1, 2, ... 11, 12` 378 | 379 | -} 380 | hourNumber : Token 381 | hourNumber = 382 | HourNumber 383 | 384 | 385 | {-| Get the hour of the 12-hour day, fixed to two places. 386 | 387 | Examples: `00, 01, 02, ... 11, 12` 388 | 389 | -} 390 | hourFixed : Token 391 | hourFixed = 392 | HourFixed 393 | 394 | 395 | {-| Get the hour of the 24-hour day, starting from one. 396 | 397 | Examples: `1, 2, ... 23, 24` 398 | 399 | -} 400 | hourMilitaryFromOneNumber : Token 401 | hourMilitaryFromOneNumber = 402 | HourMilitaryFromOneNumber 403 | 404 | 405 | {-| Get the hour of the 24-hour day, starting from one, fixed to two places. 406 | 407 | Examples: `01, 02, ... 23, 24` 408 | 409 | -} 410 | hourMilitaryFromOneFixed : Token 411 | hourMilitaryFromOneFixed = 412 | HourMilitaryFromOneFixed 413 | 414 | 415 | {-| Get the minute of the hour. 416 | 417 | Examples: `0, 1, 2, ... 58, 59` 418 | 419 | -} 420 | minuteNumber : Token 421 | minuteNumber = 422 | MinuteNumber 423 | 424 | 425 | {-| Get the minute of the hour, fixed to two places. 426 | 427 | Examples: `00, 01, 02, ... 58, 59` 428 | 429 | -} 430 | minuteFixed : Token 431 | minuteFixed = 432 | MinuteFixed 433 | 434 | 435 | {-| Get the second of the minute. 436 | 437 | Examples: `0, 1, 2, ... 58, 59` 438 | 439 | -} 440 | secondNumber : Token 441 | secondNumber = 442 | SecondNumber 443 | 444 | 445 | {-| Get the second of the minute, fixed to two places. 446 | 447 | Examples: `00, 01, 02, ... 58, 59` 448 | 449 | -} 450 | secondFixed : Token 451 | secondFixed = 452 | SecondFixed 453 | 454 | 455 | {-| Get the milliseconds of the second. 456 | 457 | Examples: `0, 1, 2, ... 998, 999` 458 | 459 | -} 460 | millisecondNumber : Token 461 | millisecondNumber = 462 | MillisecondNumber 463 | 464 | 465 | {-| Get the milliseconds of the second, fixed to three places. 466 | 467 | Examples: `000, 001, 002, ... 998, 999` 468 | 469 | -} 470 | millisecondFixed : Token 471 | millisecondFixed = 472 | MillisecondFixed 473 | 474 | 475 | {-| Represent a string value 476 | 477 | formatter : Zone -> Posix -> String 478 | formatter = 479 | DateFormat.format 480 | [ DateFormat.hourMilitaryFixed 481 | , DateFormat.text ":" 482 | , DateFormat.minuteFixed 483 | ] 484 | 485 | When given a [`Zone`](/packages/elm-lang/time/latest/Time#Zone) and [`Posix`](/packages/elm-lang/time/latest/Time#Posix), this will return something like `"23:15"` or `"04:43"` 486 | 487 | -} 488 | text : String -> Token 489 | text = 490 | Text 491 | 492 | 493 | {-| These are the available tokens to help you format dates. 494 | -} 495 | type Token 496 | = MonthNumber 497 | | MonthSuffix 498 | | MonthFixed 499 | | MonthNameAbbreviated 500 | | MonthNameFull 501 | | DayOfMonthNumber 502 | | DayOfMonthSuffix 503 | | DayOfMonthFixed 504 | | DayOfYearNumber 505 | | DayOfYearSuffix 506 | | DayOfYearFixed 507 | | DayOfWeekNumber 508 | | DayOfWeekSuffix 509 | | DayOfWeekNameAbbreviated 510 | | DayOfWeekNameFull 511 | | YearNumberLastTwo 512 | | YearNumber 513 | | QuarterNumber 514 | | QuarterSuffix 515 | | WeekOfYearNumber 516 | | WeekOfYearSuffix 517 | | WeekOfYearFixed 518 | | AmPmUppercase 519 | | AmPmLowercase 520 | | HourMilitaryNumber 521 | | HourMilitaryFixed 522 | | HourNumber 523 | | HourFixed 524 | | HourMilitaryFromOneNumber 525 | | HourMilitaryFromOneFixed 526 | | MinuteNumber 527 | | MinuteFixed 528 | | SecondNumber 529 | | SecondFixed 530 | | MillisecondNumber 531 | | MillisecondFixed 532 | | Text String 533 | 534 | 535 | {-| This function takes in a list of tokens, [`Zone`](/packages/elm-lang/time/latest/Time#Zone), and [`Posix`](/packages/elm-lang/time/latest/Time#Posix) to create your formatted string! 536 | 537 | Let's say `ourPosixValue` is November 15, 1993 at 15:06. 538 | 539 | -- "15:06" 540 | format 541 | [ hourMilitaryFixed 542 | , text ":" 543 | , minuteFixed 544 | ] 545 | utc 546 | ourPosixValue 547 | 548 | -- "3:06 pm" 549 | format 550 | [ hourNumber 551 | , text ":" 552 | , minuteFixed 553 | , text " " 554 | , amPmLowercase 555 | ] 556 | utc 557 | ourPosixValue 558 | 559 | -- "Nov 15th, 1993" 560 | format 561 | [ monthNameAbbreviated 562 | , text " " 563 | , dayOfMonthSuffix 564 | , text ", " 565 | , yearNumber 566 | ] 567 | utc 568 | ourPosixValue 569 | 570 | -} 571 | format : List Token -> Zone -> Posix -> String 572 | format = 573 | formatWithLanguage english 574 | 575 | 576 | {-| If our users don't speak English, printing out "Monday" or "Tuesday" might not be a great fit. 577 | 578 | Thanks to a great recommendation, `date-format` now supports multilingual output! 579 | 580 | All you need to do is provide your own options, and format will use your preferences instead: 581 | 582 | For a complete example, check out the [`FormatWithOptions.elm` in the examples folder](https://github.com/ryan-haskell/date-format/blob/master/examples/FormatWithOptions.elm). 583 | 584 | -} 585 | formatWithLanguage : Language -> List Token -> Zone -> Posix -> String 586 | formatWithLanguage language tokens zone time = 587 | tokens 588 | |> List.map (piece language zone time) 589 | |> String.join "" 590 | 591 | 592 | {-| Months of the year, in the correct order. 593 | -} 594 | months : List Month 595 | months = 596 | [ Jan 597 | , Feb 598 | , Mar 599 | , Apr 600 | , May 601 | , Jun 602 | , Jul 603 | , Aug 604 | , Sep 605 | , Oct 606 | , Nov 607 | , Dec 608 | ] 609 | 610 | 611 | {-| Days of the week, starting with Sunday. 612 | -} 613 | days : List Weekday 614 | days = 615 | [ Sun 616 | , Mon 617 | , Tue 618 | , Wed 619 | , Thu 620 | , Fri 621 | , Sat 622 | ] 623 | 624 | 625 | piece : Language -> Zone -> Posix -> Token -> String 626 | piece language zone posix token = 627 | case token of 628 | MonthNumber -> 629 | monthNumber_ zone posix 630 | |> String.fromInt 631 | 632 | MonthSuffix -> 633 | monthNumber_ zone posix 634 | |> (\num -> String.fromInt num ++ language.toOrdinalSuffix num) 635 | 636 | MonthFixed -> 637 | monthNumber_ zone posix 638 | |> toFixedLength 2 639 | 640 | MonthNameAbbreviated -> 641 | language.toMonthAbbreviation (Time.toMonth zone posix) 642 | 643 | MonthNameFull -> 644 | language.toMonthName (Time.toMonth zone posix) 645 | 646 | QuarterNumber -> 647 | quarter zone posix 648 | |> (+) 1 649 | |> String.fromInt 650 | 651 | QuarterSuffix -> 652 | quarter zone posix 653 | |> (+) 1 654 | |> (\num -> String.fromInt num ++ language.toOrdinalSuffix num) 655 | 656 | DayOfMonthNumber -> 657 | dayOfMonth zone posix 658 | |> String.fromInt 659 | 660 | DayOfMonthSuffix -> 661 | dayOfMonth zone posix 662 | |> (\num -> String.fromInt num ++ language.toOrdinalSuffix num) 663 | 664 | DayOfMonthFixed -> 665 | dayOfMonth zone posix 666 | |> toFixedLength 2 667 | 668 | DayOfYearNumber -> 669 | dayOfYear zone posix 670 | |> String.fromInt 671 | 672 | DayOfYearSuffix -> 673 | dayOfYear zone posix 674 | |> (\num -> String.fromInt num ++ language.toOrdinalSuffix num) 675 | 676 | DayOfYearFixed -> 677 | dayOfYear zone posix 678 | |> toFixedLength 3 679 | 680 | DayOfWeekNumber -> 681 | dayOfWeek zone posix 682 | |> String.fromInt 683 | 684 | DayOfWeekSuffix -> 685 | dayOfWeek zone posix 686 | |> (\num -> String.fromInt num ++ language.toOrdinalSuffix num) 687 | 688 | DayOfWeekNameAbbreviated -> 689 | language.toWeekdayAbbreviation (Time.toWeekday zone posix) 690 | 691 | DayOfWeekNameFull -> 692 | language.toWeekdayName (Time.toWeekday zone posix) 693 | 694 | WeekOfYearNumber -> 695 | weekOfYear zone posix 696 | |> String.fromInt 697 | 698 | WeekOfYearSuffix -> 699 | weekOfYear zone posix 700 | |> (\num -> String.fromInt num ++ language.toOrdinalSuffix num) 701 | 702 | WeekOfYearFixed -> 703 | weekOfYear zone posix 704 | |> toFixedLength 2 705 | 706 | YearNumberLastTwo -> 707 | year zone posix 708 | |> String.right 2 709 | 710 | YearNumber -> 711 | year zone posix 712 | 713 | AmPmUppercase -> 714 | amPm language zone posix 715 | |> String.toUpper 716 | 717 | AmPmLowercase -> 718 | amPm language zone posix 719 | |> String.toLower 720 | 721 | HourMilitaryNumber -> 722 | Time.toHour zone posix 723 | |> String.fromInt 724 | 725 | HourMilitaryFixed -> 726 | Time.toHour zone posix 727 | |> toFixedLength 2 728 | 729 | HourNumber -> 730 | Time.toHour zone posix 731 | |> toNonMilitary 732 | |> String.fromInt 733 | 734 | HourFixed -> 735 | Time.toHour zone posix 736 | |> toNonMilitary 737 | |> toFixedLength 2 738 | 739 | HourMilitaryFromOneNumber -> 740 | Time.toHour zone posix 741 | |> (+) 1 742 | |> String.fromInt 743 | 744 | HourMilitaryFromOneFixed -> 745 | Time.toHour zone posix 746 | |> (+) 1 747 | |> toFixedLength 2 748 | 749 | MinuteNumber -> 750 | Time.toMinute zone posix 751 | |> String.fromInt 752 | 753 | MinuteFixed -> 754 | Time.toMinute zone posix 755 | |> toFixedLength 2 756 | 757 | SecondNumber -> 758 | Time.toSecond zone posix 759 | |> String.fromInt 760 | 761 | SecondFixed -> 762 | Time.toSecond zone posix 763 | |> toFixedLength 2 764 | 765 | MillisecondNumber -> 766 | Time.toMillis zone posix 767 | |> String.fromInt 768 | 769 | MillisecondFixed -> 770 | Time.toMillis zone posix 771 | |> toFixedLength 3 772 | 773 | Text string -> 774 | string 775 | 776 | 777 | 778 | -- MONTHS 779 | 780 | 781 | monthPair : Zone -> Posix -> ( Int, Month ) 782 | monthPair zone posix = 783 | months 784 | |> List.indexedMap (\a b -> ( a, b )) 785 | |> List.filter (\( i, m ) -> m == Time.toMonth zone posix) 786 | |> List.head 787 | |> Maybe.withDefault ( 0, Jan ) 788 | 789 | 790 | monthNumber_ : Zone -> Posix -> Int 791 | monthNumber_ zone posix = 792 | monthPair zone posix 793 | |> (\( i, m ) -> i) 794 | |> (+) 1 795 | 796 | 797 | daysInMonth : Int -> Month -> Int 798 | daysInMonth year_ month = 799 | case month of 800 | Jan -> 801 | 31 802 | 803 | Feb -> 804 | if isLeapYear year_ then 805 | 29 806 | 807 | else 808 | 28 809 | 810 | Mar -> 811 | 31 812 | 813 | Apr -> 814 | 30 815 | 816 | May -> 817 | 31 818 | 819 | Jun -> 820 | 30 821 | 822 | Jul -> 823 | 31 824 | 825 | Aug -> 826 | 31 827 | 828 | Sep -> 829 | 30 830 | 831 | Oct -> 832 | 31 833 | 834 | Nov -> 835 | 30 836 | 837 | Dec -> 838 | 31 839 | 840 | 841 | isLeapYear : Int -> Bool 842 | isLeapYear year_ = 843 | if modBy 4 year_ /= 0 then 844 | False 845 | 846 | else if modBy 100 year_ /= 0 then 847 | True 848 | 849 | else if modBy 400 year_ /= 0 then 850 | False 851 | 852 | else 853 | True 854 | 855 | 856 | 857 | -- QUARTERS 858 | 859 | 860 | quarter : Zone -> Posix -> Int 861 | quarter zone posix = 862 | monthNumber_ zone posix // 4 863 | 864 | 865 | 866 | -- DAY OF MONTH 867 | 868 | 869 | dayOfMonth : Zone -> Posix -> Int 870 | dayOfMonth = 871 | Time.toDay 872 | 873 | 874 | 875 | -- DAY OF YEAR 876 | 877 | 878 | dayOfYear : Zone -> Posix -> Int 879 | dayOfYear zone posix = 880 | let 881 | monthsBeforeThisOne : List Month 882 | monthsBeforeThisOne = 883 | List.take (monthNumber_ zone posix - 1) months 884 | 885 | daysBeforeThisMonth : Int 886 | daysBeforeThisMonth = 887 | monthsBeforeThisOne 888 | |> List.map (daysInMonth (Time.toYear zone posix)) 889 | |> List.sum 890 | in 891 | daysBeforeThisMonth + dayOfMonth zone posix 892 | 893 | 894 | 895 | -- DAY OF WEEK 896 | 897 | 898 | dayOfWeek : Zone -> Posix -> Int 899 | dayOfWeek zone posix = 900 | days 901 | |> List.indexedMap (\i day -> ( i, day )) 902 | |> List.filter (\( _, day ) -> day == Time.toWeekday zone posix) 903 | |> List.head 904 | |> Maybe.withDefault ( 0, Sun ) 905 | |> (\( i, _ ) -> i) 906 | 907 | 908 | 909 | -- WEEK OF YEAR 910 | 911 | 912 | type alias SimpleDate = 913 | { month : Month 914 | , day : Int 915 | , year : Int 916 | } 917 | 918 | 919 | weekOfYear : Zone -> Posix -> Int 920 | weekOfYear zone posix = 921 | let 922 | daysSoFar : Int 923 | daysSoFar = 924 | dayOfYear zone posix 925 | 926 | firstDay : Posix 927 | firstDay = 928 | firstDayOfYear zone posix 929 | 930 | firstDayOffset : Int 931 | firstDayOffset = 932 | dayOfWeek zone firstDay 933 | in 934 | (daysSoFar + firstDayOffset) // 7 + 1 935 | 936 | 937 | millisecondsPerYear : Int 938 | millisecondsPerYear = 939 | round (1000 * 60 * 60 * 24 * 365.25) 940 | 941 | 942 | firstDayOfYear : Zone -> Posix -> Posix 943 | firstDayOfYear zone time = 944 | time 945 | |> Time.toYear zone 946 | |> (*) millisecondsPerYear 947 | |> Time.millisToPosix 948 | 949 | 950 | 951 | -- YEAR 952 | 953 | 954 | year : Zone -> Posix -> String 955 | year zone time = 956 | time 957 | |> Time.toYear zone 958 | |> String.fromInt 959 | 960 | 961 | 962 | -- AM / PM 963 | 964 | 965 | amPm : Language -> Zone -> Posix -> String 966 | amPm language zone posix = 967 | language.toAmPm (Time.toHour zone posix) 968 | 969 | 970 | 971 | -- HOUR 972 | 973 | 974 | toNonMilitary : Int -> Int 975 | toNonMilitary num = 976 | if num == 0 then 977 | 12 978 | 979 | else if num <= 12 then 980 | num 981 | 982 | else 983 | num - 12 984 | 985 | 986 | 987 | -- GENERIC 988 | 989 | 990 | toFixedLength : Int -> Int -> String 991 | toFixedLength totalChars num = 992 | let 993 | numStr = 994 | String.fromInt num 995 | 996 | numZerosNeeded = 997 | totalChars - String.length numStr 998 | 999 | zeros = 1000 | List.range 1 numZerosNeeded 1001 | |> List.map (\_ -> "0") 1002 | |> String.join "" 1003 | in 1004 | zeros ++ numStr 1005 | -------------------------------------------------------------------------------- /src/DateFormat/Language.elm: -------------------------------------------------------------------------------- 1 | module DateFormat.Language exposing 2 | ( Language 3 | , english, spanish, dutch, swedish, portuguese, french 4 | ) 5 | 6 | {-| 7 | 8 | 9 | ## Fun fact: Some people don't know english. 10 | 11 | That's why it's important to include alternative date formatting options for other languages! 12 | 13 | This module exposes `Language`, along with a few implementations. 14 | 15 | (If you want to see `german`, `greek`, or `swahili`, please add them in! I'm happy to make your language a part of the package!) 16 | 17 | 18 | ### Language 19 | 20 | @docs Language 21 | 22 | 23 | ### Languages 24 | 25 | @docs english, spanish, dutch, swedish, portuguese, french 26 | 27 | -} 28 | 29 | import Time exposing (Month(..), Weekday(..)) 30 | 31 | 32 | {-| A record with options for your language. 33 | -} 34 | type alias Language = 35 | { toMonthName : Month -> String 36 | , toMonthAbbreviation : Month -> String 37 | , toWeekdayName : Weekday -> String 38 | , toWeekdayAbbreviation : Weekday -> String 39 | , toAmPm : Int -> String 40 | , toOrdinalSuffix : Int -> String 41 | } 42 | 43 | 44 | 45 | -- English 46 | 47 | 48 | toEnglishMonthName : Month -> String 49 | toEnglishMonthName month = 50 | case month of 51 | Jan -> 52 | "January" 53 | 54 | Feb -> 55 | "February" 56 | 57 | Mar -> 58 | "March" 59 | 60 | Apr -> 61 | "April" 62 | 63 | May -> 64 | "May" 65 | 66 | Jun -> 67 | "June" 68 | 69 | Jul -> 70 | "July" 71 | 72 | Aug -> 73 | "August" 74 | 75 | Sep -> 76 | "September" 77 | 78 | Oct -> 79 | "October" 80 | 81 | Nov -> 82 | "November" 83 | 84 | Dec -> 85 | "December" 86 | 87 | 88 | toEnglishWeekdayName : Weekday -> String 89 | toEnglishWeekdayName weekday = 90 | case weekday of 91 | Mon -> 92 | "Monday" 93 | 94 | Tue -> 95 | "Tuesday" 96 | 97 | Wed -> 98 | "Wednesday" 99 | 100 | Thu -> 101 | "Thursday" 102 | 103 | Fri -> 104 | "Friday" 105 | 106 | Sat -> 107 | "Saturday" 108 | 109 | Sun -> 110 | "Sunday" 111 | 112 | 113 | toEnglishAmPm : Int -> String 114 | toEnglishAmPm hour = 115 | if hour > 11 then 116 | "pm" 117 | 118 | else 119 | "am" 120 | 121 | 122 | toEnglishSuffix : Int -> String 123 | toEnglishSuffix num = 124 | case modBy 100 num of 125 | 11 -> 126 | "th" 127 | 128 | 12 -> 129 | "th" 130 | 131 | 13 -> 132 | "th" 133 | 134 | _ -> 135 | case modBy 10 num of 136 | 1 -> 137 | "st" 138 | 139 | 2 -> 140 | "nd" 141 | 142 | 3 -> 143 | "rd" 144 | 145 | _ -> 146 | "th" 147 | 148 | 149 | {-| The english language! (used by default) 150 | -} 151 | english : Language 152 | english = 153 | Language 154 | toEnglishMonthName 155 | (toEnglishMonthName >> String.left 3) 156 | toEnglishWeekdayName 157 | (toEnglishWeekdayName >> String.left 3) 158 | toEnglishAmPm 159 | toEnglishSuffix 160 | 161 | 162 | 163 | -- Spanish 164 | 165 | 166 | {-| The spanish language! 167 | -} 168 | spanish : Language 169 | spanish = 170 | Language 171 | toSpanishMonthName 172 | (toSpanishMonthName >> String.left 3) 173 | toSpanishWeekdayName 174 | (toSpanishWeekdayName >> String.left 3) 175 | toEnglishAmPm 176 | (always "°") 177 | 178 | 179 | toSpanishMonthName : Time.Month -> String 180 | toSpanishMonthName month = 181 | case month of 182 | Jan -> 183 | "Enero" 184 | 185 | Feb -> 186 | "Febrero" 187 | 188 | Mar -> 189 | "Marzo" 190 | 191 | Apr -> 192 | "Abril" 193 | 194 | May -> 195 | "Mayo" 196 | 197 | Jun -> 198 | "Junio" 199 | 200 | Jul -> 201 | "Julio" 202 | 203 | Aug -> 204 | "Agosto" 205 | 206 | Sep -> 207 | "Septiembre" 208 | 209 | Oct -> 210 | "Octubre" 211 | 212 | Nov -> 213 | "Noviembre" 214 | 215 | Dec -> 216 | "Diciembre" 217 | 218 | 219 | toSpanishWeekdayName : Time.Weekday -> String 220 | toSpanishWeekdayName weekday = 221 | case weekday of 222 | Mon -> 223 | "Lunes" 224 | 225 | Tue -> 226 | "Martes" 227 | 228 | Wed -> 229 | "Miércoles" 230 | 231 | Thu -> 232 | "Jueves" 233 | 234 | Fri -> 235 | "Viernes" 236 | 237 | Sat -> 238 | "Sábado" 239 | 240 | Sun -> 241 | "Domingo" 242 | 243 | 244 | 245 | -- Dutch 246 | 247 | 248 | {-| The dutch language! 249 | -} 250 | dutch : Language 251 | dutch = 252 | Language 253 | toDutchMonthName 254 | (toDutchMonthName >> String.left 3) 255 | toDutchWeekdayName 256 | (toDutchWeekdayName >> String.left 3) 257 | toEnglishAmPm 258 | toDutchSuffix 259 | 260 | 261 | toDutchMonthName : Time.Month -> String 262 | toDutchMonthName month = 263 | case month of 264 | Jan -> 265 | "januari" 266 | 267 | Feb -> 268 | "februari" 269 | 270 | Mar -> 271 | "maart" 272 | 273 | Apr -> 274 | "april" 275 | 276 | May -> 277 | "mei" 278 | 279 | Jun -> 280 | "juni" 281 | 282 | Jul -> 283 | "juli" 284 | 285 | Aug -> 286 | "augustus" 287 | 288 | Sep -> 289 | "september" 290 | 291 | Oct -> 292 | "oktober" 293 | 294 | Nov -> 295 | "november" 296 | 297 | Dec -> 298 | "december" 299 | 300 | 301 | toDutchWeekdayName : Time.Weekday -> String 302 | toDutchWeekdayName weekday = 303 | case weekday of 304 | Mon -> 305 | "maandag" 306 | 307 | Tue -> 308 | "dinsdag" 309 | 310 | Wed -> 311 | "woensdag" 312 | 313 | Thu -> 314 | "donderdag" 315 | 316 | Fri -> 317 | "vrijdag" 318 | 319 | Sat -> 320 | "zaterdag" 321 | 322 | Sun -> 323 | "zondag" 324 | 325 | 326 | toDutchSuffix : Int -> String 327 | toDutchSuffix num = 328 | if num > 20 || num == 1 || num == 8 then 329 | "ste" 330 | 331 | else 332 | "de" 333 | 334 | 335 | 336 | -- Swedish 337 | 338 | 339 | {-| The Swedish language! 340 | -} 341 | swedish : Language 342 | swedish = 343 | Language 344 | toSwedishMonthName 345 | (toSwedishMonthName >> String.left 3) 346 | toSwedishWeekdayName 347 | (toSwedishWeekdayName >> String.left 3) 348 | toEnglishAmPm 349 | (\_ -> "") 350 | 351 | 352 | toSwedishMonthName : Time.Month -> String 353 | toSwedishMonthName month = 354 | case month of 355 | Jan -> 356 | "januari" 357 | 358 | Feb -> 359 | "februari" 360 | 361 | Mar -> 362 | "mars" 363 | 364 | Apr -> 365 | "april" 366 | 367 | May -> 368 | "maj" 369 | 370 | Jun -> 371 | "juni" 372 | 373 | Jul -> 374 | "juli" 375 | 376 | Aug -> 377 | "augusti" 378 | 379 | Sep -> 380 | "september" 381 | 382 | Oct -> 383 | "oktober" 384 | 385 | Nov -> 386 | "november" 387 | 388 | Dec -> 389 | "december" 390 | 391 | 392 | toSwedishWeekdayName : Time.Weekday -> String 393 | toSwedishWeekdayName weekday = 394 | case weekday of 395 | Mon -> 396 | "måndag" 397 | 398 | Tue -> 399 | "tisdag" 400 | 401 | Wed -> 402 | "onsdag" 403 | 404 | Thu -> 405 | "torsdag" 406 | 407 | Fri -> 408 | "fredag" 409 | 410 | Sat -> 411 | "lördag" 412 | 413 | Sun -> 414 | "söndag" 415 | 416 | 417 | 418 | -- Portuguese 419 | 420 | 421 | {-| The Portuguese language! 422 | -} 423 | portuguese : Language 424 | portuguese = 425 | Language 426 | toPortugueseMonthName 427 | (toPortugueseMonthName >> String.left 3) 428 | toPortugueseWeekdayName 429 | (toPortugueseWeekdayName >> String.left 3) 430 | toEnglishAmPm 431 | (always "°") 432 | 433 | 434 | toPortugueseMonthName : Time.Month -> String 435 | toPortugueseMonthName month = 436 | case month of 437 | Jan -> 438 | "Janeiro" 439 | 440 | Feb -> 441 | "Fevereiro" 442 | 443 | Mar -> 444 | "Março" 445 | 446 | Apr -> 447 | "Abril" 448 | 449 | May -> 450 | "Maio" 451 | 452 | Jun -> 453 | "Junho" 454 | 455 | Jul -> 456 | "Julho" 457 | 458 | Aug -> 459 | "Agosto" 460 | 461 | Sep -> 462 | "Setembro" 463 | 464 | Oct -> 465 | "Outubro" 466 | 467 | Nov -> 468 | "Novembro" 469 | 470 | Dec -> 471 | "Dezembro" 472 | 473 | 474 | toPortugueseWeekdayName : Time.Weekday -> String 475 | toPortugueseWeekdayName weekday = 476 | case weekday of 477 | Mon -> 478 | "Segunda" 479 | 480 | Tue -> 481 | "Terça" 482 | 483 | Wed -> 484 | "Quarta" 485 | 486 | Thu -> 487 | "Quinta" 488 | 489 | Fri -> 490 | "Sexta" 491 | 492 | Sat -> 493 | "Sábado" 494 | 495 | Sun -> 496 | "Domingo" 497 | 498 | 499 | 500 | -- French 501 | 502 | 503 | {-| The French language! 504 | -} 505 | french : Language 506 | french = 507 | Language 508 | toFrenchMonthName 509 | toFrenchMonthAbbreviation 510 | toFrenchWeekdayName 511 | (toFrenchWeekdayName >> String.left 3) 512 | toEnglishAmPm 513 | toFrenchOrdinalSuffix 514 | 515 | 516 | toFrenchMonthName : Month -> String 517 | toFrenchMonthName month = 518 | case month of 519 | Jan -> 520 | "janvier" 521 | 522 | Feb -> 523 | "février" 524 | 525 | Mar -> 526 | "mars" 527 | 528 | Apr -> 529 | "avril" 530 | 531 | May -> 532 | "mai" 533 | 534 | Jun -> 535 | "juin" 536 | 537 | Jul -> 538 | "juillet" 539 | 540 | Aug -> 541 | "août" 542 | 543 | Sep -> 544 | "septembre" 545 | 546 | Oct -> 547 | "octobre" 548 | 549 | Nov -> 550 | "novembre" 551 | 552 | Dec -> 553 | "décembre" 554 | 555 | 556 | toFrenchMonthAbbreviation : Month -> String 557 | toFrenchMonthAbbreviation month = 558 | case month of 559 | Jan -> 560 | "janv" 561 | 562 | Feb -> 563 | "févr" 564 | 565 | Mar -> 566 | "mars" 567 | 568 | Apr -> 569 | "avr" 570 | 571 | May -> 572 | "mai" 573 | 574 | Jun -> 575 | "juin" 576 | 577 | Jul -> 578 | "juil" 579 | 580 | Aug -> 581 | "août" 582 | 583 | Sep -> 584 | "sept" 585 | 586 | Oct -> 587 | "oct" 588 | 589 | Nov -> 590 | "nov" 591 | 592 | Dec -> 593 | "déc" 594 | 595 | 596 | toFrenchWeekdayName : Weekday -> String 597 | toFrenchWeekdayName weekday = 598 | case weekday of 599 | Mon -> 600 | "lundi" 601 | 602 | Tue -> 603 | "mardi" 604 | 605 | Wed -> 606 | "mercredi" 607 | 608 | Thu -> 609 | "jeudi" 610 | 611 | Fri -> 612 | "vendredi" 613 | 614 | Sat -> 615 | "samedi" 616 | 617 | Sun -> 618 | "dimanche" 619 | 620 | 621 | toFrenchOrdinalSuffix : Int -> String 622 | toFrenchOrdinalSuffix n = 623 | if n == 1 then 624 | "er" 625 | 626 | else 627 | "e" 628 | -------------------------------------------------------------------------------- /src/DateFormat/Relative.elm: -------------------------------------------------------------------------------- 1 | module DateFormat.Relative exposing (relativeTime, relativeTimeWithOptions, RelativeTimeOptions, defaultRelativeOptions) 2 | 3 | {-| A reliable way to get a pretty message for the relative time difference between two dates. 4 | 5 | 6 | # Getting relative time for two dates 7 | 8 | @docs relativeTime, relativeTimeWithOptions, RelativeTimeOptions, defaultRelativeOptions 9 | 10 | -} 11 | 12 | import Time exposing (Month(..), Posix, Weekday(..), Zone, utc) 13 | 14 | 15 | {-| This function takes in two times and returns the relative difference! 16 | 17 | Here are a few examples to help: 18 | 19 | relativeTime now tenSecondsAgo == "just now" 20 | 21 | relativeTime now tenSecondsFromNow == "in a few seconds" 22 | 23 | relativeTime now fortyThreeMinutesAgo == "43 minutes ago" 24 | 25 | relativeTime now oneHundredDaysAgo == "100 days ago" 26 | 27 | relativeTime now oneHundredDaysFromNow == "in 100 days" 28 | 29 | 30 | -- Order matters! 31 | relativeTime now tenSecondsAgo == "just now" 32 | 33 | relativeTime tenSecondsAgo now == "in a few seconds" 34 | 35 | -} 36 | relativeTime : Posix -> Posix -> String 37 | relativeTime = 38 | relativeTimeWithOptions defaultRelativeOptions 39 | 40 | 41 | {-| Maybe `relativeTime` is too lame. (Or maybe you speak a different language than English!) 42 | 43 | With `relativeTimeWithOptions`, you can provide your own custom messages for each time range. 44 | 45 | (That's what `relativeTime` uses under the hood!) 46 | 47 | You can provide a set of your own custom options, and use `relativeTimeWithOptions` instead. 48 | 49 | -} 50 | relativeTimeWithOptions : RelativeTimeOptions -> Posix -> Posix -> String 51 | relativeTimeWithOptions options start end = 52 | let 53 | differenceInMilliseconds : Int 54 | differenceInMilliseconds = 55 | toMilliseconds end - toMilliseconds start 56 | in 57 | if differenceInMilliseconds == 0 then 58 | options.rightNow 59 | 60 | else 61 | relativeTimeWithFunctions utc (abs differenceInMilliseconds) <| 62 | if differenceInMilliseconds < 0 then 63 | RelativeTimeFunctions 64 | options.someSecondsAgo 65 | options.someMinutesAgo 66 | options.someHoursAgo 67 | options.someDaysAgo 68 | options.someMonthsAgo 69 | options.someYearsAgo 70 | 71 | else 72 | RelativeTimeFunctions 73 | options.inSomeSeconds 74 | options.inSomeMinutes 75 | options.inSomeHours 76 | options.inSomeDays 77 | options.inSomeMonths 78 | options.inSomeYears 79 | 80 | 81 | {-| Options for configuring your own relative message formats! 82 | 83 | For example, here is how `someSecondsAgo` is implemented by default: 84 | 85 | defaultSomeSecondsAgo : Int -> String 86 | defaultSomeSecondsAgo seconds = 87 | if seconds < 30 then 88 | "just now" 89 | 90 | else 91 | toString seconds ++ " seconds ago" 92 | 93 | And here is how `inSomeHours` might look: 94 | 95 | defaultInSomeHours : Int -> String 96 | defaultInSomeHours hours = 97 | if hours < 2 then 98 | "in an hour" 99 | 100 | else 101 | "in " ++ toString hours ++ " hours" 102 | 103 | -} 104 | type alias RelativeTimeOptions = 105 | { someSecondsAgo : Int -> String 106 | , someMinutesAgo : Int -> String 107 | , someHoursAgo : Int -> String 108 | , someDaysAgo : Int -> String 109 | , someMonthsAgo : Int -> String 110 | , someYearsAgo : Int -> String 111 | , rightNow : String 112 | , inSomeSeconds : Int -> String 113 | , inSomeMinutes : Int -> String 114 | , inSomeHours : Int -> String 115 | , inSomeDays : Int -> String 116 | , inSomeMonths : Int -> String 117 | , inSomeYears : Int -> String 118 | } 119 | 120 | 121 | {-| If there is something you'd like to tweak based off of the defaults, this record might be a good starting point! 122 | -} 123 | defaultRelativeOptions : RelativeTimeOptions 124 | defaultRelativeOptions = 125 | { someSecondsAgo = defaultSomeSecondsAgo 126 | , someMinutesAgo = defaultSomeMinutesAgo 127 | , someHoursAgo = defaultSomeHoursAgo 128 | , someDaysAgo = defaultSomeDaysAgo 129 | , someMonthsAgo = defaultSomeMonthsAgo 130 | , someYearsAgo = defaultSomeYearsAgo 131 | , rightNow = defaultRightNow 132 | , inSomeSeconds = defaultInSomeSeconds 133 | , inSomeMinutes = defaultInSomeMinutes 134 | , inSomeHours = defaultInSomeHours 135 | , inSomeDays = defaultInSomeDays 136 | , inSomeMonths = defaultInSomeMonths 137 | , inSomeYears = defaultInSomeYears 138 | } 139 | 140 | 141 | toMilliseconds : Posix -> Int 142 | toMilliseconds = 143 | Time.posixToMillis 144 | 145 | 146 | type alias RelativeTimeFunctions = 147 | { seconds : Int -> String 148 | , minutes : Int -> String 149 | , hours : Int -> String 150 | , days : Int -> String 151 | , months : Int -> String 152 | , years : Int -> String 153 | } 154 | 155 | 156 | relativeTimeWithFunctions : Zone -> Int -> RelativeTimeFunctions -> String 157 | relativeTimeWithFunctions zone millis functions = 158 | let 159 | posix = 160 | Time.millisToPosix millis 161 | 162 | seconds = 163 | millis // 1000 164 | 165 | minutes = 166 | seconds // 60 167 | 168 | hours = 169 | minutes // 60 170 | 171 | days = 172 | hours // 24 173 | in 174 | if minutes < 1 then 175 | functions.seconds <| Time.toSecond zone posix 176 | 177 | else if hours < 1 then 178 | functions.minutes <| Time.toMinute zone posix 179 | 180 | else if hours < 24 then 181 | functions.hours <| Time.toHour zone posix 182 | 183 | else if days < 30 then 184 | functions.days <| days 185 | 186 | else if days < 365 then 187 | functions.months <| (days // 30) 188 | 189 | else 190 | functions.years <| (days // 365) 191 | 192 | 193 | defaultRightNow : String 194 | defaultRightNow = 195 | "right now" 196 | 197 | 198 | defaultSomeSecondsAgo : Int -> String 199 | defaultSomeSecondsAgo seconds = 200 | if seconds < 30 then 201 | "just now" 202 | 203 | else 204 | String.fromInt seconds ++ " seconds ago" 205 | 206 | 207 | defaultSomeMinutesAgo : Int -> String 208 | defaultSomeMinutesAgo minutes = 209 | if minutes < 2 then 210 | "a minute ago" 211 | 212 | else 213 | String.fromInt minutes ++ " minutes ago" 214 | 215 | 216 | defaultSomeHoursAgo : Int -> String 217 | defaultSomeHoursAgo hours = 218 | if hours < 2 then 219 | "an hour ago" 220 | 221 | else 222 | String.fromInt hours ++ " hours ago" 223 | 224 | 225 | defaultSomeDaysAgo : Int -> String 226 | defaultSomeDaysAgo days = 227 | if days < 2 then 228 | "yesterday" 229 | 230 | else 231 | String.fromInt days ++ " days ago" 232 | 233 | 234 | defaultSomeMonthsAgo : Int -> String 235 | defaultSomeMonthsAgo months = 236 | if months < 2 then 237 | "last month" 238 | 239 | else 240 | String.fromInt months ++ " months ago" 241 | 242 | 243 | defaultSomeYearsAgo : Int -> String 244 | defaultSomeYearsAgo years = 245 | if years < 2 then 246 | "last year" 247 | 248 | else 249 | String.fromInt years ++ " years ago" 250 | 251 | 252 | defaultInSomeSeconds : Int -> String 253 | defaultInSomeSeconds seconds = 254 | if seconds < 30 then 255 | "in a few seconds" 256 | 257 | else 258 | "in " ++ String.fromInt seconds ++ " seconds" 259 | 260 | 261 | defaultInSomeMinutes : Int -> String 262 | defaultInSomeMinutes minutes = 263 | if minutes < 2 then 264 | "in a minute" 265 | 266 | else 267 | "in " ++ String.fromInt minutes ++ " minutes" 268 | 269 | 270 | defaultInSomeHours : Int -> String 271 | defaultInSomeHours hours = 272 | if hours < 2 then 273 | "in an hour" 274 | 275 | else 276 | "in " ++ String.fromInt hours ++ " hours" 277 | 278 | 279 | defaultInSomeDays : Int -> String 280 | defaultInSomeDays days = 281 | if days < 2 then 282 | "tomorrow" 283 | 284 | else 285 | "in " ++ String.fromInt days ++ " days" 286 | 287 | 288 | defaultInSomeMonths : Int -> String 289 | defaultInSomeMonths months = 290 | if months < 2 then 291 | "in a month" 292 | 293 | else 294 | "in " ++ String.fromInt months ++ " months" 295 | 296 | 297 | defaultInSomeYears : Int -> String 298 | defaultInSomeYears years = 299 | if years < 2 then 300 | "in a year" 301 | 302 | else 303 | "in " ++ String.fromInt years ++ " years" 304 | -------------------------------------------------------------------------------- /tests/Fuzzers.elm: -------------------------------------------------------------------------------- 1 | module Fuzzers exposing (datetime, datetimeAndString) 2 | 3 | import Fuzz exposing (Fuzzer, int, list, string) 4 | import Time exposing (Posix) 5 | 6 | 7 | datetime : Fuzzer Posix 8 | datetime = 9 | Fuzz.map Time.millisToPosix 10 | (Fuzz.intRange 11 | 1529000000 12 | 1530000000 13 | ) 14 | 15 | 16 | datetimeAndString : Fuzzer ( Posix, String ) 17 | datetimeAndString = 18 | Fuzz.map2 Tuple.pair datetime Fuzz.string 19 | -------------------------------------------------------------------------------- /tests/RelativeTests.elm: -------------------------------------------------------------------------------- 1 | module RelativeTests exposing (suite) 2 | 3 | import DateFormat.Relative exposing (relativeTime) 4 | import Expect exposing (Expectation) 5 | import Fuzz 6 | import Test exposing (..) 7 | import Time exposing (Posix, utc) 8 | 9 | 10 | time = 11 | Time.millisToPosix 1537190428785 12 | 13 | 14 | applyDiff diff = 15 | Time.millisToPosix <| Time.posixToMillis time + diff 16 | 17 | 18 | addSeconds secs = 19 | applyDiff (secs * 1000) 20 | 21 | 22 | addMins mins = 23 | addSeconds (mins * 60) 24 | 25 | 26 | addHours hours = 27 | addMins (hours * 60) 28 | 29 | 30 | addDays days = 31 | addHours (days * 24) 32 | 33 | 34 | addMonths months = 35 | addDays (months * 30) 36 | 37 | 38 | suite : Test 39 | suite = 40 | describe "The DateFormat.Relative module" 41 | [ test "right now" <| 42 | \_ -> relativeTime time time |> Expect.equal "right now" 43 | , fuzz (Fuzz.intRange -29 -1) "last 30s" <| 44 | \secs -> 45 | relativeTime time (addSeconds secs) 46 | |> Expect.equal "just now" 47 | , fuzz (Fuzz.intRange -59 -30) "30-59s ago" <| 48 | \secs -> 49 | relativeTime time (addSeconds secs) 50 | |> Expect.equal (String.fromInt (abs secs) ++ " seconds ago") 51 | , test "1 min" <| 52 | \_ -> 53 | relativeTime time (addMins -1) 54 | |> Expect.equal "a minute ago" 55 | , fuzz (Fuzz.intRange -59 -2) "2-59 mins ago" <| 56 | \mins -> 57 | relativeTime time (addMins mins) 58 | |> Expect.equal (String.fromInt (abs mins) ++ " minutes ago") 59 | , test "1 hour" <| 60 | \_ -> 61 | relativeTime time (addHours -1) 62 | |> Expect.equal "an hour ago" 63 | , fuzz (Fuzz.intRange -23 -2) "2-23 hours ago" <| 64 | \hours -> 65 | relativeTime time (addHours hours) 66 | |> Expect.equal (String.fromInt (abs hours) ++ " hours ago") 67 | , test "yesterday" <| 68 | \_ -> 69 | relativeTime time (addDays -1) 70 | |> Expect.equal "yesterday" 71 | , fuzz (Fuzz.intRange -27 -2) "2-27 days ago" <| 72 | \days -> 73 | relativeTime time (addDays days) 74 | |> Expect.equal (String.fromInt (abs days) ++ " days ago") 75 | , fuzz (Fuzz.intRange -11 -2) "2-11 months ago" <| 76 | \days -> 77 | relativeTime time (addMonths days) 78 | |> Expect.equal (String.fromInt (abs days) ++ " months ago") 79 | , test "12 months ago" <| 80 | \_ -> 81 | relativeTime time (addMonths -12) 82 | |> Expect.equal "12 months ago" 83 | , test "last year" <| 84 | \_ -> 85 | relativeTime time (addMonths -13) 86 | |> Expect.equal "last year" 87 | , test "last year 2" <| 88 | \_ -> 89 | relativeTime time (addMonths -23) 90 | |> Expect.equal "last year" 91 | , test "2 years ago" <| 92 | \_ -> 93 | relativeTime time (addMonths -25) 94 | |> Expect.equal "2 years ago" 95 | , fuzz (Fuzz.intRange 1 29) "next 29s" <| 96 | \secs -> 97 | relativeTime time (addSeconds secs) 98 | |> Expect.equal "in a few seconds" 99 | , fuzz (Fuzz.intRange 31 59) "31-59s from now" <| 100 | \secs -> 101 | relativeTime time (addSeconds secs) 102 | |> Expect.equal ("in " ++ String.fromInt (abs secs) ++ " seconds") 103 | , test "1 min future" <| 104 | \_ -> 105 | relativeTime time (addMins 1) 106 | |> Expect.equal "in a minute" 107 | , fuzz (Fuzz.intRange 2 59) "2-59 mins from now" <| 108 | \mins -> 109 | relativeTime time (addMins mins) 110 | |> Expect.equal ("in " ++ String.fromInt (abs mins) ++ " minutes") 111 | , test "in 1 hour" <| 112 | \_ -> 113 | relativeTime time (addHours 1) 114 | |> Expect.equal "in an hour" 115 | , fuzz (Fuzz.intRange 2 23) "2-23 hours from now" <| 116 | \hours -> 117 | relativeTime time (addHours hours) 118 | |> Expect.equal ("in " ++ String.fromInt (abs hours) ++ " hours") 119 | , test "tomorrow" <| 120 | \_ -> 121 | relativeTime time (addDays 1) 122 | |> Expect.equal "tomorrow" 123 | , fuzz (Fuzz.intRange 2 27) "2-27 days from now" <| 124 | \days -> 125 | relativeTime time (addDays days) 126 | |> Expect.equal ("in " ++ String.fromInt (abs days) ++ " days") 127 | , fuzz (Fuzz.intRange 2 11) "in 2-11 months" <| 128 | \days -> 129 | relativeTime time (addMonths days) 130 | |> Expect.equal ("in " ++ String.fromInt (abs days) ++ " months") 131 | , test "in 12 months" <| 132 | \_ -> 133 | relativeTime time (addMonths 12) 134 | |> Expect.equal "in 12 months" 135 | , test "in a year" <| 136 | \_ -> 137 | relativeTime time (addMonths 13) 138 | |> Expect.equal "in a year" 139 | , test "in a year 2" <| 140 | \_ -> 141 | relativeTime time (addMonths 23) 142 | |> Expect.equal "in a year" 143 | , test "in 2 years" <| 144 | \_ -> 145 | relativeTime time (addMonths 25) 146 | |> Expect.equal "in 2 years" 147 | ] 148 | -------------------------------------------------------------------------------- /tests/Tests.elm: -------------------------------------------------------------------------------- 1 | module Tests exposing (suite) 2 | 3 | import DateFormat 4 | import Expect exposing (Expectation) 5 | import Fuzzers 6 | import Test exposing (..) 7 | import Time exposing (Posix, utc) 8 | 9 | 10 | atLeast : Int -> DateFormat.Token -> Posix -> Expectation 11 | atLeast min token somePosix = 12 | let 13 | formattedPosix = 14 | DateFormat.format [ token ] utc somePosix 15 | in 16 | stringAtLeast min formattedPosix 17 | 18 | 19 | withinRange : Int -> Int -> DateFormat.Token -> Posix -> Expectation 20 | withinRange min max token somePosix = 21 | let 22 | formattedPosix = 23 | DateFormat.format [ token ] utc somePosix 24 | in 25 | stringWithinRange min max formattedPosix 26 | 27 | 28 | fixedWithinRange : Int -> Int -> Int -> DateFormat.Token -> Posix -> Expectation 29 | fixedWithinRange len min max token somePosix = 30 | let 31 | formattedPosix = 32 | DateFormat.format [ token ] utc somePosix 33 | in 34 | Expect.all 35 | [ stringWithinRange min max 36 | , isLength len 37 | ] 38 | formattedPosix 39 | 40 | 41 | formatsWithLength : Int -> DateFormat.Token -> Posix -> Expectation 42 | formatsWithLength len token somePosix = 43 | DateFormat.format [ token ] utc somePosix 44 | |> isLength len 45 | 46 | 47 | isLength : Int -> String -> Expectation 48 | isLength len str = 49 | Expect.equal len (String.length str) 50 | 51 | 52 | stringAtLeast : Int -> String -> Expectation 53 | stringAtLeast min string = 54 | case String.toInt string of 55 | Just int -> 56 | Expect.atLeast min int 57 | 58 | Nothing -> 59 | Expect.fail "Doesn't return a number" 60 | 61 | 62 | stringWithinRange : Int -> Int -> String -> Expectation 63 | stringWithinRange min max string = 64 | case String.toInt string of 65 | Just int -> 66 | int 67 | |> Expect.all 68 | [ Expect.atLeast min 69 | , Expect.atMost max 70 | ] 71 | 72 | Nothing -> 73 | Expect.fail "Doesn't return a number" 74 | 75 | 76 | suffixWithinRange : Int -> Int -> DateFormat.Token -> Posix -> Expectation 77 | suffixWithinRange min max token somePosix = 78 | let 79 | result = 80 | DateFormat.format [ token ] utc somePosix 81 | in 82 | if endsInSuffix result then 83 | stringWithinRange min max (String.dropRight 2 result) 84 | 85 | else 86 | Expect.fail "Doesn't end in a suffix." 87 | 88 | 89 | endsInSuffix : String -> Bool 90 | endsInSuffix string = 91 | let 92 | lastTwoLetters = 93 | String.right 2 string 94 | in 95 | List.member lastTwoLetters [ "st", "nd", "rd", "th" ] 96 | 97 | 98 | suite : Test 99 | suite = 100 | describe "The DateFormat Module" 101 | [ -- Month 102 | describe "monthNumber" 103 | [ fuzz Fuzzers.datetime "Is between 1 and 12" <| 104 | withinRange 1 12 DateFormat.monthNumber 105 | ] 106 | , describe "monthSuffix" 107 | [ fuzz Fuzzers.datetime "Is between 1 and 12" <| 108 | suffixWithinRange 1 12 DateFormat.monthSuffix 109 | ] 110 | , describe "monthFixed" 111 | [ fuzz Fuzzers.datetime "Is between 1 and 12" <| 112 | fixedWithinRange 2 1 12 DateFormat.monthFixed 113 | ] 114 | , describe "monthNameAbbreviated" 115 | [ fuzz Fuzzers.datetime "Is always three characters long" <| 116 | formatsWithLength 3 DateFormat.monthNameAbbreviated 117 | ] 118 | , describe "monthNameFull" 119 | [ fuzz Fuzzers.datetime "Starts with abbreviation" <| 120 | \date -> 121 | let 122 | abbrev = 123 | DateFormat.format 124 | [ DateFormat.monthNameAbbreviated ] 125 | utc 126 | date 127 | 128 | fullName = 129 | DateFormat.format 130 | [ DateFormat.monthNameFull ] 131 | utc 132 | date 133 | in 134 | Expect.equal 135 | abbrev 136 | (String.left 3 fullName) 137 | , fuzz Fuzzers.datetime "Is a valid month" <| 138 | \date -> 139 | let 140 | validMonths = 141 | [ "January" 142 | , "February" 143 | , "March" 144 | , "April" 145 | , "May" 146 | , "June" 147 | , "July" 148 | , "August" 149 | , "September" 150 | , "October" 151 | , "November" 152 | , "December" 153 | ] 154 | 155 | monthName = 156 | DateFormat.format [ DateFormat.monthNameFull ] utc date 157 | in 158 | Expect.equal 159 | True 160 | (List.member monthName validMonths) 161 | ] 162 | 163 | -- Day of Month 164 | , describe "dayOfMonthNumber" 165 | [ fuzz Fuzzers.datetime "Is between 1 and 31" <| 166 | withinRange 1 31 DateFormat.dayOfMonthNumber 167 | ] 168 | , describe "dayOfMonthSuffix" 169 | [ fuzz Fuzzers.datetime "Is between 1 and 31" <| 170 | suffixWithinRange 1 31 DateFormat.dayOfMonthSuffix 171 | ] 172 | , describe "dayOfMonthFixed" 173 | [ fuzz Fuzzers.datetime "Is between 1 and 31" <| 174 | fixedWithinRange 2 1 31 DateFormat.dayOfMonthFixed 175 | ] 176 | 177 | -- Day of Year 178 | , describe "dayOfYearNumber" 179 | [ fuzz Fuzzers.datetime "Is between 1 and 365" <| 180 | withinRange 1 365 DateFormat.dayOfYearNumber 181 | ] 182 | , describe "dayOfYearSuffix" 183 | [ fuzz Fuzzers.datetime "Is between 1 and 365" <| 184 | suffixWithinRange 1 365 DateFormat.dayOfYearSuffix 185 | ] 186 | , describe "dayOfYearFixed" 187 | [ fuzz Fuzzers.datetime "Is between 1 and 365" <| 188 | fixedWithinRange 3 1 365 DateFormat.dayOfYearFixed 189 | ] 190 | 191 | -- Day of Week 192 | , describe "dayOfWeekNumber" 193 | [ fuzz Fuzzers.datetime "Is between 0 and 6" <| 194 | withinRange 0 6 DateFormat.dayOfWeekNumber 195 | ] 196 | , describe "dayOfWeekSuffix" 197 | [ fuzz Fuzzers.datetime "Is between 0 and 6" <| 198 | suffixWithinRange 0 6 DateFormat.dayOfWeekSuffix 199 | ] 200 | , describe "dayOfWeekNameAbbreviated" 201 | [ fuzz Fuzzers.datetime "Is always three characters long" <| 202 | formatsWithLength 3 DateFormat.dayOfWeekNameAbbreviated 203 | ] 204 | , describe "dayOfWeekNameFull" 205 | [ fuzz Fuzzers.datetime "Starts with abbreviation" <| 206 | \date -> 207 | let 208 | abbrev = 209 | DateFormat.format 210 | [ DateFormat.dayOfWeekNameAbbreviated ] 211 | utc 212 | date 213 | 214 | fullName = 215 | DateFormat.format 216 | [ DateFormat.dayOfWeekNameFull ] 217 | utc 218 | date 219 | in 220 | Expect.equal 221 | abbrev 222 | (String.left 3 fullName) 223 | , fuzz Fuzzers.datetime "Is a valid month" <| 224 | \date -> 225 | let 226 | validWeekdays = 227 | [ "Monday" 228 | , "Tuesday" 229 | , "Wednesday" 230 | , "Thursday" 231 | , "Friday" 232 | , "Saturday" 233 | , "Sunday" 234 | ] 235 | 236 | dayOfWeekName = 237 | DateFormat.format [ DateFormat.dayOfWeekNameFull ] utc date 238 | in 239 | Expect.equal 240 | True 241 | (List.member dayOfWeekName validWeekdays) 242 | 243 | -- Year 244 | , describe "yearNumberLastTwo" 245 | [ fuzz Fuzzers.datetime "Is between 00 and 99" <| 246 | fixedWithinRange 2 0 99 DateFormat.yearNumberLastTwo 247 | ] 248 | , describe "yearNumber" 249 | [ fuzz Fuzzers.datetime "Is at least 1970" <| 250 | atLeast 1970 DateFormat.yearNumber 251 | ] 252 | 253 | -- Quarter 254 | , describe "quarterNumber" 255 | [ fuzz Fuzzers.datetime "Is between 1 and 4" <| 256 | withinRange 1 4 DateFormat.quarterNumber 257 | ] 258 | , describe "quarterSuffix" 259 | [ fuzz Fuzzers.datetime "Is between 1 and 4" <| 260 | suffixWithinRange 1 4 DateFormat.quarterSuffix 261 | ] 262 | 263 | -- Week of Year 264 | , describe "weekOfYearNumber" 265 | [ fuzz Fuzzers.datetime "Is between 1 and 52" <| 266 | withinRange 1 52 DateFormat.weekOfYearNumber 267 | ] 268 | , describe "weekOfYearSuffix" 269 | [ fuzz Fuzzers.datetime "Is between 1 and 52" <| 270 | suffixWithinRange 1 52 DateFormat.weekOfYearSuffix 271 | ] 272 | , describe "weekOfYearFixed" 273 | [ fuzz Fuzzers.datetime "Is between 1 and 52" <| 274 | fixedWithinRange 2 1 52 DateFormat.weekOfYearFixed 275 | ] 276 | ] 277 | 278 | -- Week of Year 279 | , describe "amPmUppercase" 280 | [ fuzz Fuzzers.datetime "Is either AM or PM" <| 281 | \date -> 282 | let 283 | value = 284 | DateFormat.format [ DateFormat.amPmUppercase ] utc date 285 | in 286 | Expect.equal 287 | True 288 | (List.member 289 | value 290 | [ "AM", "PM" ] 291 | ) 292 | ] 293 | , describe "amPmLowercase" 294 | [ fuzz Fuzzers.datetime "Is either am or pm" <| 295 | \date -> 296 | let 297 | value = 298 | DateFormat.format [ DateFormat.amPmLowercase ] utc date 299 | in 300 | Expect.equal 301 | True 302 | (List.member 303 | value 304 | [ "am", "pm" ] 305 | ) 306 | ] 307 | 308 | -- Military Hour 309 | , describe "hourMilitaryNumber" 310 | [ fuzz Fuzzers.datetime "Is between 0 and 23" <| 311 | withinRange 0 23 DateFormat.hourMilitaryNumber 312 | ] 313 | , describe "hourMilitaryFixed" 314 | [ fuzz Fuzzers.datetime "Is between 0 and 23" <| 315 | fixedWithinRange 2 0 23 DateFormat.hourMilitaryFixed 316 | ] 317 | 318 | -- Hour 319 | , describe "hourNumber" 320 | [ fuzz Fuzzers.datetime "Is between 1 and 12" <| 321 | withinRange 1 12 DateFormat.hourNumber 322 | ] 323 | , describe "hourFixed" 324 | [ fuzz Fuzzers.datetime "Is between 1 and 12" <| 325 | fixedWithinRange 2 1 12 DateFormat.hourFixed 326 | ] 327 | 328 | -- Military Hour 329 | , describe "hourMilitaryFromOneNumber" 330 | [ fuzz Fuzzers.datetime "Is between 1 and 24" <| 331 | withinRange 1 24 DateFormat.hourMilitaryFromOneNumber 332 | ] 333 | , describe "hourMilitaryFromOneFixed" 334 | [ fuzz Fuzzers.datetime "Is between 1 and 24" <| 335 | fixedWithinRange 2 1 24 DateFormat.hourMilitaryFromOneFixed 336 | ] 337 | 338 | -- Hour 339 | , describe "minuteNumber" 340 | [ fuzz Fuzzers.datetime "Is between 0 and 59" <| 341 | withinRange 0 59 DateFormat.minuteNumber 342 | ] 343 | , describe "minuteFixed" 344 | [ fuzz Fuzzers.datetime "Is between 0 and 59" <| 345 | fixedWithinRange 2 0 59 DateFormat.minuteFixed 346 | ] 347 | 348 | -- Hour 349 | , describe "secondNumber" 350 | [ fuzz Fuzzers.datetime "Is between 0 and 59" <| 351 | withinRange 0 59 DateFormat.secondNumber 352 | ] 353 | , describe "secondFixed" 354 | [ fuzz Fuzzers.datetime "Is between 0 and 59" <| 355 | fixedWithinRange 2 0 59 DateFormat.secondFixed 356 | ] 357 | , describe "millisecondNumber" 358 | [ fuzz Fuzzers.datetime "Is between 0 and 999" <| 359 | withinRange 0 999 DateFormat.millisecondNumber 360 | ] 361 | , describe "millisecondFixed" 362 | [ fuzz Fuzzers.datetime "Is between 0 and 59" <| 363 | fixedWithinRange 3 0 999 DateFormat.millisecondFixed 364 | ] 365 | , describe "text" 366 | [ fuzz Fuzzers.datetimeAndString "Passes through any string" <| 367 | \( date, someString ) -> 368 | Expect.equal 369 | someString 370 | (DateFormat.format [ DateFormat.text someString ] utc date) 371 | ] 372 | ] 373 | --------------------------------------------------------------------------------