Skip to content
This repository was archived by the owner on Feb 26, 2025. It is now read-only.

Commit 435a037

Browse files
Merge pull request #16 from jasonleibowitz/luxon
Luxon Docs, Versatile Duration & More Testing
2 parents d379172 + 762e966 commit 435a037

File tree

7 files changed

+213
-24
lines changed

7 files changed

+213
-24
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ All of these properties are required.
5252
| Prop | Type | Description |
5353
|--------------|---------------|-------------|
5454
|description |string |Description of event. Put in the notes or body section of event. |
55-
|duration |string |Duration of event in hours. Must be four digits, e.g. `0200` or `0130`. This is only used for Yahoo. |
55+
|duration |string or number |Duration of event in hours. If string, must be four digits, e.g. `'0200'` or `'0130'`. If number, must represent hours in decimal form, i.e. `2` or `2.15`. This is only used for Yahoo. |
5656
|endDatetime |string |End date time of event. Must be formatted in `YYYYMMDDTHHmmssZ` format or `YYYYMMDDTHHmmss` if timezone is also provided. Use any date lib you want, but the format must match this. |
5757
|location |string |Location of event. Use an address for best specificity and for Google Maps to populate a Map. Sometimes a location name will also populate a map. |
5858
|startDatetime |string |Start date time of event. Must be formatted in `YYYYMMDDTHHmmssZ` format or `YYYYMMDDTHHmmss` if timezone is also provided. Use any date lib you want, but the format must match this. |

lib/utils.js

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
Object.defineProperty(exports, "__esModule", {
44
value: true
55
});
6-
exports.buildShareUrl = exports.isInternetExplorer = exports.isMobile = exports.formatDate = void 0;
6+
exports.buildShareUrl = exports.isInternetExplorer = exports.isMobile = exports.formatDuration = exports.formatDate = void 0;
77

88
var _enums = require("./enums");
99

@@ -16,13 +16,28 @@ var _enums = require("./enums");
1616
var formatDate = function formatDate(date) {
1717
return date && date.replace('+00:00', 'Z');
1818
};
19+
20+
exports.formatDate = formatDate;
21+
22+
var formatDuration = function formatDuration(duration) {
23+
if (typeof duration === 'string') return duration;
24+
var parts = duration.toString().split('.');
25+
26+
if (parts.length < 2) {
27+
parts.push('00');
28+
}
29+
30+
return parts.map(function (part) {
31+
return part.length === 2 ? part : "0".concat(part);
32+
}).join('');
33+
};
1934
/**
2035
* Tests provided UserAgent against Known Mobile User Agents
2136
* @returns {bool} isMobileDevice
2237
*/
2338

2439

25-
exports.formatDate = formatDate;
40+
exports.formatDuration = formatDuration;
2641

2742
var isMobile = function isMobile() {
2843
return /Mobile|iP(hone|od|ad)|Android|BlackBerry|IEMobile/.test(window.navigator.userAgent || window.navigator.vendor || window.opera);
@@ -58,7 +73,7 @@ var googleShareUrl = function googleShareUrl(_ref) {
5873
startDatetime = _ref.startDatetime,
5974
timezone = _ref.timezone,
6075
title = _ref.title;
61-
return "https://calendar.google.com/calendar/render?action=TEMPLATE&dates=".concat(startDatetime, "/").concat(endDatetime, "&ctz=").concat(timezone, "&location=").concat(location, "&text=").concat(title, "&details=").concat(description);
76+
return "https://calendar.google.com/calendar/render?action=TEMPLATE&dates=".concat(startDatetime, "/").concat(endDatetime).concat(timezone && "&ctz=".concat(timezone), "&location=").concat(location, "&text=").concat(title, "&details=").concat(description);
6277
};
6378
/**
6479
* Takes an event object and returns a Yahoo Calendar Event URL
@@ -136,7 +151,7 @@ var buildShareUrl = function buildShareUrl(_ref4, type) {
136151
var encodeURI = type !== _enums.SHARE_SITES.ICAL && type !== _enums.SHARE_SITES.OUTLOOK;
137152
var data = {
138153
description: encodeURI ? encodeURIComponent(description) : description,
139-
duration: duration,
154+
duration: formatDuration(duration),
140155
endDatetime: formatDate(endDatetime),
141156
location: encodeURI ? encodeURIComponent(location) : location,
142157
startDatetime: formatDate(startDatetime),

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
"emotion": "^9.1.3",
4040
"html-webpack-plugin": "^3.2.0",
4141
"jest": "^23.1.0",
42+
"luxon": "^1.4.4",
4243
"moment-timezone": "^0.5.21",
4344
"react": "^16.3.2",
4445
"react-dom": "^16.3.2",

src/docs/index.jsx

Lines changed: 53 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,11 @@ import React from "react";
55
import { render } from "react-dom";
66
import AddToCalendarHOC, { SHARE_SITES } from "../../lib";
77
import Button from './Button';
8-
import Dropdown from './Dropdown';
98
import CalendarModal from './Modal';
10-
import moment from 'moment-timezone';
119
import CodeSnippet from './CodeSnippet';
10+
import Dropdown from './Dropdown';
11+
import { DateTime } from 'luxon';
12+
import moment from 'moment-timezone';
1213
import { css } from 'emotion';
1314
import "./styles.css";
1415

@@ -68,9 +69,10 @@ const paragraphStyles = css`
6869

6970
const startDatetime = moment().utc().add(2, 'days');
7071
const endDatetime = startDatetime.clone().add(2, 'hours');
72+
const duration = endDatetime.diff(startDatetime, 'hours');
7173
const event = {
7274
description: 'Description of event. Going to have a lot of fun doing things that we scheduled ahead of time.',
73-
duration: '0200',
75+
duration,
7476
endDatetime: endDatetime.format('YYYYMMDDTHHmmssZ'),
7577
location: 'NYC',
7678
startDatetime: startDatetime.format('YYYYMMDDTHHmmssZ'),
@@ -85,6 +87,16 @@ const eventInDifferentTimezone = {
8587
timezone: 'Europe/London',
8688
}
8789

90+
const luxonStart = DateTime.fromObject({ year: 2018, month: 10, day: 24, hour: 12, minute: 15, zone: 'America/New_York' });
91+
const luxonEnd = DateTime.fromObject({ year: 2018, month: 10, day: 24, hour: 14, minute: 15, zone: 'America/New_York' });
92+
const luxonEvent = {
93+
...event,
94+
startDatetime: `${luxonStart.toFormat('yyyyLLdd')}T${luxonStart.toFormat('HHmmss')}`,
95+
endDatetime: `${luxonEnd.toFormat('yyyyLLdd')}T${luxonEnd.toFormat('HHmmss')}`,
96+
location: 'NYC',
97+
timezone: 'America/New_York',
98+
}
99+
88100
const AddToCalendarDropdown = AddToCalendarHOC(Button, Dropdown);
89101
const AddToCalendarModal = AddToCalendarHOC(Button, CalendarModal);
90102
const isiOS = /iPad|iPhone|iPod/.test(navigator.userAgent);
@@ -101,7 +113,7 @@ function Demo() {
101113
{`
102114
const startDatetime = moment().utc().add(2, 'days');
103115
const endDatetime = startDatetime.clone().add(2, 'hours');
104-
const duration = endDatetime.diff(startDatetime, 'hours');
116+
const duration = moment.duration(endDatetime.diff(startDatetime)).asHours();
105117
const event = {
106118
description: 'Description of event. Going to have a lot of fun doing things that we scheduled ahead of time.',
107119
duration,
@@ -253,6 +265,43 @@ function Demo() {
253265
timezone: 'Europe/London',
254266
}
255267
...
268+
<AddToCalendarModal
269+
className={componentStyles}
270+
linkProps={{
271+
className: linkStyles,
272+
}}
273+
event={eventInDifferentTimezone}
274+
/>
275+
`}
276+
</CodeSnippet>
277+
278+
<h2 className={subTitleStyles}>Use Moment Alternative</h2>
279+
<p className={paragraphStyles}>Moment is known to be a MASSIVE library. v2.22.2 is 64.2kb minified + gzipped and moment-timezone v0.5.21 is 89.8kb minified + gzipped. There are plenty of other date time libraries for JS that are way smaller. Using one of these helps you avoid overly bloating your application and sending too many vendor files to the client. One great option is Luxon. Luxon v.1.4.4 is 16.9kb minified + gzipped.</p>
280+
<p className={paragraphStyles}>This example shows how to use the Luxon library (instead of Moment) to construct <span className={highlightText}>startDatetime</span> and <span className={highlightText}>endDatetime</span></p>
281+
<AddToCalendarModal
282+
className={componentStyles}
283+
linkProps={{
284+
className: linkStyles,
285+
}}
286+
event={luxonEvent}
287+
/>
288+
<CodeSnippet>
289+
{`
290+
const AddToCalendarModal = AddToCalendarHOC(Button, CalendarModal);
291+
const startTime = DateTime.fromObject({ year: 2018, month: 10, day, 25, hour: 12 });
292+
const endTime = startTime.plus({ hours: 2 });
293+
const duration = endDatetime.diff(startDatetime).as('hours');
294+
const eventInDifferentTimezone = {
295+
...event,
296+
description: 'Description of event. Going to have a lot of fun doing things that we scheduled ahead of time.',
297+
duration,
298+
endDatetime: endTime.toFormat('YYYYMMDDTHHmmss'),
299+
location: 'London',
300+
startDatetime: startTime.toFormat('YYYYMMDDTHHmmss'),
301+
timezone: 'Europe/London',
302+
title: 'Super Fun Event',
303+
}
304+
...
256305
<AddToCalendarModal
257306
className={componentStyles}
258307
linkProps={{

src/lib/utils.js

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,16 @@ import { SHARE_SITES } from './enums';
99
*/
1010
export const formatDate = date => date && date.replace('+00:00', 'Z');
1111

12+
export const formatDuration = duration => {
13+
if (typeof duration === 'string') return duration;
14+
const parts = duration.toString().split('.');
15+
if (parts.length < 2) {
16+
parts.push('00');
17+
}
18+
19+
return parts.map(part => part.length === 2 ? part : `0${part}`).join('');
20+
};
21+
1222
/**
1323
* Tests provided UserAgent against Known Mobile User Agents
1424
* @returns {bool} isMobileDevice
@@ -40,7 +50,7 @@ const googleShareUrl = ({
4050
}) =>
4151
`https://calendar.google.com/calendar/render?action=TEMPLATE&dates=${
4252
startDatetime
43-
}/${endDatetime}&ctz=${timezone}&location=${location}&text=${title}&details=${description}`;
53+
}/${endDatetime}${timezone && `&ctz=${timezone}`}&location=${location}&text=${title}&details=${description}`;
4454

4555
/**
4656
* Takes an event object and returns a Yahoo Calendar Event URL
@@ -119,7 +129,7 @@ export const buildShareUrl = (
119129

120130
const data = {
121131
description: encodeURI ? encodeURIComponent(description) : description,
122-
duration,
132+
duration: formatDuration(duration),
123133
endDatetime: formatDate(endDatetime),
124134
location: encodeURI ? encodeURIComponent(location) : location,
125135
startDatetime: formatDate(startDatetime),

src/lib/utils.test.js

Lines changed: 122 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import moment from 'moment';
22
import { SHARE_SITES } from './enums';
3-
import { buildShareUrl, formatDate, padString } from './utils';
3+
import { buildShareUrl, formatDate, formatDuration, isInternetExplorer, isMobile } from './utils';
44

55
const testEvent = {
66
description: 'Description of event. Going to have a lot of fun doing things that we scheduled ahead of time.',
@@ -11,34 +11,143 @@ const testEvent = {
1111
title: 'Super Fun Event',
1212
}
1313

14+
const expectedOutputs = {
15+
google: 'https://calendar.google.com/calendar/render?action=TEMPLATE&dates=20150126T000000Z/20150126T020000Z&location=NYC&text=Super%20Fun%20Event&details=Description%20of%20event.%20Going%20to%20have%20a%20lot%20of%20fun%20doing%20things%20that%20we%20scheduled%20ahead%20of%20time.',
16+
yahoo: 'https://calendar.yahoo.com/?v=60&view=d&type=20&title=Super%20Fun%20Event&st=20150126T000000Z&dur=0200&desc=Description%20of%20event.%20Going%20to%20have%20a%20lot%20of%20fun%20doing%20things%20that%20we%20scheduled%20ahead%20of%20time.&in_loc=NYC',
17+
ics: 'BEGIN:VCALENDAR\nVERSION:2.0\nBEGIN:VEVENT\nURL:about:blank\nMETHOD:PUBLISH\nDTSTART:20150126T000000Z\nDTEND:20150126T020000Z\nSUMMARY:Super Fun Event\nDESCRIPTION:Description of event. Going to have a lot of fun doing things that we scheduled ahead of time.\nLOCATION:NYC\nEND:VEVENT\nEND:VCALENDAR',
18+
icsMobile: 'data:text/calendar;charset=utf8,BEGIN:VCALENDAR\nVERSION:2.0\nBEGIN:VEVENT\nURL:about:blank\nMETHOD:PUBLISH\nDTSTART:20150126T000000Z\nDTEND:20150126T020000Z\nSUMMARY:Super Fun Event\nDESCRIPTION:Description of event. Going to have a lot of fun doing things that we scheduled ahead of time.\nLOCATION:NYC\nEND:VEVENT\nEND:VCALENDAR',
19+
}
20+
1421
describe('formatDate', () => {
1522
it('replaces +00:00 from a date string with Z', () => {
1623
expect(formatDate('20180603T172721+00:00')).toEqual('20180603T172721Z');
1724
});
1825
});
1926

27+
describe('formatDuration', () => {
28+
it ('converts number 2 to string 0200', () => {
29+
expect(formatDuration(2)).toEqual('0200');
30+
});
31+
32+
it('converts number 2.25 to 0225', () => {
33+
expect(formatDuration(2.25)).toEqual('0225');
34+
});
35+
36+
it('returns string 0200 as it was received', () => {
37+
expect(formatDuration('0200')).toEqual('0200');
38+
});
39+
});
40+
2041
describe('buildShareUrl', () => {
21-
it('returns a proper Google share URL', () => {
22-
const result = buildShareUrl(testEvent, SHARE_SITES.GOOGLE);
23-
expect(result).toEqual('https://calendar.google.com/calendar/render?action=TEMPLATE&dates=20150126T000000Z/20150126T020000Z&location=NYC&text=Super%20Fun%20Event&details=Description%20of%20event.%20Going%20to%20have%20a%20lot%20of%20fun%20doing%20things%20that%20we%20scheduled%20ahead%20of%20time.');
42+
describe('Google', () => {
43+
it('returns a proper Google share URL', () => {
44+
const result = buildShareUrl(testEvent, SHARE_SITES.GOOGLE);
45+
expect(result).toEqual(expectedOutputs.google);
46+
});
2447
});
2548

26-
it('returns a proper Yahoo share URL', () => {
27-
const result = buildShareUrl(testEvent, SHARE_SITES.YAHOO);
28-
expect(result).toEqual('https://calendar.yahoo.com/?v=60&view=d&type=20&title=Super%20Fun%20Event&st=20150126T000000Z&dur=0200&desc=Description%20of%20event.%20Going%20to%20have%20a%20lot%20of%20fun%20doing%20things%20that%20we%20scheduled%20ahead%20of%20time.&in_loc=NYC')
49+
describe('Yahoo', () => {
50+
it('returns a proper Yahoo share URL', () => {
51+
const result = buildShareUrl(testEvent, SHARE_SITES.YAHOO);
52+
expect(result).toEqual(expectedOutputs.yahoo)
53+
});
54+
55+
it('returns a proper Yahoo share URL when duration is a number', () => {
56+
const result = buildShareUrl({...testEvent, duration: 2}, SHARE_SITES.YAHOO);
57+
expect(result).toEqual(expectedOutputs.yahoo);
58+
});
2959
});
3060

31-
it('returns a proper iCal content object', () => {
32-
const result = buildShareUrl(testEvent, SHARE_SITES.ICAL);
33-
expect(result).toEqual('BEGIN:VCALENDAR\nVERSION:2.0\nBEGIN:VEVENT\nURL:about:blank\nMETHOD:PUBLISH\nDTSTART:20150126T000000Z\nDTEND:20150126T020000Z\nSUMMARY:Super Fun Event\nDESCRIPTION:Description of event. Going to have a lot of fun doing things that we scheduled ahead of time.\nLOCATION:NYC\nEND:VEVENT\nEND:VCALENDAR');
61+
62+
describe('iCal', () => {
63+
it('returns a proper iCal content object', () => {
64+
const result = buildShareUrl(testEvent, SHARE_SITES.ICAL);
65+
expect(result).toEqual(expectedOutputs.ics);
66+
});
67+
68+
it('prepends a data URL when userAgent is mobile', () => {
69+
navigator.__defineGetter__('userAgent', function(){
70+
return "Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1";
71+
});
72+
73+
const result = buildShareUrl(testEvent, SHARE_SITES.ICAL);
74+
expect(result).toEqual(encodeURI(expectedOutputs.icsMobile));
75+
});
3476
});
77+
});
3578

36-
it('prepends a data URL when userAgent is mobile', () => {
79+
describe('isInternetExplorer', () => {
80+
it('returns true is userAgent is IE 11', () => {
81+
navigator.__defineGetter__('userAgent', function(){
82+
return "Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; .NET4.0C; .NET4.0E; .NET CLR 2.0.50727; .NET CLR 3.0.30729; .NET CLR 3.5.30729; rv:11.0) like Gecko";
83+
});
84+
85+
const result = isInternetExplorer();
86+
expect(result).toBe(true);
87+
});
88+
89+
it('returns true is userAgent is IE 10', () => {
90+
navigator.__defineGetter__('userAgent', function(){
91+
return "Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; WOW64; Trident/6.0; .NET4.0E; .NET4.0C; .NET CLR 3.5.30729; .NET CLR 2.0.50727; .NET CLR 3.0.30729)";
92+
});
93+
94+
const result = isInternetExplorer();
95+
expect(result).toBe(true);
96+
});
97+
98+
it('returns true is userAgent is IE 9', () => {
99+
navigator.__defineGetter__('userAgent', function(){
100+
return "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET4.0C; .NET4.0E)";
101+
});
102+
103+
const result = isInternetExplorer();
104+
expect(result).toBe(true);
105+
});
106+
107+
it('returns false is userAgent is MS Edge', () => {
108+
navigator.__defineGetter__('userAgent', function(){
109+
return "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36";
110+
});
111+
112+
const result = isInternetExplorer();
113+
expect(result).toBe(false);
114+
});
115+
116+
it('returns false is userAgent is not IE', () => {
117+
navigator.__defineGetter__('userAgent', function(){
118+
return "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36";
119+
});
120+
121+
const result = isInternetExplorer();
122+
expect(result).toBe(false);
123+
});
124+
})
125+
126+
describe('isMobile', () => {
127+
it('returns true if userAgent is iPhone', () => {
37128
navigator.__defineGetter__('userAgent', function(){
38129
return "Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1";
39130
});
40131

41-
const result = buildShareUrl(testEvent, SHARE_SITES.ICAL);
42-
expect(result).toEqual(encodeURI('data:text/calendar;charset=utf8,BEGIN:VCALENDAR\nVERSION:2.0\nBEGIN:VEVENT\nURL:about:blank\nMETHOD:PUBLISH\nDTSTART:20150126T000000Z\nDTEND:20150126T020000Z\nSUMMARY:Super Fun Event\nDESCRIPTION:Description of event. Going to have a lot of fun doing things that we scheduled ahead of time.\nLOCATION:NYC\nEND:VEVENT\nEND:VCALENDAR'));
132+
const result = isMobile();
133+
expect(result).toBe(true);
43134
});
135+
136+
it('returns true if userAgent is Android', () => {
137+
navigator.__defineGetter__('userAgent', function(){
138+
return "Mozilla/5.0 (Linux; Android 8.0.0; SM-G965F Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.109 Mobile Safari/537.36";
139+
});
140+
141+
const result = isMobile();
142+
expect(result).toBe(true);
143+
});
144+
145+
it('returns false if userAgent is desktop', () => {
146+
navigator.__defineGetter__('userAgent', function(){
147+
return "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36";
148+
});
149+
150+
const result = isMobile();
151+
expect(result).toBe(false);
152+
})
44153
});

yarn.lock

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6155,6 +6155,11 @@ lru-cache@^4.0.1, lru-cache@^4.1.1:
61556155
pseudomap "^1.0.2"
61566156
yallist "^2.1.2"
61576157

6158+
luxon@^1.4.4:
6159+
version "1.4.4"
6160+
resolved "https://registry.yarnpkg.com/luxon/-/luxon-1.4.4.tgz#36929fb9559d4119bec4a1b8bdee1516ddb63bf5"
6161+
integrity sha512-M21p8nbKZRLqVnQ0XAhhCo+2Z3Z6+zm9tuogDMZ2vlkFkw8AREF+d5/abn8tz1/ycu4cemNlqcQcc7HqjhrpTw==
6162+
61586163
make-dir@^1.0.0, make-dir@^1.1.0:
61596164
version "1.3.0"
61606165
resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.3.0.tgz#79c1033b80515bd6d24ec9933e860ca75ee27f0c"

0 commit comments

Comments
 (0)